milens 0.5.8 → 0.6.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.
package/dist/cli.js CHANGED
@@ -329,401 +329,401 @@ function generateDashboardHtml(stats) {
329
329
  ? Math.round((stats.totalTokensSaved / (stats.totalTokensOut + stats.totalTokensSaved)) * 100)
330
330
  : 0;
331
331
  const fmtNum = (n) => n >= 1_000_000 ? (n / 1_000_000).toFixed(1) + 'M' : n >= 1_000 ? (n / 1_000).toFixed(1) + 'K' : String(n);
332
- return `<!DOCTYPE html>
333
- <html lang="en">
334
- <head>
335
- <meta charset="UTF-8">
336
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
337
- <title>milens Dashboard</title>
338
- <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
339
- <style>
340
- :root {
341
- --bg: #0a0e14; --surface: #12171e; --card: #161d27; --card-hover: #1a2332;
342
- --border: #1e2a3a; --border-light: #2a3a4e;
343
- --text: #e2e8f0; --text-secondary: #94a3b8; --text-muted: #64748b;
344
- --accent: #60a5fa; --accent-dim: #60a5fa22;
345
- --green: #34d399; --green-dim: #34d39915;
346
- --orange: #fbbf24; --orange-dim: #fbbf2415;
347
- --purple: #a78bfa; --purple-dim: #a78bfa15;
348
- --red: #f87171;
349
- --radius: 16px; --radius-sm: 10px;
350
- }
351
- * { margin: 0; padding: 0; box-sizing: border-box; }
352
- body {
353
- background: var(--bg); color: var(--text);
354
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
355
- line-height: 1.5; min-height: 100vh;
356
- }
357
-
358
- /* ── Layout ── */
359
- .wrapper { max-width: 1400px; margin: 0 auto; padding: 32px 24px 80px; }
360
-
361
- /* ── Header ── */
362
- .header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 32px; }
363
- .header-left { display: flex; align-items: center; gap: 14px; }
364
- .logo { width: 40px; height: 40px; border-radius: 12px; background: linear-gradient(135deg, var(--accent), var(--purple)); display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 18px; color: #fff; }
365
- .header h1 { font-size: 22px; font-weight: 700; letter-spacing: -0.3px; }
366
- .header h1 span { color: var(--text-muted); font-weight: 400; font-size: 14px; margin-left: 8px; }
367
- .header-right { display: flex; align-items: center; gap: 12px; }
368
- .status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); animation: pulse 2s infinite; }
369
- @keyframes pulse { 0%,80%,100% { opacity: 1; } 40% { opacity: 0.4; } }
370
- .status-text { font-size: 12px; color: var(--text-muted); }
371
- .refresh-btn {
372
- background: var(--accent-dim); color: var(--accent); border: 1px solid var(--border);
373
- border-radius: var(--radius-sm); padding: 8px 16px; cursor: pointer;
374
- font-weight: 500; font-size: 13px; transition: all 0.2s;
375
- }
376
- .refresh-btn:hover { background: var(--accent); color: #0a0e14; }
377
-
378
- /* ── KPI Cards ── */
379
- .kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 28px; }
380
- .kpi {
381
- background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
382
- padding: 24px; position: relative; overflow: hidden; transition: border-color 0.2s;
383
- }
384
- .kpi:hover { border-color: var(--border-light); }
385
- .kpi-icon { width: 40px; height: 40px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; margin-bottom: 16px; }
386
- .kpi-icon.blue { background: var(--accent-dim); }
387
- .kpi-icon.green { background: var(--green-dim); }
388
- .kpi-icon.purple { background: var(--purple-dim); }
389
- .kpi-icon.orange { background: var(--orange-dim); }
390
- .kpi .value { font-size: 32px; font-weight: 800; letter-spacing: -1px; line-height: 1; }
391
- .kpi .value.blue { color: var(--accent); }
392
- .kpi .value.green { color: var(--green); }
393
- .kpi .value.purple { color: var(--purple); }
394
- .kpi .value.orange { color: var(--orange); }
395
- .kpi .label { color: var(--text-muted); font-size: 13px; margin-top: 6px; font-weight: 500; }
396
- .kpi .sub { color: var(--text-secondary); font-size: 12px; margin-top: 4px; }
397
- .kpi-glow {
398
- position: absolute; top: -40px; right: -40px; width: 120px; height: 120px;
399
- border-radius: 50%; opacity: 0.06; pointer-events: none;
400
- }
401
- .kpi-glow.blue { background: var(--accent); }
402
- .kpi-glow.green { background: var(--green); }
403
- .kpi-glow.purple { background: var(--purple); }
404
- .kpi-glow.orange { background: var(--orange); }
405
-
406
- /* ── Charts ── */
407
- .grid-2 { display: grid; grid-template-columns: 5fr 7fr; gap: 16px; margin-bottom: 16px; }
408
- .grid-full { margin-bottom: 16px; }
409
- .card {
410
- background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
411
- padding: 24px; transition: border-color 0.2s;
412
- }
413
- .card:hover { border-color: var(--border-light); }
414
- .card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
415
- .card-title { font-size: 14px; font-weight: 600; color: var(--text); }
416
- .card-subtitle { font-size: 12px; color: var(--text-muted); }
417
-
418
- /* ── Chart containers ── */
419
- .chart-container { position: relative; width: 100%; }
420
- .chart-container.h-280 { height: 280px; }
421
- .chart-container.h-300 { height: 300px; }
422
-
423
- /* ── Top Tools Bar ── */
424
- .tool-bars { display: flex; flex-direction: column; gap: 10px; }
425
- .tool-bar-row { display: flex; align-items: center; gap: 12px; }
426
- .tool-bar-name { width: 140px; font-size: 12px; font-family: 'SF Mono', 'Fira Code', monospace; color: var(--text-secondary); text-align: right; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
427
- .tool-bar-track { flex: 1; height: 28px; background: var(--surface); border-radius: 6px; overflow: hidden; position: relative; }
428
- .tool-bar-fill { height: 100%; border-radius: 6px; display: flex; align-items: center; padding: 0 10px; font-size: 11px; font-weight: 600; color: #fff; min-width: fit-content; transition: width 0.6s ease; }
429
- .tool-bar-count { font-size: 12px; color: var(--text-muted); min-width: 36px; text-align: right; }
430
-
431
- /* ── Recent Table ── */
432
- .table-wrapper { max-height: 400px; overflow-y: auto; border-radius: var(--radius-sm); }
433
- .table-wrapper::-webkit-scrollbar { width: 6px; }
434
- .table-wrapper::-webkit-scrollbar-track { background: transparent; }
435
- .table-wrapper::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 3px; }
436
- table { width: 100%; border-collapse: collapse; font-size: 13px; }
437
- thead { position: sticky; top: 0; z-index: 1; }
438
- th {
439
- text-align: left; color: var(--text-muted); font-weight: 500; padding: 10px 16px;
440
- background: var(--card); border-bottom: 1px solid var(--border); font-size: 11px;
441
- text-transform: uppercase; letter-spacing: 0.5px;
442
- }
443
- td { padding: 10px 16px; border-bottom: 1px solid var(--border); }
444
- tr:hover td { background: var(--surface); }
445
- .tool-badge {
446
- display: inline-flex; align-items: center; gap: 4px;
447
- background: var(--accent-dim); color: var(--accent); padding: 3px 10px;
448
- border-radius: 6px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px; font-weight: 500;
449
- }
450
- .when-text { color: var(--text-muted); }
451
- .duration-text { color: var(--text-secondary); font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; }
452
- .saved-text { color: var(--green); font-weight: 600; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; }
453
-
454
- /* ── Footer ── */
455
- .footer { text-align: center; padding: 24px 0 0; color: var(--text-muted); font-size: 12px; }
456
-
457
- /* ── Responsive ── */
458
- @media (max-width: 1024px) { .kpi-grid { grid-template-columns: repeat(2, 1fr); } .grid-2 { grid-template-columns: 1fr; } }
459
- @media (max-width: 640px) { .kpi-grid { grid-template-columns: 1fr; } .wrapper { padding: 16px 12px 80px; } }
460
- </style>
461
- </head>
462
- <body>
463
- <div class="wrapper">
464
-
465
- <!-- Header -->
466
- <div class="header">
467
- <div class="header-left">
468
- <div class="logo">m</div>
469
- <h1>milens <span>dashboard</span></h1>
470
- </div>
471
- <div class="header-right">
472
- <div class="status-dot"></div>
473
- <span class="status-text">Live</span>
474
- <button class="refresh-btn" onclick="refreshData()">&#8635; Refresh</button>
475
- </div>
476
- </div>
477
-
478
- <!-- KPIs -->
479
- <div class="kpi-grid">
480
- <div class="kpi">
481
- <div class="kpi-icon blue">&#9881;</div>
482
- <div class="value blue" id="kpi-calls">${fmtNum(stats.totalCalls)}</div>
483
- <div class="label">Tool Calls</div>
484
- <div class="sub" id="kpi-calls-sub">${stats.totalCalls.toLocaleString()} total invocations</div>
485
- <div class="kpi-glow blue"></div>
486
- </div>
487
- <div class="kpi">
488
- <div class="kpi-icon green">&#9889;</div>
489
- <div class="value green" id="kpi-saved">${fmtNum(stats.totalTokensSaved)}</div>
490
- <div class="label">Tokens Saved</div>
491
- <div class="sub" id="kpi-saved-sub">${stats.totalTokensSaved.toLocaleString()} tokens not wasted</div>
492
- <div class="kpi-glow green"></div>
493
- </div>
494
- <div class="kpi">
495
- <div class="kpi-icon purple">&#9733;</div>
496
- <div class="value purple" id="kpi-pct">${savingsPercent}%</div>
497
- <div class="label">Token Efficiency</div>
498
- <div class="sub">${fmtNum(stats.totalTokensOut)} returned vs ${fmtNum(stats.totalTokensSaved)} saved</div>
499
- <div class="kpi-glow purple"></div>
500
- </div>
501
- <div class="kpi">
502
- <div class="kpi-icon orange">&#9201;</div>
503
- <div class="value orange" id="kpi-avg">${stats.totalCalls > 0 ? Math.round(stats.totalDurationMs / stats.totalCalls) : 0}ms</div>
504
- <div class="label">Avg Response Time</div>
505
- <div class="sub">${(stats.totalDurationMs / 1000).toFixed(1)}s total processing</div>
506
- <div class="kpi-glow orange"></div>
507
- </div>
508
- </div>
509
-
510
- <!-- Charts Row -->
511
- <div class="grid-2">
512
- <div class="card">
513
- <div class="card-header">
514
- <div>
515
- <div class="card-title">Top Tools</div>
516
- <div class="card-subtitle">By number of calls</div>
517
- </div>
518
- </div>
519
- <div id="toolBars" class="tool-bars"></div>
520
- </div>
521
- <div class="card">
522
- <div class="card-header">
523
- <div>
524
- <div class="card-title">Daily Activity</div>
525
- <div class="card-subtitle">Calls &amp; tokens saved over the last 30 days</div>
526
- </div>
527
- </div>
528
- <div class="chart-container h-280"><canvas id="dayChart"></canvas></div>
529
- </div>
530
- </div>
531
-
532
- <!-- Savings Distribution -->
533
- <div class="grid-2" style="grid-template-columns: 7fr 5fr;">
534
- <div class="card">
535
- <div class="card-header">
536
- <div>
537
- <div class="card-title">Recent Tool Calls</div>
538
- <div class="card-subtitle">Last 50 invocations</div>
539
- </div>
540
- </div>
541
- <div class="table-wrapper">
542
- <table>
543
- <thead><tr><th>Tool</th><th>When</th><th>Duration</th><th style="text-align:right">Tokens Saved</th></tr></thead>
544
- <tbody id="recentBody"></tbody>
545
- </table>
546
- </div>
547
- </div>
548
- <div class="card">
549
- <div class="card-header">
550
- <div>
551
- <div class="card-title">Savings by Tool</div>
552
- <div class="card-subtitle">Token savings distribution</div>
553
- </div>
554
- </div>
555
- <div class="chart-container h-300"><canvas id="savingsChart"></canvas></div>
556
- </div>
557
- </div>
558
-
559
- <div class="footer">milens &middot; auto-refreshes every 30s</div>
560
- </div>
561
-
562
- <script>
563
- const COLORS = ['#60a5fa','#34d399','#fbbf24','#a78bfa','#f87171','#2dd4bf','#818cf8','#fb923c','#e879f9','#38bdf8','#4ade80','#facc15','#f472b6','#22d3ee','#a3e635','#c084fc'];
564
- const BAR_COLORS = ['#60a5fa','#34d399','#fbbf24','#a78bfa','#f87171','#2dd4bf','#818cf8','#fb923c','#e879f9','#38bdf8'];
565
- let byTool = ${byToolJson};
566
- let byDay = ${byDayJson};
567
-
568
- function fmtK(n) { return n >= 1e6 ? (n/1e6).toFixed(1)+'M' : n >= 1e3 ? (n/1e3).toFixed(1)+'K' : n; }
569
-
570
- /* ── Top Tools Horizontal Bars ── */
571
- function renderToolBars() {
572
- const el = document.getElementById('toolBars');
573
- const sorted = [...byTool].sort((a,b) => b.calls - a.calls).slice(0, 10);
574
- const maxCalls = sorted[0]?.calls || 1;
575
- el.innerHTML = sorted.map((t, i) => {
576
- const pct = Math.max(8, (t.calls / maxCalls) * 100);
577
- const col = BAR_COLORS[i % BAR_COLORS.length];
578
- return \`<div class="tool-bar-row">
579
- <span class="tool-bar-name">\${t.tool}</span>
580
- <div class="tool-bar-track">
581
- <div class="tool-bar-fill" style="width:\${pct}%;background:linear-gradient(90deg,\${col}dd,\${col}88)">\${t.calls}</div>
582
- </div>
583
- <span class="tool-bar-count">\${fmtK(t.tokensSaved)}</span>
584
- </div>\`;
585
- }).join('');
586
- }
587
-
588
- /* ── Daily Activity Chart ── */
589
- let dayChartInstance = null;
590
- function renderDayChart() {
591
- if (dayChartInstance) dayChartInstance.destroy();
592
- const ctx = document.getElementById('dayChart');
593
- const labels = byDay.map(d => {
594
- const parts = d.date.split('-');
595
- return parts[1] + '/' + parts[2];
596
- });
597
- dayChartInstance = new Chart(ctx, {
598
- type: 'bar',
599
- data: {
600
- labels,
601
- datasets: [
602
- {
603
- label: 'Calls', data: byDay.map(d => d.calls),
604
- backgroundColor: '#60a5fa44', hoverBackgroundColor: '#60a5fa88',
605
- borderRadius: 4, borderSkipped: false, yAxisID: 'y', barPercentage: 0.7,
606
- },
607
- {
608
- label: 'Tokens Saved', data: byDay.map(d => d.tokensSaved),
609
- type: 'line', borderColor: '#34d399', pointBackgroundColor: '#34d399',
610
- pointRadius: 2, pointHoverRadius: 5, borderWidth: 2.5,
611
- yAxisID: 'y1', tension: 0.4, fill: { target: 'origin', above: '#34d39910' },
612
- },
613
- ],
614
- },
615
- options: {
616
- responsive: true, maintainAspectRatio: false,
617
- interaction: { mode: 'index', intersect: false },
618
- scales: {
619
- x: {
620
- ticks: { color: '#64748b', font: { size: 11 }, maxRotation: 0, autoSkip: true, maxTicksLimit: 15 },
621
- grid: { display: false },
622
- },
623
- y: {
624
- position: 'left', ticks: { color: '#60a5fa', font: { size: 11 } },
625
- grid: { color: '#1e2a3a' }, title: { display: true, text: 'Calls', color: '#60a5fa', font: { size: 11 } },
626
- },
627
- y1: {
628
- position: 'right', ticks: { color: '#34d399', font: { size: 11 }, callback: v => fmtK(v) },
629
- grid: { drawOnChartArea: false }, title: { display: true, text: 'Tokens Saved', color: '#34d399', font: { size: 11 } },
630
- },
631
- },
632
- plugins: {
633
- legend: { labels: { color: '#94a3b8', boxWidth: 12, usePointStyle: true, padding: 16 } },
634
- tooltip: {
635
- backgroundColor: '#1e293b', borderColor: '#334155', borderWidth: 1, titleColor: '#e2e8f0',
636
- bodyColor: '#94a3b8', cornerRadius: 8, padding: 12,
637
- callbacks: { label: ctx => ctx.dataset.label + ': ' + (ctx.datasetIndex === 1 ? fmtK(ctx.raw) : ctx.raw) },
638
- },
639
- },
640
- },
641
- });
642
- }
643
-
644
- /* ── Savings Doughnut ── */
645
- function renderSavingsChart() {
646
- const ctx = document.getElementById('savingsChart');
647
- const sorted = [...byTool].sort((a,b) => b.tokensSaved - a.tokensSaved);
648
- const top = sorted.slice(0, 8);
649
- const rest = sorted.slice(8);
650
- if (rest.length) top.push({ tool: 'others', tokensSaved: rest.reduce((s,t) => s + t.tokensSaved, 0), calls: 0 });
651
- new Chart(ctx, {
652
- type: 'doughnut',
653
- data: {
654
- labels: top.map(t => t.tool),
655
- datasets: [{
656
- data: top.map(t => t.tokensSaved),
657
- backgroundColor: top.map((_, i) => COLORS[i]),
658
- borderWidth: 0, hoverOffset: 6,
659
- }],
660
- },
661
- options: {
662
- responsive: true, cutout: '68%',
663
- plugins: {
664
- legend: { position: 'right', labels: { color: '#94a3b8', font: { size: 12 }, padding: 8, usePointStyle: true, pointStyleWidth: 10 } },
665
- tooltip: {
666
- backgroundColor: '#1e293b', borderColor: '#334155', borderWidth: 1, titleColor: '#e2e8f0',
667
- bodyColor: '#94a3b8', cornerRadius: 8, padding: 12,
668
- callbacks: { label: ctx => ' ' + ctx.label + ': ' + fmtK(ctx.raw) + ' tokens' },
669
- },
670
- },
671
- },
672
- });
673
- }
674
-
675
- /* ── Recent Calls Table ── */
676
- function renderRecent(data) {
677
- const tbody = document.getElementById('recentBody');
678
- tbody.innerHTML = data.map(r => {
679
- const ago = timeAgo(r.calledAt);
680
- return \`<tr>
681
- <td><span class="tool-badge">\${r.tool}</span></td>
682
- <td class="when-text">\${ago}</td>
683
- <td class="duration-text">\${r.durationMs}ms</td>
684
- <td class="saved-text" style="text-align:right">+\${r.tokensSaved.toLocaleString()}</td>
685
- </tr>\`;
686
- }).join('');
687
- }
688
-
689
- function timeAgo(iso) {
690
- const d = new Date(iso.includes('T') ? iso : iso + 'Z');
691
- const s = Math.floor((Date.now() - d.getTime()) / 1000);
692
- if (s < 0) return 'just now';
693
- if (s < 60) return s + 's ago';
694
- if (s < 3600) return Math.floor(s/60) + 'm ago';
695
- if (s < 86400) return Math.floor(s/3600) + 'h ago';
696
- return Math.floor(s/86400) + 'd ago';
697
- }
698
-
699
- /* ── Refresh ── */
700
- async function refreshData() {
701
- const btn = document.querySelector('.refresh-btn');
702
- btn.textContent = '⟳ Loading...';
703
- try {
704
- const res = await fetch('/api/stats');
705
- const data = await res.json();
706
- byTool = data.byTool; byDay = data.byDay;
707
- document.getElementById('kpi-calls').textContent = fmtK(data.totalCalls);
708
- document.getElementById('kpi-calls-sub').textContent = data.totalCalls.toLocaleString() + ' total invocations';
709
- document.getElementById('kpi-saved').textContent = fmtK(data.totalTokensSaved);
710
- document.getElementById('kpi-saved-sub').textContent = data.totalTokensSaved.toLocaleString() + ' tokens not wasted';
711
- const pct = data.totalTokensOut > 0 ? Math.round((data.totalTokensSaved / (data.totalTokensOut + data.totalTokensSaved)) * 100) : 0;
712
- document.getElementById('kpi-pct').textContent = pct + '%';
713
- document.getElementById('kpi-avg').textContent = (data.totalCalls > 0 ? Math.round(data.totalDurationMs / data.totalCalls) : 0) + 'ms';
714
- renderToolBars(); renderDayChart(); renderRecent(data.recentCalls);
715
- } catch(e) { console.error('Refresh failed', e); }
716
- btn.innerHTML = '&#8635; Refresh';
717
- }
718
-
719
- /* ── Init ── */
720
- renderToolBars();
721
- renderDayChart();
722
- renderSavingsChart();
723
- renderRecent(${recentJson});
724
- setInterval(refreshData, 30000);
725
- </script>
726
- </body>
332
+ return `<!DOCTYPE html>
333
+ <html lang="en">
334
+ <head>
335
+ <meta charset="UTF-8">
336
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
337
+ <title>milens Dashboard</title>
338
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
339
+ <style>
340
+ :root {
341
+ --bg: #0a0e14; --surface: #12171e; --card: #161d27; --card-hover: #1a2332;
342
+ --border: #1e2a3a; --border-light: #2a3a4e;
343
+ --text: #e2e8f0; --text-secondary: #94a3b8; --text-muted: #64748b;
344
+ --accent: #60a5fa; --accent-dim: #60a5fa22;
345
+ --green: #34d399; --green-dim: #34d39915;
346
+ --orange: #fbbf24; --orange-dim: #fbbf2415;
347
+ --purple: #a78bfa; --purple-dim: #a78bfa15;
348
+ --red: #f87171;
349
+ --radius: 16px; --radius-sm: 10px;
350
+ }
351
+ * { margin: 0; padding: 0; box-sizing: border-box; }
352
+ body {
353
+ background: var(--bg); color: var(--text);
354
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
355
+ line-height: 1.5; min-height: 100vh;
356
+ }
357
+
358
+ /* ── Layout ── */
359
+ .wrapper { max-width: 1400px; margin: 0 auto; padding: 32px 24px 80px; }
360
+
361
+ /* ── Header ── */
362
+ .header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 32px; }
363
+ .header-left { display: flex; align-items: center; gap: 14px; }
364
+ .logo { width: 40px; height: 40px; border-radius: 12px; background: linear-gradient(135deg, var(--accent), var(--purple)); display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 18px; color: #fff; }
365
+ .header h1 { font-size: 22px; font-weight: 700; letter-spacing: -0.3px; }
366
+ .header h1 span { color: var(--text-muted); font-weight: 400; font-size: 14px; margin-left: 8px; }
367
+ .header-right { display: flex; align-items: center; gap: 12px; }
368
+ .status-dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green); animation: pulse 2s infinite; }
369
+ @keyframes pulse { 0%,80%,100% { opacity: 1; } 40% { opacity: 0.4; } }
370
+ .status-text { font-size: 12px; color: var(--text-muted); }
371
+ .refresh-btn {
372
+ background: var(--accent-dim); color: var(--accent); border: 1px solid var(--border);
373
+ border-radius: var(--radius-sm); padding: 8px 16px; cursor: pointer;
374
+ font-weight: 500; font-size: 13px; transition: all 0.2s;
375
+ }
376
+ .refresh-btn:hover { background: var(--accent); color: #0a0e14; }
377
+
378
+ /* ── KPI Cards ── */
379
+ .kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin-bottom: 28px; }
380
+ .kpi {
381
+ background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
382
+ padding: 24px; position: relative; overflow: hidden; transition: border-color 0.2s;
383
+ }
384
+ .kpi:hover { border-color: var(--border-light); }
385
+ .kpi-icon { width: 40px; height: 40px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; margin-bottom: 16px; }
386
+ .kpi-icon.blue { background: var(--accent-dim); }
387
+ .kpi-icon.green { background: var(--green-dim); }
388
+ .kpi-icon.purple { background: var(--purple-dim); }
389
+ .kpi-icon.orange { background: var(--orange-dim); }
390
+ .kpi .value { font-size: 32px; font-weight: 800; letter-spacing: -1px; line-height: 1; }
391
+ .kpi .value.blue { color: var(--accent); }
392
+ .kpi .value.green { color: var(--green); }
393
+ .kpi .value.purple { color: var(--purple); }
394
+ .kpi .value.orange { color: var(--orange); }
395
+ .kpi .label { color: var(--text-muted); font-size: 13px; margin-top: 6px; font-weight: 500; }
396
+ .kpi .sub { color: var(--text-secondary); font-size: 12px; margin-top: 4px; }
397
+ .kpi-glow {
398
+ position: absolute; top: -40px; right: -40px; width: 120px; height: 120px;
399
+ border-radius: 50%; opacity: 0.06; pointer-events: none;
400
+ }
401
+ .kpi-glow.blue { background: var(--accent); }
402
+ .kpi-glow.green { background: var(--green); }
403
+ .kpi-glow.purple { background: var(--purple); }
404
+ .kpi-glow.orange { background: var(--orange); }
405
+
406
+ /* ── Charts ── */
407
+ .grid-2 { display: grid; grid-template-columns: 5fr 7fr; gap: 16px; margin-bottom: 16px; }
408
+ .grid-full { margin-bottom: 16px; }
409
+ .card {
410
+ background: var(--card); border: 1px solid var(--border); border-radius: var(--radius);
411
+ padding: 24px; transition: border-color 0.2s;
412
+ }
413
+ .card:hover { border-color: var(--border-light); }
414
+ .card-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
415
+ .card-title { font-size: 14px; font-weight: 600; color: var(--text); }
416
+ .card-subtitle { font-size: 12px; color: var(--text-muted); }
417
+
418
+ /* ── Chart containers ── */
419
+ .chart-container { position: relative; width: 100%; }
420
+ .chart-container.h-280 { height: 280px; }
421
+ .chart-container.h-300 { height: 300px; }
422
+
423
+ /* ── Top Tools Bar ── */
424
+ .tool-bars { display: flex; flex-direction: column; gap: 10px; }
425
+ .tool-bar-row { display: flex; align-items: center; gap: 12px; }
426
+ .tool-bar-name { width: 140px; font-size: 12px; font-family: 'SF Mono', 'Fira Code', monospace; color: var(--text-secondary); text-align: right; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
427
+ .tool-bar-track { flex: 1; height: 28px; background: var(--surface); border-radius: 6px; overflow: hidden; position: relative; }
428
+ .tool-bar-fill { height: 100%; border-radius: 6px; display: flex; align-items: center; padding: 0 10px; font-size: 11px; font-weight: 600; color: #fff; min-width: fit-content; transition: width 0.6s ease; }
429
+ .tool-bar-count { font-size: 12px; color: var(--text-muted); min-width: 36px; text-align: right; }
430
+
431
+ /* ── Recent Table ── */
432
+ .table-wrapper { max-height: 400px; overflow-y: auto; border-radius: var(--radius-sm); }
433
+ .table-wrapper::-webkit-scrollbar { width: 6px; }
434
+ .table-wrapper::-webkit-scrollbar-track { background: transparent; }
435
+ .table-wrapper::-webkit-scrollbar-thumb { background: var(--border-light); border-radius: 3px; }
436
+ table { width: 100%; border-collapse: collapse; font-size: 13px; }
437
+ thead { position: sticky; top: 0; z-index: 1; }
438
+ th {
439
+ text-align: left; color: var(--text-muted); font-weight: 500; padding: 10px 16px;
440
+ background: var(--card); border-bottom: 1px solid var(--border); font-size: 11px;
441
+ text-transform: uppercase; letter-spacing: 0.5px;
442
+ }
443
+ td { padding: 10px 16px; border-bottom: 1px solid var(--border); }
444
+ tr:hover td { background: var(--surface); }
445
+ .tool-badge {
446
+ display: inline-flex; align-items: center; gap: 4px;
447
+ background: var(--accent-dim); color: var(--accent); padding: 3px 10px;
448
+ border-radius: 6px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 11px; font-weight: 500;
449
+ }
450
+ .when-text { color: var(--text-muted); }
451
+ .duration-text { color: var(--text-secondary); font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; }
452
+ .saved-text { color: var(--green); font-weight: 600; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; }
453
+
454
+ /* ── Footer ── */
455
+ .footer { text-align: center; padding: 24px 0 0; color: var(--text-muted); font-size: 12px; }
456
+
457
+ /* ── Responsive ── */
458
+ @media (max-width: 1024px) { .kpi-grid { grid-template-columns: repeat(2, 1fr); } .grid-2 { grid-template-columns: 1fr; } }
459
+ @media (max-width: 640px) { .kpi-grid { grid-template-columns: 1fr; } .wrapper { padding: 16px 12px 80px; } }
460
+ </style>
461
+ </head>
462
+ <body>
463
+ <div class="wrapper">
464
+
465
+ <!-- Header -->
466
+ <div class="header">
467
+ <div class="header-left">
468
+ <div class="logo">m</div>
469
+ <h1>milens <span>dashboard</span></h1>
470
+ </div>
471
+ <div class="header-right">
472
+ <div class="status-dot"></div>
473
+ <span class="status-text">Live</span>
474
+ <button class="refresh-btn" onclick="refreshData()">&#8635; Refresh</button>
475
+ </div>
476
+ </div>
477
+
478
+ <!-- KPIs -->
479
+ <div class="kpi-grid">
480
+ <div class="kpi">
481
+ <div class="kpi-icon blue">&#9881;</div>
482
+ <div class="value blue" id="kpi-calls">${fmtNum(stats.totalCalls)}</div>
483
+ <div class="label">Tool Calls</div>
484
+ <div class="sub" id="kpi-calls-sub">${stats.totalCalls.toLocaleString()} total invocations</div>
485
+ <div class="kpi-glow blue"></div>
486
+ </div>
487
+ <div class="kpi">
488
+ <div class="kpi-icon green">&#9889;</div>
489
+ <div class="value green" id="kpi-saved">${fmtNum(stats.totalTokensSaved)}</div>
490
+ <div class="label">Tokens Saved</div>
491
+ <div class="sub" id="kpi-saved-sub">${stats.totalTokensSaved.toLocaleString()} tokens not wasted</div>
492
+ <div class="kpi-glow green"></div>
493
+ </div>
494
+ <div class="kpi">
495
+ <div class="kpi-icon purple">&#9733;</div>
496
+ <div class="value purple" id="kpi-pct">${savingsPercent}%</div>
497
+ <div class="label">Token Efficiency</div>
498
+ <div class="sub">${fmtNum(stats.totalTokensOut)} returned vs ${fmtNum(stats.totalTokensSaved)} saved</div>
499
+ <div class="kpi-glow purple"></div>
500
+ </div>
501
+ <div class="kpi">
502
+ <div class="kpi-icon orange">&#9201;</div>
503
+ <div class="value orange" id="kpi-avg">${stats.totalCalls > 0 ? Math.round(stats.totalDurationMs / stats.totalCalls) : 0}ms</div>
504
+ <div class="label">Avg Response Time</div>
505
+ <div class="sub">${(stats.totalDurationMs / 1000).toFixed(1)}s total processing</div>
506
+ <div class="kpi-glow orange"></div>
507
+ </div>
508
+ </div>
509
+
510
+ <!-- Charts Row -->
511
+ <div class="grid-2">
512
+ <div class="card">
513
+ <div class="card-header">
514
+ <div>
515
+ <div class="card-title">Top Tools</div>
516
+ <div class="card-subtitle">By number of calls</div>
517
+ </div>
518
+ </div>
519
+ <div id="toolBars" class="tool-bars"></div>
520
+ </div>
521
+ <div class="card">
522
+ <div class="card-header">
523
+ <div>
524
+ <div class="card-title">Daily Activity</div>
525
+ <div class="card-subtitle">Calls &amp; tokens saved over the last 30 days</div>
526
+ </div>
527
+ </div>
528
+ <div class="chart-container h-280"><canvas id="dayChart"></canvas></div>
529
+ </div>
530
+ </div>
531
+
532
+ <!-- Savings Distribution -->
533
+ <div class="grid-2" style="grid-template-columns: 7fr 5fr;">
534
+ <div class="card">
535
+ <div class="card-header">
536
+ <div>
537
+ <div class="card-title">Recent Tool Calls</div>
538
+ <div class="card-subtitle">Last 50 invocations</div>
539
+ </div>
540
+ </div>
541
+ <div class="table-wrapper">
542
+ <table>
543
+ <thead><tr><th>Tool</th><th>When</th><th>Duration</th><th style="text-align:right">Tokens Saved</th></tr></thead>
544
+ <tbody id="recentBody"></tbody>
545
+ </table>
546
+ </div>
547
+ </div>
548
+ <div class="card">
549
+ <div class="card-header">
550
+ <div>
551
+ <div class="card-title">Savings by Tool</div>
552
+ <div class="card-subtitle">Token savings distribution</div>
553
+ </div>
554
+ </div>
555
+ <div class="chart-container h-300"><canvas id="savingsChart"></canvas></div>
556
+ </div>
557
+ </div>
558
+
559
+ <div class="footer">milens &middot; auto-refreshes every 30s</div>
560
+ </div>
561
+
562
+ <script>
563
+ const COLORS = ['#60a5fa','#34d399','#fbbf24','#a78bfa','#f87171','#2dd4bf','#818cf8','#fb923c','#e879f9','#38bdf8','#4ade80','#facc15','#f472b6','#22d3ee','#a3e635','#c084fc'];
564
+ const BAR_COLORS = ['#60a5fa','#34d399','#fbbf24','#a78bfa','#f87171','#2dd4bf','#818cf8','#fb923c','#e879f9','#38bdf8'];
565
+ let byTool = ${byToolJson};
566
+ let byDay = ${byDayJson};
567
+
568
+ function fmtK(n) { return n >= 1e6 ? (n/1e6).toFixed(1)+'M' : n >= 1e3 ? (n/1e3).toFixed(1)+'K' : n; }
569
+
570
+ /* ── Top Tools Horizontal Bars ── */
571
+ function renderToolBars() {
572
+ const el = document.getElementById('toolBars');
573
+ const sorted = [...byTool].sort((a,b) => b.calls - a.calls).slice(0, 10);
574
+ const maxCalls = sorted[0]?.calls || 1;
575
+ el.innerHTML = sorted.map((t, i) => {
576
+ const pct = Math.max(8, (t.calls / maxCalls) * 100);
577
+ const col = BAR_COLORS[i % BAR_COLORS.length];
578
+ return \`<div class="tool-bar-row">
579
+ <span class="tool-bar-name">\${t.tool}</span>
580
+ <div class="tool-bar-track">
581
+ <div class="tool-bar-fill" style="width:\${pct}%;background:linear-gradient(90deg,\${col}dd,\${col}88)">\${t.calls}</div>
582
+ </div>
583
+ <span class="tool-bar-count">\${fmtK(t.tokensSaved)}</span>
584
+ </div>\`;
585
+ }).join('');
586
+ }
587
+
588
+ /* ── Daily Activity Chart ── */
589
+ let dayChartInstance = null;
590
+ function renderDayChart() {
591
+ if (dayChartInstance) dayChartInstance.destroy();
592
+ const ctx = document.getElementById('dayChart');
593
+ const labels = byDay.map(d => {
594
+ const parts = d.date.split('-');
595
+ return parts[1] + '/' + parts[2];
596
+ });
597
+ dayChartInstance = new Chart(ctx, {
598
+ type: 'bar',
599
+ data: {
600
+ labels,
601
+ datasets: [
602
+ {
603
+ label: 'Calls', data: byDay.map(d => d.calls),
604
+ backgroundColor: '#60a5fa44', hoverBackgroundColor: '#60a5fa88',
605
+ borderRadius: 4, borderSkipped: false, yAxisID: 'y', barPercentage: 0.7,
606
+ },
607
+ {
608
+ label: 'Tokens Saved', data: byDay.map(d => d.tokensSaved),
609
+ type: 'line', borderColor: '#34d399', pointBackgroundColor: '#34d399',
610
+ pointRadius: 2, pointHoverRadius: 5, borderWidth: 2.5,
611
+ yAxisID: 'y1', tension: 0.4, fill: { target: 'origin', above: '#34d39910' },
612
+ },
613
+ ],
614
+ },
615
+ options: {
616
+ responsive: true, maintainAspectRatio: false,
617
+ interaction: { mode: 'index', intersect: false },
618
+ scales: {
619
+ x: {
620
+ ticks: { color: '#64748b', font: { size: 11 }, maxRotation: 0, autoSkip: true, maxTicksLimit: 15 },
621
+ grid: { display: false },
622
+ },
623
+ y: {
624
+ position: 'left', ticks: { color: '#60a5fa', font: { size: 11 } },
625
+ grid: { color: '#1e2a3a' }, title: { display: true, text: 'Calls', color: '#60a5fa', font: { size: 11 } },
626
+ },
627
+ y1: {
628
+ position: 'right', ticks: { color: '#34d399', font: { size: 11 }, callback: v => fmtK(v) },
629
+ grid: { drawOnChartArea: false }, title: { display: true, text: 'Tokens Saved', color: '#34d399', font: { size: 11 } },
630
+ },
631
+ },
632
+ plugins: {
633
+ legend: { labels: { color: '#94a3b8', boxWidth: 12, usePointStyle: true, padding: 16 } },
634
+ tooltip: {
635
+ backgroundColor: '#1e293b', borderColor: '#334155', borderWidth: 1, titleColor: '#e2e8f0',
636
+ bodyColor: '#94a3b8', cornerRadius: 8, padding: 12,
637
+ callbacks: { label: ctx => ctx.dataset.label + ': ' + (ctx.datasetIndex === 1 ? fmtK(ctx.raw) : ctx.raw) },
638
+ },
639
+ },
640
+ },
641
+ });
642
+ }
643
+
644
+ /* ── Savings Doughnut ── */
645
+ function renderSavingsChart() {
646
+ const ctx = document.getElementById('savingsChart');
647
+ const sorted = [...byTool].sort((a,b) => b.tokensSaved - a.tokensSaved);
648
+ const top = sorted.slice(0, 8);
649
+ const rest = sorted.slice(8);
650
+ if (rest.length) top.push({ tool: 'others', tokensSaved: rest.reduce((s,t) => s + t.tokensSaved, 0), calls: 0 });
651
+ new Chart(ctx, {
652
+ type: 'doughnut',
653
+ data: {
654
+ labels: top.map(t => t.tool),
655
+ datasets: [{
656
+ data: top.map(t => t.tokensSaved),
657
+ backgroundColor: top.map((_, i) => COLORS[i]),
658
+ borderWidth: 0, hoverOffset: 6,
659
+ }],
660
+ },
661
+ options: {
662
+ responsive: true, cutout: '68%',
663
+ plugins: {
664
+ legend: { position: 'right', labels: { color: '#94a3b8', font: { size: 12 }, padding: 8, usePointStyle: true, pointStyleWidth: 10 } },
665
+ tooltip: {
666
+ backgroundColor: '#1e293b', borderColor: '#334155', borderWidth: 1, titleColor: '#e2e8f0',
667
+ bodyColor: '#94a3b8', cornerRadius: 8, padding: 12,
668
+ callbacks: { label: ctx => ' ' + ctx.label + ': ' + fmtK(ctx.raw) + ' tokens' },
669
+ },
670
+ },
671
+ },
672
+ });
673
+ }
674
+
675
+ /* ── Recent Calls Table ── */
676
+ function renderRecent(data) {
677
+ const tbody = document.getElementById('recentBody');
678
+ tbody.innerHTML = data.map(r => {
679
+ const ago = timeAgo(r.calledAt);
680
+ return \`<tr>
681
+ <td><span class="tool-badge">\${r.tool}</span></td>
682
+ <td class="when-text">\${ago}</td>
683
+ <td class="duration-text">\${r.durationMs}ms</td>
684
+ <td class="saved-text" style="text-align:right">+\${r.tokensSaved.toLocaleString()}</td>
685
+ </tr>\`;
686
+ }).join('');
687
+ }
688
+
689
+ function timeAgo(iso) {
690
+ const d = new Date(iso.includes('T') ? iso : iso + 'Z');
691
+ const s = Math.floor((Date.now() - d.getTime()) / 1000);
692
+ if (s < 0) return 'just now';
693
+ if (s < 60) return s + 's ago';
694
+ if (s < 3600) return Math.floor(s/60) + 'm ago';
695
+ if (s < 86400) return Math.floor(s/3600) + 'h ago';
696
+ return Math.floor(s/86400) + 'd ago';
697
+ }
698
+
699
+ /* ── Refresh ── */
700
+ async function refreshData() {
701
+ const btn = document.querySelector('.refresh-btn');
702
+ btn.textContent = '⟳ Loading...';
703
+ try {
704
+ const res = await fetch('/api/stats');
705
+ const data = await res.json();
706
+ byTool = data.byTool; byDay = data.byDay;
707
+ document.getElementById('kpi-calls').textContent = fmtK(data.totalCalls);
708
+ document.getElementById('kpi-calls-sub').textContent = data.totalCalls.toLocaleString() + ' total invocations';
709
+ document.getElementById('kpi-saved').textContent = fmtK(data.totalTokensSaved);
710
+ document.getElementById('kpi-saved-sub').textContent = data.totalTokensSaved.toLocaleString() + ' tokens not wasted';
711
+ const pct = data.totalTokensOut > 0 ? Math.round((data.totalTokensSaved / (data.totalTokensOut + data.totalTokensSaved)) * 100) : 0;
712
+ document.getElementById('kpi-pct').textContent = pct + '%';
713
+ document.getElementById('kpi-avg').textContent = (data.totalCalls > 0 ? Math.round(data.totalDurationMs / data.totalCalls) : 0) + 'ms';
714
+ renderToolBars(); renderDayChart(); renderRecent(data.recentCalls);
715
+ } catch(e) { console.error('Refresh failed', e); }
716
+ btn.innerHTML = '&#8635; Refresh';
717
+ }
718
+
719
+ /* ── Init ── */
720
+ renderToolBars();
721
+ renderDayChart();
722
+ renderSavingsChart();
723
+ renderRecent(${recentJson});
724
+ setInterval(refreshData, 30000);
725
+ </script>
726
+ </body>
727
727
  </html>`;
728
728
  }
729
729
  //# sourceMappingURL=cli.js.map