token-counter-mcp 1.1.1 → 1.2.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.
@@ -1 +1 @@
1
- {"version":3,"file":"costs.d.ts","sourceRoot":"","sources":["../src/costs.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAgCtD,CAAC;AASF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAUtD;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,GACnB,SAAS,CAUX;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9C;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAI9C"}
1
+ {"version":3,"file":"costs.d.ts","sourceRoot":"","sources":["../src/costs.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,YAAY;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAgCtD,CAAC;AASF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,CAUtD;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,EACpB,eAAe,SAAI,EACnB,gBAAgB,SAAI,GACnB,SAAS,CAQX;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9C;AAED,wBAAgB,YAAY,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAI9C"}
package/dist/costs.js CHANGED
@@ -50,9 +50,7 @@ export function getPricing(model) {
50
50
  }
51
51
  export function calculateCost(model, inputTokens, outputTokens, cacheReadTokens = 0, cacheWriteTokens = 0) {
52
52
  const pricing = getPricing(model);
53
- // inputTokens may be total (raw+cache) subtract cache to get raw for cost calc
54
- const rawInput = Math.max(0, inputTokens - cacheReadTokens - cacheWriteTokens);
55
- const inputCost = (rawInput / 1_000_000) * pricing.inputPerMillion;
53
+ const inputCost = (inputTokens / 1_000_000) * pricing.inputPerMillion;
56
54
  const outputCost = (outputTokens / 1_000_000) * pricing.outputPerMillion;
57
55
  const cacheReadCost = (cacheReadTokens / 1_000_000) * pricing.cacheReadPerMillion;
58
56
  const cacheWriteCost = (cacheWriteTokens / 1_000_000) * pricing.cacheWritePerMillion;
package/dist/costs.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"costs.js","sourceRoot":"","sources":["../src/costs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"costs.js","sourceRoot":"","sources":["../src/costs.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- export declare const DASHBOARD_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Token Counter \u2014 Dashboard</title>\n <script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js\"></script>\n <style>\n :root {\n --bg: #06080e;\n --surface: rgba(255,255,255,0.03);\n --surface-hover: rgba(255,255,255,0.055);\n --surface-raised: rgba(255,255,255,0.045);\n --border: rgba(255,255,255,0.06);\n --border-bright: rgba(255,255,255,0.13);\n --text: #f1f5f9;\n --text-sec: #94a3b8;\n --text-dim: #475569;\n --purple: #a78bfa;\n --purple-dim: rgba(167,139,250,0.10);\n --blue: #38bdf8;\n --blue-dim: rgba(56,189,248,0.08);\n --pink: #f472b6;\n --pink-dim: rgba(244,114,182,0.10);\n --green: #34d399;\n --green-dim: rgba(52,211,153,0.10);\n --amber: #fbbf24;\n --amber-dim: rgba(251,191,36,0.10);\n --red: #f87171;\n --red-dim: rgba(248,113,113,0.10);\n --cyan: #22d3ee;\n --indigo: #818cf8;\n --radius: 14px;\n --radius-sm: 10px;\n }\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'SF Pro Display', 'Segoe UI', system-ui, sans-serif;\n background: var(--bg);\n color: var(--text);\n min-height: 100vh;\n -webkit-font-smoothing: antialiased;\n overflow-x: hidden;\n }\n body::before {\n content: '';\n position: fixed; inset: 0; z-index: 0; pointer-events: none;\n background:\n radial-gradient(ellipse 800px 600px at 10% 10%, rgba(167,139,250,0.06) 0%, transparent 60%),\n radial-gradient(ellipse 600px 800px at 90% 90%, rgba(56,189,248,0.04) 0%, transparent 60%),\n radial-gradient(ellipse 500px 400px at 75% 5%, rgba(244,114,182,0.035) 0%, transparent 50%),\n radial-gradient(ellipse 400px 400px at 40% 80%, rgba(52,211,153,0.025) 0%, transparent 50%);\n }\n .wrap { position: relative; z-index: 1; max-width: 1200px; margin: 0 auto; padding: 24px 20px 80px; }\n\n /* \u2500\u2500 Header \u2500\u2500 */\n .hdr { display: flex; align-items: center; justify-content: space-between; margin-bottom: 32px; gap: 12px; flex-wrap: wrap; }\n .hdr-left { display: flex; align-items: center; gap: 14px; }\n .logo {\n width: 42px; height: 42px; border-radius: 12px; flex-shrink: 0;\n background: linear-gradient(135deg, #a78bfa 0%, #38bdf8 50%, #f472b6 100%);\n display: flex; align-items: center; justify-content: center;\n font-size: 20px; box-shadow: 0 0 24px rgba(167,139,250,0.25), 0 0 48px rgba(56,189,248,0.1);\n }\n .hdr h1 { font-size: 1.1rem; font-weight: 700; letter-spacing: -0.03em; color: var(--text); }\n .hdr-sub { font-size: 0.72rem; color: var(--text-dim); margin-top: 2px; }\n .badge {\n display: flex; align-items: center; gap: 6px;\n border-radius: 100px; padding: 4px 12px;\n font-size: 0.66rem; font-weight: 700; letter-spacing: 0.07em;\n background: var(--green-dim); border: 1px solid rgba(52,211,153,0.2);\n color: var(--green); transition: all 0.3s;\n }\n .badge.off { background: var(--red-dim); border-color: rgba(248,113,113,0.2); color: var(--red); }\n .badge-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; animation: pulse 2s ease-in-out infinite; }\n @keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.4;transform:scale(.7)} }\n .hdr-right { text-align: right; }\n .hdr-time { font-size: 0.72rem; color: var(--text-dim); line-height: 1.5; }\n\n /* \u2500\u2500 Section titles \u2500\u2500 */\n .sec-title {\n font-size: 0.62rem; font-weight: 700; letter-spacing: 0.12em;\n text-transform: uppercase; color: var(--text-dim); margin-bottom: 12px;\n display: flex; align-items: center; gap: 8px;\n }\n .sec-title::after { content: ''; flex: 1; height: 1px; background: var(--border); }\n\n /* \u2500\u2500 Stat cards \u2500\u2500 */\n .stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(155px, 1fr)); gap: 10px; margin-bottom: 32px; }\n .stat-card {\n background: var(--surface); border: 1px solid var(--border);\n border-radius: var(--radius); padding: 18px 16px;\n transition: border-color 0.2s, background 0.2s, transform 0.15s;\n position: relative; overflow: hidden;\n }\n .stat-card:hover { border-color: var(--border-bright); background: var(--surface-hover); transform: translateY(-1px); }\n .stat-card::before {\n content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;\n background: linear-gradient(90deg, transparent, var(--purple), transparent);\n opacity: 0; transition: opacity 0.3s;\n }\n .stat-card:hover::before { opacity: 1; }\n .stat-lbl { font-size: 0.62rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.09em; color: var(--text-dim); margin-bottom: 10px; }\n .stat-val { font-size: 1.65rem; font-weight: 700; letter-spacing: -0.04em; line-height: 1; color: var(--text); }\n .stat-val.grad {\n background: linear-gradient(130deg, #a78bfa 0%, #f472b6 50%, #38bdf8 100%);\n -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;\n }\n .stat-note { font-size: 0.58rem; color: var(--text-dim); margin-top: 6px; }\n\n /* \u2500\u2500 Charts \u2500\u2500 */\n .charts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 32px; }\n @media (max-width: 768px) { .charts-grid { grid-template-columns: 1fr; } }\n .chart-card {\n background: var(--surface); border: 1px solid var(--border);\n border-radius: var(--radius); padding: 20px;\n transition: border-color 0.2s;\n }\n .chart-card:hover { border-color: var(--border-bright); }\n .chart-title { font-size: 0.74rem; font-weight: 600; color: var(--text-sec); margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }\n .chart-title-icon { font-size: 0.85rem; opacity: 0.6; }\n .chart-wrap { position: relative; height: 220px; }\n .chart-wrap canvas { width: 100% !important; height: 100% !important; }\n .chart-empty { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-dim); font-size: 0.78rem; }\n .no-charts-msg { text-align: center; padding: 40px 20px; color: var(--text-dim); font-size: 0.82rem; border: 1px dashed var(--border); border-radius: var(--radius); margin-bottom: 32px; }\n\n /* \u2500\u2500 Projects \u2500\u2500 */\n .projects-section { margin-bottom: 32px; }\n .proj-card {\n background: var(--surface); border: 1px solid var(--border);\n border-radius: var(--radius); margin-bottom: 8px; overflow: hidden;\n transition: border-color 0.2s, box-shadow 0.3s;\n }\n .proj-card:hover { border-color: var(--border-bright); box-shadow: 0 4px 24px rgba(0,0,0,0.15); }\n .proj-hdr {\n display: flex; align-items: center; padding: 14px 18px;\n cursor: pointer; gap: 14px; user-select: none;\n transition: background 0.15s;\n }\n .proj-hdr:hover { background: rgba(255,255,255,0.015); }\n .proj-icon {\n width: 38px; height: 38px; border-radius: 10px; flex-shrink: 0;\n display: flex; align-items: center; justify-content: center; font-size: 16px;\n border: 1px solid rgba(167,139,250,0.12);\n }\n .proj-icon.tier-high { background: linear-gradient(135deg, rgba(167,139,250,0.2), rgba(244,114,182,0.15)); }\n .proj-icon.tier-mid { background: linear-gradient(135deg, rgba(56,189,248,0.15), rgba(52,211,153,0.1)); }\n .proj-icon.tier-low { background: linear-gradient(135deg, rgba(255,255,255,0.06), rgba(255,255,255,0.03)); }\n .proj-info { flex: 1; min-width: 0; }\n .proj-name { font-size: 0.88rem; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n .proj-path { font-size: 0.64rem; color: var(--text-dim); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: 'SF Mono', 'Fira Code', monospace; }\n .proj-metrics { display: flex; align-items: center; gap: 24px; flex-shrink: 0; }\n .proj-metric { text-align: right; }\n .proj-metric-val { font-size: 0.88rem; font-weight: 700; }\n .proj-metric-val.cost { color: var(--purple); }\n .proj-metric-val.tokens { color: var(--blue); }\n .proj-metric-lbl { font-size: 0.58rem; color: var(--text-dim); margin-top: 2px; letter-spacing: 0.03em; }\n .chevron {\n width: 14px; height: 14px; flex-shrink: 0; color: var(--text-dim);\n transition: transform 0.25s ease;\n }\n .proj-card.open .chevron { transform: rotate(90deg); }\n .proj-bar-wrap { height: 3px; background: rgba(255,255,255,0.04); margin: 0 18px; border-radius: 3px; overflow: hidden; }\n .proj-bar { height: 100%; border-radius: 3px; transition: width 0.6s ease; }\n .proj-bar.tier-high { background: linear-gradient(90deg, #a78bfa, #f472b6); }\n .proj-bar.tier-mid { background: linear-gradient(90deg, #38bdf8, #34d399); }\n .proj-bar.tier-low { background: linear-gradient(90deg, rgba(148,163,184,0.4), rgba(148,163,184,0.2)); }\n\n /* Project detail panel */\n .proj-detail { display: none; border-top: 1px solid var(--border); }\n .proj-card.open .proj-detail { display: block; }\n .proj-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--border); }\n .proj-detail-grid > div { background: var(--bg); padding: 14px 18px; }\n .proj-detail-label { font-size: 0.6rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim); margin-bottom: 4px; }\n .proj-detail-value { font-size: 0.92rem; font-weight: 600; color: var(--text-sec); }\n .proj-sessions-title { font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-dim); padding: 12px 18px 6px; }\n .sess-row {\n display: flex; align-items: center; gap: 12px;\n padding: 10px 18px 10px 18px;\n border-bottom: 1px solid rgba(255,255,255,0.03);\n transition: background 0.15s;\n }\n .sess-row:last-child { border-bottom: none; }\n .sess-row:hover { background: rgba(255,255,255,0.015); }\n .sess-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; background: var(--text-dim); }\n .sess-dot.active { background: var(--green); box-shadow: 0 0 8px rgba(52,211,153,0.5); }\n .sess-time { font-size: 0.76rem; color: var(--text-sec); flex: 1; }\n .sess-tokens { font-size: 0.68rem; color: var(--text-dim); }\n .sess-calls { font-size: 0.68rem; color: var(--text-dim); min-width: 50px; text-align: right; }\n .sess-cost { font-size: 0.82rem; font-weight: 600; color: var(--purple); min-width: 72px; text-align: right; }\n\n /* \u2500\u2500 Table \u2500\u2500 */\n .tbl-section { margin-bottom: 32px; }\n .tbl-wrap { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }\n table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }\n thead th {\n padding: 11px 14px; text-align: left; white-space: nowrap;\n font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em;\n color: var(--text-dim); background: rgba(0,0,0,0.2); border-bottom: 1px solid var(--border);\n }\n tbody td { padding: 10px 14px; border-bottom: 1px solid rgba(255,255,255,0.03); white-space: nowrap; vertical-align: middle; }\n tbody tr:last-child td { border-bottom: none; }\n tbody tr { transition: background 0.12s; }\n tbody tr:hover td { background: rgba(255,255,255,0.018); }\n .model-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n font-size: 0.66rem; font-weight: 600;\n }\n .model-tag.opus { background: var(--purple-dim); color: var(--purple); border: 1px solid rgba(167,139,250,0.12); }\n .model-tag.sonnet { background: var(--blue-dim); color: var(--blue); border: 1px solid rgba(56,189,248,0.12); }\n .model-tag.haiku { background: var(--green-dim); color: var(--green); border: 1px solid rgba(52,211,153,0.12); }\n .model-tag.other { background: rgba(255,255,255,0.04); color: var(--text-sec); border: 1px solid var(--border); }\n .cost-cell { color: var(--purple); font-weight: 600; }\n .dim { color: var(--text-dim); }\n .sec { color: var(--text-sec); }\n @keyframes flash-in { 0% { background: rgba(167,139,250,0.1); } 100% { background: transparent; } }\n .flash td { animation: flash-in 1.8s ease forwards; }\n .empty-cell { text-align: center; padding: 48px 24px; }\n .empty-icon { font-size: 1.8rem; opacity: 0.25; margin-bottom: 10px; }\n .empty-text { font-size: 0.8rem; color: var(--text-dim); }\n .no-proj { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 32px; text-align: center; color: var(--text-dim); font-size: 0.82rem; line-height: 1.7; }\n .no-proj code { background: rgba(255,255,255,0.06); border-radius: 4px; padding: 1px 6px; font-family: 'SF Mono', monospace; font-size: 0.78rem; color: var(--text-sec); }\n\n /* \u2500\u2500 Pagination \u2500\u2500 */\n .pager {\n display: flex; align-items: center; justify-content: space-between;\n padding: 12px 16px; border-top: 1px solid var(--border);\n background: rgba(0,0,0,0.12);\n }\n .pager-info { font-size: 0.7rem; color: var(--text-dim); }\n .pager-btns { display: flex; align-items: center; gap: 4px; }\n .pager-btn {\n width: 32px; height: 32px; border-radius: 8px; border: 1px solid var(--border);\n background: var(--surface); color: var(--text-sec);\n font-size: 0.72rem; font-weight: 600; cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n transition: all 0.15s;\n }\n .pager-btn:hover:not(:disabled) { border-color: var(--border-bright); background: var(--surface-hover); color: var(--text); }\n .pager-btn:disabled { opacity: 0.3; cursor: default; }\n .pager-btn.active { background: var(--purple-dim); border-color: rgba(167,139,250,0.3); color: var(--purple); }\n .pager-btn.nav { font-size: 0.82rem; width: 36px; }\n .pager-ellipsis { width: 24px; text-align: center; color: var(--text-dim); font-size: 0.7rem; }\n\n /* \u2500\u2500 Footer \u2500\u2500 */\n .footer { text-align: center; padding: 24px; font-size: 0.65rem; color: var(--text-dim); }\n .footer a { color: var(--purple); text-decoration: none; }\n\n /* \u2500\u2500 Scrollbar \u2500\u2500 */\n ::-webkit-scrollbar { width: 5px; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 3px; }\n ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.14); }\n\n /* \u2500\u2500 Responsive \u2500\u2500 */\n @media (max-width: 640px) {\n .stats-grid { grid-template-columns: repeat(2, 1fr); }\n .proj-metrics { gap: 14px; }\n .proj-metric:nth-child(n+3) { display: none; }\n thead th:nth-child(4), tbody td:nth-child(4),\n thead th:nth-child(7), tbody td:nth-child(7) { display: none; }\n }\n </style>\n</head>\n<body>\n<div class=\"wrap\">\n\n <!-- Header -->\n <div class=\"hdr\">\n <div class=\"hdr-left\">\n <div class=\"logo\">&#x2B21;</div>\n <div>\n <h1>Token Counter</h1>\n <div class=\"hdr-sub\" id=\"session-line\">Connecting&hellip;</div>\n </div>\n <div class=\"badge\" id=\"badge\"><div class=\"badge-dot\"></div><span id=\"badge-txt\">LIVE</span></div>\n </div>\n <div class=\"hdr-right\">\n <div class=\"hdr-time\" id=\"hdr-time\"></div>\n </div>\n </div>\n\n <!-- Stats -->\n <div class=\"sec-title\" id=\"sess-section-label\">Current Session</div>\n <div class=\"stats-grid\">\n <div class=\"stat-card\"><div class=\"stat-lbl\">Total Cost</div><div class=\"stat-val grad\" id=\"c-cost\">&mdash;</div><div class=\"stat-note\" id=\"c-cost-note\"></div></div>\n <div class=\"stat-card\"><div class=\"stat-lbl\">Input Tokens</div><div class=\"stat-val\" id=\"c-in\">&mdash;</div></div>\n <div class=\"stat-card\"><div class=\"stat-lbl\">Output Tokens</div><div class=\"stat-val\" id=\"c-out\">&mdash;</div></div>\n <div class=\"stat-card\"><div class=\"stat-lbl\">Cache Read</div><div class=\"stat-val\" id=\"c-cr\">&mdash;</div></div>\n <div class=\"stat-card\"><div class=\"stat-lbl\">Cache Write</div><div class=\"stat-val\" id=\"c-cw\">&mdash;</div></div>\n <div class=\"stat-card\"><div class=\"stat-lbl\">API Calls</div><div class=\"stat-val\" id=\"c-n\">&mdash;</div><div class=\"stat-note\" id=\"c-proj-count\"></div></div>\n </div>\n\n <!-- Charts -->\n <div class=\"sec-title\">Analytics</div>\n <div id=\"charts-section\">\n <div class=\"charts-grid\">\n <div class=\"chart-card\">\n <div class=\"chart-title\"><span class=\"chart-title-icon\">&#x1F4C8;</span> Spending Trend</div>\n <div class=\"chart-wrap\"><canvas id=\"chart-trend\"></canvas></div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\"><span class=\"chart-title-icon\">&#x1F4C1;</span> Cost by Project</div>\n <div class=\"chart-wrap\"><canvas id=\"chart-proj-cost\"></canvas></div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\"><span class=\"chart-title-icon\">&#x1F4CA;</span> Token Breakdown</div>\n <div class=\"chart-wrap\"><canvas id=\"chart-tokens\"></canvas></div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\"><span class=\"chart-title-icon\">&#x2699;</span> Model Distribution</div>\n <div class=\"chart-wrap\"><canvas id=\"chart-models\"></canvas></div>\n </div>\n </div>\n </div>\n\n <!-- Projects -->\n <div class=\"projects-section\">\n <div class=\"sec-title\">Projects</div>\n <div id=\"proj-list\"><div class=\"no-proj\">Loading&hellip;</div></div>\n </div>\n\n <!-- Recent calls -->\n <div class=\"tbl-section\">\n <div class=\"sec-title\">Recent Calls</div>\n <div class=\"tbl-wrap\">\n <table>\n <thead>\n <tr><th>Time</th><th>Model</th><th>Project</th><th>Description</th><th>Input</th><th>Output</th><th>Cache R/W</th><th>Cost</th></tr>\n </thead>\n <tbody id=\"tbody\">\n <tr><td colspan=\"8\"><div class=\"empty-cell\"><div class=\"empty-icon\">&#x25CE;</div><div class=\"empty-text\">Waiting for first log_usage call&hellip;</div></div></td></tr>\n </tbody>\n </table>\n <div class=\"pager\" id=\"pager\" style=\"display:none\">\n <div class=\"pager-info\" id=\"pager-info\"></div>\n <div class=\"pager-btns\" id=\"pager-btns\"></div>\n </div>\n </div>\n </div>\n\n <div class=\"footer\">Token Counter MCP &middot; Real-time usage tracking</div>\n</div>\n\n<script>\n(function() {\n 'use strict';\n\n // \u2500\u2500 Chart.js check \u2500\u2500\n var hasChartJs = typeof Chart !== 'undefined';\n if (!hasChartJs) {\n document.getElementById('charts-section').innerHTML = '<div class=\"no-charts-msg\">Charts require internet connection (Chart.js CDN). Live stats are still updating.</div>';\n }\n\n // \u2500\u2500 Chart.js global defaults \u2500\u2500\n if (hasChartJs) {\n Chart.defaults.color = '#64748b';\n Chart.defaults.borderColor = 'rgba(255,255,255,0.04)';\n Chart.defaults.font.family = \"-apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif\";\n Chart.defaults.font.size = 11;\n Chart.defaults.plugins.legend.labels.padding = 12;\n Chart.defaults.plugins.legend.labels.usePointStyle = true;\n Chart.defaults.plugins.legend.labels.pointStyleWidth = 8;\n }\n\n var COLORS = ['#a78bfa','#38bdf8','#f472b6','#34d399','#fbbf24','#f87171','#818cf8','#22d3ee','#fb923c','#a3e635'];\n var COLORS_DIM = COLORS.map(function(c) { return c + '18'; });\n\n // \u2500\u2500 Utility functions \u2500\u2500\n function fmt(n) { n = n || 0; if (n >= 1e6) return (n/1e6).toFixed(1)+'M'; if (n >= 1e3) return (n/1e3).toFixed(1)+'K'; return String(n); }\n function fmtCost(u) { if (!u || u < 0.0001) return '~\\$0.00'; var s = parseFloat(u.toPrecision(2)); if (s < 0.01) return '~\\$'+s.toFixed(4); if (s < 1) return '~\\$'+s.toFixed(3); return '~\\$'+s.toFixed(2); }\n function fmtCostShort(u) { if (!u || u < 0.001) return '\\$0'; if (u < 1) return '\\$'+u.toFixed(2); return '\\$'+u.toFixed(1); }\n function hhmm(ts) { return new Date(ts).toLocaleTimeString([], {hour:'2-digit', minute:'2-digit', second:'2-digit'}); }\n function dtFmt(ts) { var d = new Date(ts); var today = new Date(); var isToday = d.toDateString() === today.toDateString(); var t = d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); return isToday ? 'Today '+t : d.toLocaleDateString([], {month:'short', day:'numeric'})+' '+t; }\n function mdl(m) { return m ? m.replace('claude-','').replace(/-\\d{8}$/, '') : '\\u2014'; }\n function mdlClass(m) { if (!m) return 'other'; if (m.indexOf('opus') !== -1) return 'opus'; if (m.indexOf('sonnet') !== -1) return 'sonnet'; if (m.indexOf('haiku') !== -1) return 'haiku'; return 'other'; }\n function projBase(p) { if (!p || p === '(no project)') return '\\u2014'; var parts = p.split('/').filter(Boolean); return parts[parts.length - 1] || p; }\n function dateFmt(iso) { var d = new Date(iso); return d.toLocaleDateString([], {month:'short', day:'numeric'}); }\n\n // \u2500\u2500 State \u2500\u2500\n var token = new URLSearchParams(location.search).get('token') || '';\n var isRemote = !!token;\n var LS_KEY = 'tc_v2_' + token;\n var localEntries = [];\n var allHistory = [];\n var openProjects = {};\n\n var badge = document.getElementById('badge');\n var badgeTxt = document.getElementById('badge-txt');\n var sessionLine = document.getElementById('session-line');\n var hdrTime = document.getElementById('hdr-time');\n\n // Chart instances\n var chartTrend = null;\n var chartProjCost = null;\n var chartTokens = null;\n var chartModels = null;\n\n if (isRemote) {\n document.getElementById('sess-section-label').textContent = 'All Time Stats';\n try { localEntries = JSON.parse(localStorage.getItem(LS_KEY) || '[]'); } catch(e) {}\n if (localEntries.length) {\n var st = buildState(localEntries);\n renderState(st, false);\n updateCharts(localEntries, st.grouped);\n }\n }\n\n // \u2500\u2500 SSE \u2500\u2500\n var sseUrl = isRemote ? '/events?token=' + encodeURIComponent(token) : '/events';\n var es = new EventSource(sseUrl);\n es.onerror = function() { badge.className = 'badge off'; badgeTxt.textContent = 'OFFLINE'; };\n es.onopen = function() { badge.className = 'badge'; badgeTxt.textContent = 'LIVE'; };\n\n es.onmessage = function(ev) {\n var d = JSON.parse(ev.data);\n if (isRemote) {\n var map = {};\n localEntries.forEach(function(e) { map[e.id] = e; });\n (d.entries || []).forEach(function(e) { map[e.id] = e; });\n localEntries = Object.values(map).sort(function(a, b) { return a.timestamp < b.timestamp ? -1 : 1; });\n if (localEntries.length > 1000) localEntries = localEntries.slice(-1000);\n try { localStorage.setItem(LS_KEY, JSON.stringify(localEntries)); } catch(e) {}\n var st = buildState(localEntries);\n renderState(st, true);\n updateCharts(localEntries, st.grouped);\n } else {\n renderLocalState(d);\n var entries = (d.history || d.session.entries || []);\n updateCharts(entries, d.grouped || []);\n }\n };\n\n // \u2500\u2500 Build state (remote mode) \u2500\u2500\n function buildState(entries) {\n var totals = {inputTokens:0, outputTokens:0, cacheReadTokens:0, cacheWriteTokens:0, totalCost:0};\n entries.forEach(function(e) {\n totals.inputTokens += e.inputTokens || 0;\n totals.outputTokens += e.outputTokens || 0;\n totals.cacheReadTokens += e.cacheReadTokens || 0;\n totals.cacheWriteTokens += e.cacheWriteTokens || 0;\n totals.totalCost += e.totalCost || 0;\n });\n var pm = {};\n entries.forEach(function(e) {\n var proj = e.project || '(no project)';\n if (!pm[proj]) pm[proj] = {};\n var sid = e.sessionId || 'default';\n if (!pm[proj][sid]) pm[proj][sid] = { entries: [], startedAt: e.timestamp };\n pm[proj][sid].entries.push(e);\n });\n var grouped = Object.keys(pm).map(function(project) {\n var sm = pm[project];\n var allE = [];\n var sessions = Object.keys(sm).map(function(sid) {\n var s = sm[sid];\n allE = allE.concat(s.entries);\n return {\n sessionId: sid, startedAt: s.startedAt,\n totalInputTokens: s.entries.reduce(function(a, e) { return a + (e.inputTokens||0); }, 0),\n totalOutputTokens: s.entries.reduce(function(a, e) { return a + (e.outputTokens||0); }, 0),\n totalCost: s.entries.reduce(function(a, e) { return a + (e.totalCost||0); }, 0),\n entryCount: s.entries.length,\n };\n }).sort(function(a, b) { return b.startedAt.localeCompare(a.startedAt); });\n var parts = project.split('/').filter(Boolean);\n return {\n project: project, sessions: sessions,\n displayName: project === '(no project)' ? '(no project)' : parts[parts.length-1] || project,\n totalCost: allE.reduce(function(a, e) { return a + (e.totalCost||0); }, 0),\n totalInputTokens: allE.reduce(function(a, e) { return a + (e.inputTokens||0); }, 0),\n totalOutputTokens: allE.reduce(function(a, e) { return a + (e.outputTokens||0); }, 0),\n lastActiveAt: sessions[0] ? sessions[0].startedAt : '',\n };\n }).sort(function(a, b) { return b.totalCost - a.totalCost; });\n return { totals: totals, grouped: grouped, entries: entries };\n }\n\n // \u2500\u2500 Render: remote mode \u2500\u2500\n function renderState(st, isLive) {\n var t = st.totals;\n var n = st.entries.length;\n sessionLine.textContent = n + ' call' + (n===1?'':'s') + ' \\u00B7 ' + st.grouped.length + ' project' + (st.grouped.length===1?'':'s') + (isLive ? '' : ' \\u00B7 cached');\n hdrTime.innerHTML = 'Updated ' + new Date().toLocaleTimeString();\n updateStatCards(t, n, st.grouped.length);\n renderProjects(st.grouped, null);\n renderTable(st.entries.slice().reverse());\n }\n\n // \u2500\u2500 Render: local mode \u2500\u2500\n function renderLocalState(d) {\n var sess = d.session, t = sess.totals, entries = sess.entries || [], grouped = d.grouped || [];\n var started = new Date(sess.startedAt);\n sessionLine.textContent = 'Session since ' + started.toLocaleTimeString() + ' \\u00B7 ' + entries.length + ' call' + (entries.length===1?'':'s');\n hdrTime.innerHTML = 'Updated ' + new Date().toLocaleTimeString() + '<br><span style=\"color:var(--text-dim);font-size:.64rem\">' + started.toLocaleDateString([], {weekday:'short', month:'short', day:'numeric'}) + '</span>';\n updateStatCards(t, entries.length, grouped.length);\n renderProjects(grouped, sess.sessionId);\n renderTable(entries.slice().reverse());\n }\n\n function updateStatCards(t, callCount, projCount) {\n document.getElementById('c-cost').textContent = fmtCost(t.totalCost);\n document.getElementById('c-in').textContent = fmt(t.inputTokens);\n document.getElementById('c-out').textContent = fmt(t.outputTokens);\n document.getElementById('c-cr').textContent = fmt(t.cacheReadTokens);\n document.getElementById('c-cw').textContent = fmt(t.cacheWriteTokens);\n document.getElementById('c-n').textContent = callCount;\n document.getElementById('c-proj-count').textContent = projCount + ' project' + (projCount === 1 ? '' : 's');\n }\n\n // \u2500\u2500 Chart data aggregators \u2500\u2500\n function aggregateByDate(entries) {\n var byDate = {};\n entries.forEach(function(e) {\n var date = e.timestamp ? e.timestamp.slice(0, 10) : '';\n if (!date) return;\n if (!byDate[date]) byDate[date] = 0;\n byDate[date] += e.totalCost || 0;\n });\n var result = [];\n var today = new Date();\n for (var i = 29; i >= 0; i--) {\n var d = new Date(today);\n d.setDate(d.getDate() - i);\n var key = d.toISOString().slice(0, 10);\n result.push({ date: key, label: dateFmt(key), cost: byDate[key] || 0 });\n }\n return result;\n }\n\n function aggregateByModel(entries) {\n var byModel = {};\n entries.forEach(function(e) {\n var m = mdl(e.model || 'unknown');\n if (!byModel[m]) byModel[m] = { count: 0, cost: 0, tokens: 0 };\n byModel[m].count++;\n byModel[m].cost += e.totalCost || 0;\n byModel[m].tokens += (e.inputTokens || 0) + (e.outputTokens || 0);\n });\n return Object.keys(byModel).map(function(m) {\n return { model: m, count: byModel[m].count, cost: byModel[m].cost, tokens: byModel[m].tokens };\n }).sort(function(a, b) { return b.cost - a.cost; });\n }\n\n function aggregateTokensByProject(grouped) {\n return grouped.slice(0, 8).map(function(g) {\n return {\n name: g.displayName,\n input: g.totalInputTokens || 0,\n output: g.totalOutputTokens || 0,\n };\n });\n }\n\n // \u2500\u2500 Chart rendering \u2500\u2500\n function updateCharts(entries, grouped) {\n if (!hasChartJs || !entries.length) return;\n\n // 1. Spending Trend (area line chart)\n var trendData = aggregateByDate(entries);\n var trendLabels = trendData.map(function(d) { return d.label; });\n var trendValues = trendData.map(function(d) { return d.cost; });\n\n if (!chartTrend) {\n var ctx1 = document.getElementById('chart-trend').getContext('2d');\n var gradient1 = ctx1.createLinearGradient(0, 0, 0, 220);\n gradient1.addColorStop(0, 'rgba(167,139,250,0.25)');\n gradient1.addColorStop(1, 'rgba(167,139,250,0.0)');\n chartTrend = new Chart(ctx1, {\n type: 'line',\n data: {\n labels: trendLabels,\n datasets: [{\n label: 'Daily Cost',\n data: trendValues,\n borderColor: '#a78bfa',\n backgroundColor: gradient1,\n fill: true,\n tension: 0.4,\n borderWidth: 2,\n pointRadius: 0,\n pointHoverRadius: 5,\n pointHoverBackgroundColor: '#a78bfa',\n pointHoverBorderColor: '#fff',\n pointHoverBorderWidth: 2,\n }]\n },\n options: {\n responsive: true, maintainAspectRatio: false,\n interaction: { mode: 'index', intersect: false },\n plugins: {\n legend: { display: false },\n tooltip: {\n backgroundColor: 'rgba(15,17,25,0.95)',\n borderColor: 'rgba(167,139,250,0.3)',\n borderWidth: 1,\n titleColor: '#f1f5f9',\n bodyColor: '#94a3b8',\n padding: 10,\n callbacks: {\n label: function(ctx) { return '\\$' + ctx.parsed.y.toFixed(4); }\n }\n }\n },\n scales: {\n x: {\n grid: { display: false },\n ticks: { maxTicksLimit: 7, font: { size: 10 } }\n },\n y: {\n grid: { color: 'rgba(255,255,255,0.03)' },\n ticks: { font: { size: 10 }, callback: function(v) { return '\\$' + v.toFixed(2); } }\n }\n }\n }\n });\n } else {\n chartTrend.data.labels = trendLabels;\n chartTrend.data.datasets[0].data = trendValues;\n chartTrend.update('none');\n }\n\n // 2. Cost by Project (doughnut)\n var projData = grouped.slice(0, 8).filter(function(g) { return g.totalCost > 0; });\n var projLabels = projData.map(function(g) { return g.displayName; });\n var projValues = projData.map(function(g) { return g.totalCost; });\n\n if (!chartProjCost) {\n var ctx2 = document.getElementById('chart-proj-cost').getContext('2d');\n chartProjCost = new Chart(ctx2, {\n type: 'doughnut',\n data: {\n labels: projLabels,\n datasets: [{\n data: projValues,\n backgroundColor: COLORS.slice(0, projValues.length),\n borderColor: 'rgba(6,8,14,0.8)',\n borderWidth: 2,\n hoverBorderColor: '#fff',\n hoverBorderWidth: 2,\n }]\n },\n options: {\n responsive: true, maintainAspectRatio: false,\n cutout: '62%',\n plugins: {\n legend: {\n position: 'right',\n labels: { font: { size: 10 }, padding: 8, boxWidth: 10 }\n },\n tooltip: {\n backgroundColor: 'rgba(15,17,25,0.95)',\n borderColor: 'rgba(167,139,250,0.3)',\n borderWidth: 1,\n padding: 10,\n callbacks: {\n label: function(ctx) { return ' ' + ctx.label + ': \\$' + ctx.parsed.toFixed(4); }\n }\n }\n }\n }\n });\n } else {\n chartProjCost.data.labels = projLabels;\n chartProjCost.data.datasets[0].data = projValues;\n chartProjCost.data.datasets[0].backgroundColor = COLORS.slice(0, projValues.length);\n chartProjCost.update('none');\n }\n\n // 3. Token Breakdown by Project (stacked horizontal bar)\n var tokenData = aggregateTokensByProject(grouped);\n var tokenLabels = tokenData.map(function(d) { return d.name; });\n if (!tokenLabels.length) tokenLabels = ['No data'];\n\n if (!chartTokens) {\n var ctx3 = document.getElementById('chart-tokens').getContext('2d');\n chartTokens = new Chart(ctx3, {\n type: 'bar',\n data: {\n labels: tokenLabels,\n datasets: [\n { label: 'Input', data: tokenData.map(function(d) { return d.input; }), backgroundColor: '#a78bfa', borderRadius: 3 },\n { label: 'Output', data: tokenData.map(function(d) { return d.output; }), backgroundColor: '#38bdf8', borderRadius: 3 },\n ]\n },\n options: {\n responsive: true, maintainAspectRatio: false,\n indexAxis: 'y',\n plugins: {\n legend: { position: 'top', labels: { font: { size: 10 }, padding: 8, boxWidth: 10 } },\n tooltip: {\n backgroundColor: 'rgba(15,17,25,0.95)',\n borderColor: 'rgba(167,139,250,0.3)',\n borderWidth: 1,\n padding: 10,\n callbacks: {\n label: function(ctx) { return ' ' + ctx.dataset.label + ': ' + fmt(ctx.parsed.x); }\n }\n }\n },\n scales: {\n x: {\n stacked: true,\n grid: { color: 'rgba(255,255,255,0.03)' },\n ticks: { font: { size: 10 }, callback: function(v) { return fmt(v); } }\n },\n y: {\n stacked: true,\n grid: { display: false },\n ticks: { font: { size: 10 } }\n }\n }\n }\n });\n } else {\n chartTokens.data.labels = tokenLabels;\n chartTokens.data.datasets[0].data = tokenData.map(function(d) { return d.input; });\n chartTokens.data.datasets[1].data = tokenData.map(function(d) { return d.output; });\n chartTokens.update('none');\n }\n\n // 4. Model Distribution (doughnut)\n var modelData = aggregateByModel(entries);\n var modelLabels = modelData.map(function(d) { return d.model; });\n var modelValues = modelData.map(function(d) { return d.cost; });\n\n if (!chartModels) {\n var ctx4 = document.getElementById('chart-models').getContext('2d');\n chartModels = new Chart(ctx4, {\n type: 'doughnut',\n data: {\n labels: modelLabels,\n datasets: [{\n data: modelValues,\n backgroundColor: ['#a78bfa','#38bdf8','#34d399','#fbbf24','#f472b6','#f87171'],\n borderColor: 'rgba(6,8,14,0.8)',\n borderWidth: 2,\n hoverBorderColor: '#fff',\n hoverBorderWidth: 2,\n }]\n },\n options: {\n responsive: true, maintainAspectRatio: false,\n cutout: '62%',\n plugins: {\n legend: {\n position: 'right',\n labels: { font: { size: 10 }, padding: 8, boxWidth: 10 }\n },\n tooltip: {\n backgroundColor: 'rgba(15,17,25,0.95)',\n borderColor: 'rgba(167,139,250,0.3)',\n borderWidth: 1,\n padding: 10,\n callbacks: {\n label: function(ctx) { return ' ' + ctx.label + ': \\$' + ctx.parsed.toFixed(4) + ' (' + modelData[ctx.dataIndex].count + ' calls)'; }\n }\n }\n }\n }\n });\n } else {\n chartModels.data.labels = modelLabels;\n chartModels.data.datasets[0].data = modelValues;\n chartModels.update('none');\n }\n }\n\n // \u2500\u2500 Render projects \u2500\u2500\n function renderProjects(grouped, currentSessId) {\n var pl = document.getElementById('proj-list');\n if (!grouped || !grouped.length) {\n pl.innerHTML = '<div class=\"no-proj\">No project data yet.<br>Pass a <code>project</code> param to <code>log_usage</code> to track by folder.</div>';\n return;\n }\n var maxCost = Math.max.apply(null, grouped.map(function(g) { return g.totalCost; }).concat([0.001]));\n var totalCost = grouped.reduce(function(a, g) { return a + g.totalCost; }, 0);\n\n pl.innerHTML = grouped.map(function(g) {\n var isOpen = openProjects[g.project];\n var barPct = Math.max(4, Math.round(g.totalCost / maxCost * 100));\n var costPct = totalCost > 0 ? ((g.totalCost / totalCost) * 100).toFixed(1) : '0';\n var tier = g.totalCost / maxCost > 0.5 ? 'high' : (g.totalCost / maxCost > 0.15 ? 'mid' : 'low');\n var totalTokens = (g.totalInputTokens || 0) + (g.totalOutputTokens || 0);\n\n var sessHtml = (g.sessions || []).map(function(s) {\n var isActive = currentSessId && s.sessionId === currentSessId;\n return '<div class=\"sess-row\">' +\n '<div class=\"sess-dot' + (isActive ? ' active' : '') + '\"></div>' +\n '<span class=\"sess-time\">' + dtFmt(s.startedAt) + '</span>' +\n '<span class=\"sess-tokens\">' + fmt((s.totalInputTokens||0)+(s.totalOutputTokens||0)) + ' tok</span>' +\n '<span class=\"sess-calls\">' + s.entryCount + ' call' + (s.entryCount===1?'':'s') + '</span>' +\n '<span class=\"sess-cost\">' + fmtCost(s.totalCost) + '</span>' +\n '</div>';\n }).join('');\n\n return '<div class=\"proj-card' + (isOpen ? ' open' : '') + '\" data-proj=\"' + g.project + '\">' +\n '<div class=\"proj-hdr\" onclick=\"window._toggleProj(this.parentElement)\">' +\n '<div class=\"proj-icon tier-' + tier + '\">&#x1F4C1;</div>' +\n '<div class=\"proj-info\">' +\n '<div class=\"proj-name\">' + g.displayName + '</div>' +\n '<div class=\"proj-path\">' + g.project + '</div>' +\n '</div>' +\n '<div class=\"proj-metrics\">' +\n '<div class=\"proj-metric\"><div class=\"proj-metric-val cost\">' + fmtCost(g.totalCost) + '</div><div class=\"proj-metric-lbl\">cost (' + costPct + '%)</div></div>' +\n '<div class=\"proj-metric\"><div class=\"proj-metric-val tokens\">' + fmt(totalTokens) + '</div><div class=\"proj-metric-lbl\">tokens</div></div>' +\n '<div class=\"proj-metric\"><div class=\"proj-metric-val\" style=\"color:var(--text-sec)\">' + (g.sessions||[]).length + '</div><div class=\"proj-metric-lbl\">sessions</div></div>' +\n '</div>' +\n '<svg class=\"chevron\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2.5\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5l7 7-7 7\"/></svg>' +\n '</div>' +\n '<div class=\"proj-bar-wrap\"><div class=\"proj-bar tier-' + tier + '\" style=\"width:' + barPct + '%\"></div></div>' +\n '<div class=\"proj-detail\">' +\n '<div class=\"proj-detail-grid\">' +\n '<div><div class=\"proj-detail-label\">Input Tokens</div><div class=\"proj-detail-value\">' + fmt(g.totalInputTokens) + '</div></div>' +\n '<div><div class=\"proj-detail-label\">Output Tokens</div><div class=\"proj-detail-value\">' + fmt(g.totalOutputTokens) + '</div></div>' +\n '<div><div class=\"proj-detail-label\">Total Cost</div><div class=\"proj-detail-value\" style=\"color:var(--purple)\">' + fmtCost(g.totalCost) + '</div></div>' +\n '<div><div class=\"proj-detail-label\">Last Active</div><div class=\"proj-detail-value\">' + (g.lastActiveAt ? dtFmt(g.lastActiveAt) : '\\u2014') + '</div></div>' +\n '</div>' +\n '<div class=\"proj-sessions-title\">Sessions</div>' +\n sessHtml +\n '</div>' +\n '</div>';\n }).join('');\n }\n\n // \u2500\u2500 Pagination state \u2500\u2500\n var PAGE_SIZE = 10;\n var currentPage = 1;\n var allRows = [];\n\n function renderTable(rows) {\n allRows = rows || [];\n // When new data arrives, stay on page 1 if new entries were added, otherwise keep current page\n var totalPages = Math.max(1, Math.ceil(allRows.length / PAGE_SIZE));\n if (currentPage > totalPages) currentPage = totalPages;\n renderTablePage();\n }\n\n function renderTablePage() {\n var tbody = document.getElementById('tbody');\n var pager = document.getElementById('pager');\n\n if (!allRows.length) {\n tbody.innerHTML = '<tr><td colspan=\"8\"><div class=\"empty-cell\"><div class=\"empty-icon\">&#x25CE;</div><div class=\"empty-text\">Waiting for first log_usage call&hellip;</div></div></td></tr>';\n pager.style.display = 'none';\n return;\n }\n\n var totalPages = Math.ceil(allRows.length / PAGE_SIZE);\n var start = (currentPage - 1) * PAGE_SIZE;\n var end = Math.min(start + PAGE_SIZE, allRows.length);\n var pageRows = allRows.slice(start, end);\n\n tbody.innerHTML = pageRows.map(function(en, i) {\n var mc = mdlClass(en.model);\n var isFirst = (currentPage === 1 && i === 0);\n return '<tr class=\"' + (isFirst ? 'flash' : '') + '\">' +\n '<td class=\"dim\">' + hhmm(en.timestamp) + '</td>' +\n '<td><span class=\"model-tag ' + mc + '\">' + mdl(en.model) + '</span></td>' +\n '<td class=\"dim\" title=\"' + (en.project || '') + '\">' + projBase(en.project) + '</td>' +\n '<td class=\"' + (en.description ? 'sec' : 'dim') + '\">' + (en.description || '\\u2014') + '</td>' +\n '<td class=\"sec\">' + fmt(en.inputTokens) + '</td>' +\n '<td class=\"sec\">' + fmt(en.outputTokens) + '</td>' +\n '<td class=\"dim\">' + fmt(en.cacheReadTokens) + '/' + fmt(en.cacheWriteTokens) + '</td>' +\n '<td class=\"cost-cell\">' + fmtCost(en.totalCost) + '</td>' +\n '</tr>';\n }).join('');\n\n // Pagination controls\n if (totalPages <= 1) {\n pager.style.display = 'flex';\n document.getElementById('pager-info').textContent = 'Showing ' + allRows.length + ' of ' + allRows.length + ' calls';\n document.getElementById('pager-btns').innerHTML = '';\n return;\n }\n\n pager.style.display = 'flex';\n document.getElementById('pager-info').textContent = 'Showing ' + (start+1) + '\\u2013' + end + ' of ' + allRows.length + ' calls';\n\n var btns = '';\n // Prev button\n btns += '<button class=\"pager-btn nav\" ' + (currentPage <= 1 ? 'disabled' : '') + ' onclick=\"window._goPage(' + (currentPage-1) + ')\" title=\"Previous\">&#x2039;</button>';\n\n // Page number buttons with smart ellipsis\n var pages = buildPageNumbers(currentPage, totalPages);\n for (var i = 0; i < pages.length; i++) {\n if (pages[i] === '...') {\n btns += '<span class=\"pager-ellipsis\">&#x2026;</span>';\n } else {\n var p = pages[i];\n btns += '<button class=\"pager-btn' + (p === currentPage ? ' active' : '') + '\" onclick=\"window._goPage(' + p + ')\">' + p + '</button>';\n }\n }\n\n // Next button\n btns += '<button class=\"pager-btn nav\" ' + (currentPage >= totalPages ? 'disabled' : '') + ' onclick=\"window._goPage(' + (currentPage+1) + ')\" title=\"Next\">&#x203A;</button>';\n\n document.getElementById('pager-btns').innerHTML = btns;\n }\n\n function buildPageNumbers(current, total) {\n if (total <= 7) {\n var arr = [];\n for (var i = 1; i <= total; i++) arr.push(i);\n return arr;\n }\n var pages = [];\n pages.push(1);\n if (current > 3) pages.push('...');\n var rangeStart = Math.max(2, current - 1);\n var rangeEnd = Math.min(total - 1, current + 1);\n for (var j = rangeStart; j <= rangeEnd; j++) pages.push(j);\n if (current < total - 2) pages.push('...');\n pages.push(total);\n return pages;\n }\n\n window._goPage = function(page) {\n var totalPages = Math.ceil(allRows.length / PAGE_SIZE);\n if (page < 1 || page > totalPages) return;\n currentPage = page;\n renderTablePage();\n // Scroll table into view\n document.querySelector('.tbl-section').scrollIntoView({ behavior: 'smooth', block: 'start' });\n };\n\n // \u2500\u2500 Project toggle \u2500\u2500\n window._toggleProj = function(card) {\n var proj = card.getAttribute('data-proj');\n if (card.classList.contains('open')) {\n card.classList.remove('open');\n delete openProjects[proj];\n } else {\n card.classList.add('open');\n openProjects[proj] = true;\n }\n };\n\n})();\n</script>\n</body>\n</html>";
1
+ export declare const DASHBOARD_HTML = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>Token Counter \u2014 Dashboard</title>\n <script src=\"https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js\"></script>\n <style>\n :root {\n --bg: #06080e;\n --surface: rgba(255,255,255,0.03);\n --surface-hover: rgba(255,255,255,0.055);\n --surface-raised: rgba(255,255,255,0.045);\n --border: rgba(255,255,255,0.06);\n --border-bright: rgba(255,255,255,0.13);\n --text: #f1f5f9;\n --text-sec: #94a3b8;\n --text-dim: #475569;\n --purple: #a78bfa;\n --purple-dim: rgba(167,139,250,0.10);\n --blue: #38bdf8;\n --blue-dim: rgba(56,189,248,0.08);\n --pink: #f472b6;\n --pink-dim: rgba(244,114,182,0.10);\n --green: #34d399;\n --green-dim: rgba(52,211,153,0.10);\n --amber: #fbbf24;\n --amber-dim: rgba(251,191,36,0.10);\n --red: #f87171;\n --red-dim: rgba(248,113,113,0.10);\n --cyan: #22d3ee;\n --indigo: #818cf8;\n --radius: 14px;\n --radius-sm: 10px;\n }\n *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n body {\n font-family: -apple-system, BlinkMacSystemFont, 'Inter', 'SF Pro Display', 'Segoe UI', system-ui, sans-serif;\n background: var(--bg);\n color: var(--text);\n min-height: 100vh;\n -webkit-font-smoothing: antialiased;\n overflow-x: hidden;\n }\n body::before {\n content: '';\n position: fixed; inset: 0; z-index: 0; pointer-events: none;\n background:\n radial-gradient(ellipse 800px 600px at 10% 10%, rgba(167,139,250,0.06) 0%, transparent 60%),\n radial-gradient(ellipse 600px 800px at 90% 90%, rgba(56,189,248,0.04) 0%, transparent 60%),\n radial-gradient(ellipse 500px 400px at 75% 5%, rgba(244,114,182,0.035) 0%, transparent 50%),\n radial-gradient(ellipse 400px 400px at 40% 80%, rgba(52,211,153,0.025) 0%, transparent 50%);\n }\n .wrap { position: relative; z-index: 1; max-width: 1200px; margin: 0 auto; padding: 24px 20px 80px; }\n\n /* \u2500\u2500 Header \u2500\u2500 */\n .hdr { display: flex; align-items: center; justify-content: space-between; margin-bottom: 32px; gap: 12px; flex-wrap: wrap; }\n .hdr-left { display: flex; align-items: center; gap: 14px; }\n .logo {\n width: 42px; height: 42px; border-radius: 12px; flex-shrink: 0;\n background: linear-gradient(135deg, #a78bfa 0%, #38bdf8 50%, #f472b6 100%);\n display: flex; align-items: center; justify-content: center;\n font-size: 20px; box-shadow: 0 0 24px rgba(167,139,250,0.25), 0 0 48px rgba(56,189,248,0.1);\n }\n .hdr h1 { font-size: 1.1rem; font-weight: 700; letter-spacing: -0.03em; color: var(--text); }\n .hdr-sub { font-size: 0.72rem; color: var(--text-dim); margin-top: 2px; }\n .badge {\n display: flex; align-items: center; gap: 6px;\n border-radius: 100px; padding: 4px 12px;\n font-size: 0.66rem; font-weight: 700; letter-spacing: 0.07em;\n background: var(--green-dim); border: 1px solid rgba(52,211,153,0.2);\n color: var(--green); transition: all 0.3s;\n }\n .badge.off { background: var(--red-dim); border-color: rgba(248,113,113,0.2); color: var(--red); }\n .badge-dot { width: 6px; height: 6px; border-radius: 50%; background: currentColor; animation: pulse 2s ease-in-out infinite; }\n @keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.4;transform:scale(.7)} }\n .hdr-right { text-align: right; }\n .hdr-time { font-size: 0.72rem; color: var(--text-dim); line-height: 1.5; }\n\n /* \u2500\u2500 Section titles \u2500\u2500 */\n .sec-title {\n font-size: 0.62rem; font-weight: 700; letter-spacing: 0.12em;\n text-transform: uppercase; color: var(--text-dim); margin-bottom: 12px;\n display: flex; align-items: center; gap: 8px;\n }\n .sec-title::after { content: ''; flex: 1; height: 1px; background: var(--border); }\n\n /* \u2500\u2500 Stat cards \u2500\u2500 */\n .stats-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(155px, 1fr)); gap: 10px; margin-bottom: 32px; }\n .stat-card {\n background: var(--surface); border: 1px solid var(--border);\n border-radius: var(--radius); padding: 18px 16px;\n transition: border-color 0.2s, background 0.2s, transform 0.15s;\n position: relative; overflow: hidden;\n }\n .stat-card:hover { border-color: var(--border-bright); background: var(--surface-hover); transform: translateY(-1px); }\n .stat-card::before {\n content: ''; position: absolute; top: 0; left: 0; right: 0; height: 2px;\n background: linear-gradient(90deg, transparent, var(--purple), transparent);\n opacity: 0; transition: opacity 0.3s;\n }\n .stat-card:hover::before { opacity: 1; }\n .stat-lbl { font-size: 0.62rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.09em; color: var(--text-dim); margin-bottom: 10px; }\n .stat-val { font-size: 1.65rem; font-weight: 700; letter-spacing: -0.04em; line-height: 1; color: var(--text); }\n .stat-val.grad {\n background: linear-gradient(130deg, #a78bfa 0%, #f472b6 50%, #38bdf8 100%);\n -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;\n }\n .stat-note { font-size: 0.58rem; color: var(--text-dim); margin-top: 6px; }\n\n /* \u2500\u2500 Charts \u2500\u2500 */\n .charts-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; margin-bottom: 32px; }\n @media (max-width: 768px) { .charts-grid { grid-template-columns: 1fr; } }\n .chart-card {\n background: var(--surface); border: 1px solid var(--border);\n border-radius: var(--radius); padding: 20px;\n transition: border-color 0.2s;\n }\n .chart-card:hover { border-color: var(--border-bright); }\n .chart-title { font-size: 0.74rem; font-weight: 600; color: var(--text-sec); margin-bottom: 16px; display: flex; align-items: center; gap: 8px; }\n .chart-title-icon { font-size: 0.85rem; opacity: 0.6; }\n .chart-wrap { position: relative; height: 220px; }\n .chart-wrap canvas { width: 100% !important; height: 100% !important; }\n .chart-empty { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--text-dim); font-size: 0.78rem; }\n .no-charts-msg { text-align: center; padding: 40px 20px; color: var(--text-dim); font-size: 0.82rem; border: 1px dashed var(--border); border-radius: var(--radius); margin-bottom: 32px; }\n\n /* \u2500\u2500 Projects \u2500\u2500 */\n .projects-section { margin-bottom: 32px; }\n .proj-card {\n background: var(--surface); border: 1px solid var(--border);\n border-radius: var(--radius); margin-bottom: 8px; overflow: hidden;\n transition: border-color 0.2s, box-shadow 0.3s;\n }\n .proj-card:hover { border-color: var(--border-bright); box-shadow: 0 4px 24px rgba(0,0,0,0.15); }\n .proj-hdr {\n display: flex; align-items: center; padding: 14px 18px;\n cursor: pointer; gap: 14px; user-select: none;\n transition: background 0.15s;\n }\n .proj-hdr:hover { background: rgba(255,255,255,0.015); }\n .proj-icon {\n width: 38px; height: 38px; border-radius: 10px; flex-shrink: 0;\n display: flex; align-items: center; justify-content: center; font-size: 16px;\n border: 1px solid rgba(167,139,250,0.12);\n }\n .proj-icon.tier-high { background: linear-gradient(135deg, rgba(167,139,250,0.2), rgba(244,114,182,0.15)); }\n .proj-icon.tier-mid { background: linear-gradient(135deg, rgba(56,189,248,0.15), rgba(52,211,153,0.1)); }\n .proj-icon.tier-low { background: linear-gradient(135deg, rgba(255,255,255,0.06), rgba(255,255,255,0.03)); }\n .proj-info { flex: 1; min-width: 0; }\n .proj-name { font-size: 0.88rem; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n .proj-path { font-size: 0.64rem; color: var(--text-dim); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-family: 'SF Mono', 'Fira Code', monospace; }\n .proj-metrics { display: flex; align-items: center; gap: 24px; flex-shrink: 0; }\n .proj-metric { text-align: right; }\n .proj-metric-val { font-size: 0.88rem; font-weight: 700; }\n .proj-metric-val.cost { color: var(--purple); }\n .proj-metric-val.tokens { color: var(--blue); }\n .proj-metric-lbl { font-size: 0.58rem; color: var(--text-dim); margin-top: 2px; letter-spacing: 0.03em; }\n .chevron {\n width: 14px; height: 14px; flex-shrink: 0; color: var(--text-dim);\n transition: transform 0.25s ease;\n }\n .proj-card.open .chevron { transform: rotate(90deg); }\n .proj-bar-wrap { height: 3px; background: rgba(255,255,255,0.04); margin: 0 18px; border-radius: 3px; overflow: hidden; }\n .proj-bar { height: 100%; border-radius: 3px; transition: width 0.6s ease; }\n .proj-bar.tier-high { background: linear-gradient(90deg, #a78bfa, #f472b6); }\n .proj-bar.tier-mid { background: linear-gradient(90deg, #38bdf8, #34d399); }\n .proj-bar.tier-low { background: linear-gradient(90deg, rgba(148,163,184,0.4), rgba(148,163,184,0.2)); }\n\n /* Project detail panel */\n .proj-detail { display: none; border-top: 1px solid var(--border); }\n .proj-card.open .proj-detail { display: block; }\n .proj-detail-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: var(--border); }\n .proj-detail-grid > div { background: var(--bg); padding: 14px 18px; }\n .proj-detail-label { font-size: 0.6rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.08em; color: var(--text-dim); margin-bottom: 4px; }\n .proj-detail-value { font-size: 0.92rem; font-weight: 600; color: var(--text-sec); }\n .proj-sessions-title { font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; color: var(--text-dim); padding: 12px 18px 6px; }\n .sess-row {\n display: flex; align-items: center; gap: 12px;\n padding: 10px 18px 10px 18px;\n border-bottom: 1px solid rgba(255,255,255,0.03);\n transition: background 0.15s;\n }\n .sess-row:last-child { border-bottom: none; }\n .sess-row:hover { background: rgba(255,255,255,0.015); }\n .sess-dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; background: var(--text-dim); }\n .sess-dot.active { background: var(--green); box-shadow: 0 0 8px rgba(52,211,153,0.5); }\n .sess-time { font-size: 0.76rem; color: var(--text-sec); flex: 1; }\n .sess-tokens { font-size: 0.68rem; color: var(--text-dim); }\n .sess-calls { font-size: 0.68rem; color: var(--text-dim); min-width: 50px; text-align: right; }\n .sess-cost { font-size: 0.82rem; font-weight: 600; color: var(--purple); min-width: 72px; text-align: right; }\n\n /* \u2500\u2500 Table \u2500\u2500 */\n .tbl-section { margin-bottom: 32px; }\n .tbl-wrap { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }\n table { width: 100%; border-collapse: collapse; font-size: 0.78rem; }\n thead th {\n padding: 11px 14px; text-align: left; white-space: nowrap;\n font-size: 0.6rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em;\n color: var(--text-dim); background: rgba(0,0,0,0.2); border-bottom: 1px solid var(--border);\n }\n tbody td { padding: 10px 14px; border-bottom: 1px solid rgba(255,255,255,0.03); white-space: nowrap; vertical-align: middle; }\n tbody tr:last-child td { border-bottom: none; }\n tbody tr { transition: background 0.12s; }\n tbody tr:hover td { background: rgba(255,255,255,0.018); }\n .model-tag {\n display: inline-block; padding: 2px 8px; border-radius: 6px;\n font-size: 0.66rem; font-weight: 600;\n }\n .model-tag.opus { background: var(--purple-dim); color: var(--purple); border: 1px solid rgba(167,139,250,0.12); }\n .model-tag.sonnet { background: var(--blue-dim); color: var(--blue); border: 1px solid rgba(56,189,248,0.12); }\n .model-tag.haiku { background: var(--green-dim); color: var(--green); border: 1px solid rgba(52,211,153,0.12); }\n .model-tag.other { background: rgba(255,255,255,0.04); color: var(--text-sec); border: 1px solid var(--border); }\n .cost-cell { color: var(--purple); font-weight: 600; }\n .dim { color: var(--text-dim); }\n .sec { color: var(--text-sec); }\n @keyframes flash-in { 0% { background: rgba(167,139,250,0.1); } 100% { background: transparent; } }\n .flash td { animation: flash-in 1.8s ease forwards; }\n .empty-cell { text-align: center; padding: 48px 24px; }\n .empty-icon { font-size: 1.8rem; opacity: 0.25; margin-bottom: 10px; }\n .empty-text { font-size: 0.8rem; color: var(--text-dim); }\n .no-proj { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 32px; text-align: center; color: var(--text-dim); font-size: 0.82rem; line-height: 1.7; }\n .no-proj code { background: rgba(255,255,255,0.06); border-radius: 4px; padding: 1px 6px; font-family: 'SF Mono', monospace; font-size: 0.78rem; color: var(--text-sec); }\n\n /* \u2500\u2500 Pagination \u2500\u2500 */\n .pager {\n display: flex; align-items: center; justify-content: space-between;\n padding: 12px 16px; border-top: 1px solid var(--border);\n background: rgba(0,0,0,0.12);\n }\n .pager-info { font-size: 0.7rem; color: var(--text-dim); }\n .pager-btns { display: flex; align-items: center; gap: 4px; }\n .pager-btn {\n width: 32px; height: 32px; border-radius: 8px; border: 1px solid var(--border);\n background: var(--surface); color: var(--text-sec);\n font-size: 0.72rem; font-weight: 600; cursor: pointer;\n display: flex; align-items: center; justify-content: center;\n transition: all 0.15s;\n }\n .pager-btn:hover:not(:disabled) { border-color: var(--border-bright); background: var(--surface-hover); color: var(--text); }\n .pager-btn:disabled { opacity: 0.3; cursor: default; }\n .pager-btn.active { background: var(--purple-dim); border-color: rgba(167,139,250,0.3); color: var(--purple); }\n .pager-btn.nav { font-size: 0.82rem; width: 36px; }\n .pager-ellipsis { width: 24px; text-align: center; color: var(--text-dim); font-size: 0.7rem; }\n\n /* \u2500\u2500 FAQ \u2500\u2500 */\n .faq { max-width: 720px; margin: 32px auto 0; padding: 0 16px; }\n .faq-title { font-size: 0.82rem; font-weight: 600; color: var(--text-sec); margin-bottom: 12px; }\n .faq details { margin-bottom: 8px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); }\n .faq summary { cursor: pointer; padding: 10px 14px; font-size: 0.75rem; color: var(--text-sec); font-weight: 500; }\n .faq summary:hover { color: var(--text); }\n .faq .faq-body { padding: 0 14px 12px; font-size: 0.7rem; color: var(--text-dim); line-height: 1.6; }\n .faq .faq-body code { background: rgba(167,139,250,0.1); padding: 1px 5px; border-radius: 3px; font-size: 0.68rem; }\n .faq table { width: 100%; border-collapse: collapse; margin: 8px 0; font-size: 0.68rem; }\n .faq th, .faq td { text-align: left; padding: 4px 8px; border-bottom: 1px solid var(--border); }\n .faq th { color: var(--text-sec); font-weight: 500; }\n\n /* \u2500\u2500 Footer \u2500\u2500 */\n .footer { text-align: center; padding: 24px; font-size: 0.65rem; color: var(--text-dim); }\n .footer a { color: var(--purple); text-decoration: none; }\n\n /* \u2500\u2500 Scrollbar \u2500\u2500 */\n ::-webkit-scrollbar { width: 5px; }\n ::-webkit-scrollbar-track { background: transparent; }\n ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 3px; }\n ::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.14); }\n\n /* \u2500\u2500 Responsive \u2500\u2500 */\n @media (max-width: 640px) {\n .stats-grid { grid-template-columns: repeat(2, 1fr); }\n .proj-metrics { gap: 14px; }\n .proj-metric:nth-child(n+3) { display: none; }\n thead th:nth-child(4), tbody td:nth-child(4),\n thead th:nth-child(7), tbody td:nth-child(7) { display: none; }\n }\n </style>\n</head>\n<body>\n<div class=\"wrap\">\n\n <!-- Header -->\n <div class=\"hdr\">\n <div class=\"hdr-left\">\n <div class=\"logo\">&#x2B21;</div>\n <div>\n <h1>Token Counter</h1>\n <div class=\"hdr-sub\" id=\"session-line\">Connecting&hellip;</div>\n </div>\n <div class=\"badge\" id=\"badge\"><div class=\"badge-dot\"></div><span id=\"badge-txt\">LIVE</span></div>\n </div>\n <div class=\"hdr-right\">\n <div class=\"hdr-time\" id=\"hdr-time\"></div>\n </div>\n </div>\n\n <!-- Stats -->\n <div class=\"sec-title\" id=\"sess-section-label\">Current Session</div>\n <div class=\"stats-grid\">\n <div class=\"stat-card\"><div class=\"stat-lbl\">Total Cost</div><div class=\"stat-val grad\" id=\"c-cost\">&mdash;</div><div class=\"stat-note\" id=\"c-cost-note\"></div></div>\n <div class=\"stat-card\"><div class=\"stat-lbl\">Raw Input</div><div class=\"stat-val\" id=\"c-in\">&mdash;</div></div>\n <div class=\"stat-card\"><div class=\"stat-lbl\">Output Tokens</div><div class=\"stat-val\" id=\"c-out\">&mdash;</div></div>\n <div class=\"stat-card\"><div class=\"stat-lbl\">Cache Read</div><div class=\"stat-val\" id=\"c-cr\">&mdash;</div></div>\n <div class=\"stat-card\"><div class=\"stat-lbl\">Cache Write</div><div class=\"stat-val\" id=\"c-cw\">&mdash;</div></div>\n <div class=\"stat-card\"><div class=\"stat-lbl\">API Calls</div><div class=\"stat-val\" id=\"c-n\">&mdash;</div><div class=\"stat-note\" id=\"c-proj-count\"></div></div>\n </div>\n\n <!-- Charts -->\n <div class=\"sec-title\">Analytics</div>\n <div id=\"charts-section\">\n <div class=\"charts-grid\">\n <div class=\"chart-card\">\n <div class=\"chart-title\"><span class=\"chart-title-icon\">&#x1F4C8;</span> Spending Trend</div>\n <div class=\"chart-wrap\"><canvas id=\"chart-trend\"></canvas></div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\"><span class=\"chart-title-icon\">&#x1F4C1;</span> Cost by Project</div>\n <div class=\"chart-wrap\"><canvas id=\"chart-proj-cost\"></canvas></div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\"><span class=\"chart-title-icon\">&#x1F4CA;</span> Token Breakdown</div>\n <div class=\"chart-wrap\"><canvas id=\"chart-tokens\"></canvas></div>\n </div>\n <div class=\"chart-card\">\n <div class=\"chart-title\"><span class=\"chart-title-icon\">&#x2699;</span> Model Distribution</div>\n <div class=\"chart-wrap\"><canvas id=\"chart-models\"></canvas></div>\n </div>\n </div>\n </div>\n\n <!-- Projects -->\n <div class=\"projects-section\">\n <div class=\"sec-title\">Projects</div>\n <div id=\"proj-list\"><div class=\"no-proj\">Loading&hellip;</div></div>\n </div>\n\n <!-- Recent calls -->\n <div class=\"tbl-section\">\n <div class=\"sec-title\">Recent Calls</div>\n <div class=\"tbl-wrap\">\n <table>\n <thead>\n <tr><th>Time</th><th>Model</th><th>Project</th><th>Description</th><th>Raw Input</th><th>Output</th><th>Cache R/W</th><th>Cost</th></tr>\n </thead>\n <tbody id=\"tbody\">\n <tr><td colspan=\"8\"><div class=\"empty-cell\"><div class=\"empty-icon\">&#x25CE;</div><div class=\"empty-text\">Waiting for first log_usage call&hellip;</div></div></td></tr>\n </tbody>\n </table>\n <div class=\"pager\" id=\"pager\" style=\"display:none\">\n <div class=\"pager-info\" id=\"pager-info\"></div>\n <div class=\"pager-btns\" id=\"pager-btns\"></div>\n </div>\n </div>\n </div>\n\n <div class=\"faq\">\n <div class=\"faq-title\">FAQ &mdash; Understanding Your Token Usage</div>\n <details>\n <summary>What do the columns mean?</summary>\n <div class=\"faq-body\">\n <table>\n <tr><th>Column</th><th>Meaning</th></tr>\n <tr><td><code>Raw Input</code></td><td>Non-cached input tokens &mdash; only the new tokens sent to the API that weren't in any cache. Often very small because Claude Code caches aggressively.</td></tr>\n <tr><td><code>Output</code></td><td>Tokens generated by Claude in its response.</td></tr>\n <tr><td><code>Cache R/W</code></td><td><b>R</b> = cache read (reused from previous turns), <b>W</b> = cache write (newly cached). These represent your conversation context, system prompt, and CLAUDE.md being efficiently reused.</td></tr>\n <tr><td><code>Cost</code></td><td>Total cost for this call, calculated from all four token types at their respective rates.</td></tr>\n </table>\n </div>\n </details>\n <details>\n <summary>Why is Raw Input so low (e.g. 6 tokens)?</summary>\n <div class=\"faq-body\">\n Claude Code uses <b>prompt caching</b>. Your system prompt, CLAUDE.md, tool definitions, and conversation history are all cached. On each turn, only the brand-new user message is \"raw input\" &mdash; everything else is a cheap cache read. This is normal and saves you money.\n </div>\n </details>\n <details>\n <summary>How is cost calculated?</summary>\n <div class=\"faq-body\">\n Each token type has a different rate per million tokens:\n <table>\n <tr><th>Token Type</th><th>Sonnet</th><th>Opus</th></tr>\n <tr><td>Raw Input</td><td>$3.00/M</td><td>$15.00/M</td></tr>\n <tr><td>Cache Write</td><td>$3.75/M</td><td>$18.75/M</td></tr>\n <tr><td>Cache Read</td><td>$0.30/M</td><td>$1.50/M</td></tr>\n <tr><td>Output</td><td>$15.00/M</td><td>$75.00/M</td></tr>\n </table>\n <b>Cost</b> = (Raw Input &times; rate) + (Cache Write &times; rate) + (Cache Read &times; rate) + (Output &times; rate)<br>\n Cache reads are 10x cheaper than raw input &mdash; caching saves significant cost.\n </div>\n </details>\n <details>\n <summary>What is the total context size per turn?</summary>\n <div class=\"faq-body\">\n Total context = <code>Raw Input + Cache Read + Cache Write</code>. For example, if Raw Input is 6, Cache Read is 60K, and Cache Write is 13K, your total context was ~73K tokens. The breakdown shows that 99.99% was efficiently cached.\n </div>\n </details>\n </div>\n <div class=\"footer\">Token Counter MCP &middot; Real-time usage tracking</div>\n</div>\n\n<script>\n(function() {\n 'use strict';\n\n // \u2500\u2500 Chart.js check \u2500\u2500\n var hasChartJs = typeof Chart !== 'undefined';\n if (!hasChartJs) {\n document.getElementById('charts-section').innerHTML = '<div class=\"no-charts-msg\">Charts require internet connection (Chart.js CDN). Live stats are still updating.</div>';\n }\n\n // \u2500\u2500 Chart.js global defaults \u2500\u2500\n if (hasChartJs) {\n Chart.defaults.color = '#64748b';\n Chart.defaults.borderColor = 'rgba(255,255,255,0.04)';\n Chart.defaults.font.family = \"-apple-system, BlinkMacSystemFont, 'Inter', system-ui, sans-serif\";\n Chart.defaults.font.size = 11;\n Chart.defaults.plugins.legend.labels.padding = 12;\n Chart.defaults.plugins.legend.labels.usePointStyle = true;\n Chart.defaults.plugins.legend.labels.pointStyleWidth = 8;\n }\n\n var COLORS = ['#a78bfa','#38bdf8','#f472b6','#34d399','#fbbf24','#f87171','#818cf8','#22d3ee','#fb923c','#a3e635'];\n var COLORS_DIM = COLORS.map(function(c) { return c + '18'; });\n\n // \u2500\u2500 Utility functions \u2500\u2500\n function fmt(n) { n = n || 0; if (n >= 1e6) return (n/1e6).toFixed(1)+'M'; if (n >= 1e3) return (n/1e3).toFixed(1)+'K'; return String(n); }\n function fmtCost(u) { if (!u || u < 0.0001) return '~\\$0.00'; var s = parseFloat(u.toPrecision(2)); if (s < 0.01) return '~\\$'+s.toFixed(4); if (s < 1) return '~\\$'+s.toFixed(3); return '~\\$'+s.toFixed(2); }\n function fmtCostShort(u) { if (!u || u < 0.001) return '\\$0'; if (u < 1) return '\\$'+u.toFixed(2); return '\\$'+u.toFixed(1); }\n function hhmm(ts) { return new Date(ts).toLocaleTimeString([], {hour:'2-digit', minute:'2-digit', second:'2-digit'}); }\n function dtFmt(ts) { var d = new Date(ts); var today = new Date(); var isToday = d.toDateString() === today.toDateString(); var t = d.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'}); return isToday ? 'Today '+t : d.toLocaleDateString([], {month:'short', day:'numeric'})+' '+t; }\n function mdl(m) { return m ? m.replace('claude-','').replace(/-\\d{8}$/, '') : '\\u2014'; }\n function mdlClass(m) { if (!m) return 'other'; if (m.indexOf('opus') !== -1) return 'opus'; if (m.indexOf('sonnet') !== -1) return 'sonnet'; if (m.indexOf('haiku') !== -1) return 'haiku'; return 'other'; }\n function projBase(p) { if (!p || p === '(no project)') return '\\u2014'; var parts = p.split('/').filter(Boolean); return parts[parts.length - 1] || p; }\n function dateFmt(iso) { var d = new Date(iso); return d.toLocaleDateString([], {month:'short', day:'numeric'}); }\n\n // \u2500\u2500 State \u2500\u2500\n var token = new URLSearchParams(location.search).get('token') || '';\n var isRemote = !!token;\n var LS_KEY = 'tc_v2_' + token;\n var localEntries = [];\n var allHistory = [];\n var openProjects = {};\n\n var badge = document.getElementById('badge');\n var badgeTxt = document.getElementById('badge-txt');\n var sessionLine = document.getElementById('session-line');\n var hdrTime = document.getElementById('hdr-time');\n\n // Chart instances\n var chartTrend = null;\n var chartProjCost = null;\n var chartTokens = null;\n var chartModels = null;\n\n if (isRemote) {\n document.getElementById('sess-section-label').textContent = 'All Time Stats';\n try { localEntries = JSON.parse(localStorage.getItem(LS_KEY) || '[]'); } catch(e) {}\n if (localEntries.length) {\n var st = buildState(localEntries);\n renderState(st, false);\n updateCharts(localEntries, st.grouped);\n }\n }\n\n // \u2500\u2500 SSE \u2500\u2500\n var sseUrl = isRemote ? '/events?token=' + encodeURIComponent(token) : '/events';\n var es = new EventSource(sseUrl);\n es.onerror = function() { badge.className = 'badge off'; badgeTxt.textContent = 'OFFLINE'; };\n es.onopen = function() { badge.className = 'badge'; badgeTxt.textContent = 'LIVE'; };\n\n es.onmessage = function(ev) {\n var d = JSON.parse(ev.data);\n if (isRemote) {\n var map = {};\n localEntries.forEach(function(e) { map[e.id] = e; });\n (d.entries || []).forEach(function(e) { map[e.id] = e; });\n localEntries = Object.values(map).sort(function(a, b) { return a.timestamp < b.timestamp ? -1 : 1; });\n if (localEntries.length > 1000) localEntries = localEntries.slice(-1000);\n try { localStorage.setItem(LS_KEY, JSON.stringify(localEntries)); } catch(e) {}\n var st = buildState(localEntries);\n renderState(st, true);\n updateCharts(localEntries, st.grouped);\n } else {\n renderLocalState(d);\n var entries = (d.history || d.session.entries || []);\n updateCharts(entries, d.grouped || []);\n }\n };\n\n // \u2500\u2500 Build state (remote mode) \u2500\u2500\n function buildState(entries) {\n var totals = {inputTokens:0, outputTokens:0, cacheReadTokens:0, cacheWriteTokens:0, totalCost:0};\n entries.forEach(function(e) {\n totals.inputTokens += e.inputTokens || 0;\n totals.outputTokens += e.outputTokens || 0;\n totals.cacheReadTokens += e.cacheReadTokens || 0;\n totals.cacheWriteTokens += e.cacheWriteTokens || 0;\n totals.totalCost += e.totalCost || 0;\n });\n var pm = {};\n entries.forEach(function(e) {\n var proj = e.project || '(no project)';\n if (!pm[proj]) pm[proj] = {};\n var sid = e.sessionId || 'default';\n if (!pm[proj][sid]) pm[proj][sid] = { entries: [], startedAt: e.timestamp };\n pm[proj][sid].entries.push(e);\n });\n var grouped = Object.keys(pm).map(function(project) {\n var sm = pm[project];\n var allE = [];\n var sessions = Object.keys(sm).map(function(sid) {\n var s = sm[sid];\n allE = allE.concat(s.entries);\n return {\n sessionId: sid, startedAt: s.startedAt,\n totalInputTokens: s.entries.reduce(function(a, e) { return a + (e.inputTokens||0); }, 0),\n totalOutputTokens: s.entries.reduce(function(a, e) { return a + (e.outputTokens||0); }, 0),\n totalCost: s.entries.reduce(function(a, e) { return a + (e.totalCost||0); }, 0),\n entryCount: s.entries.length,\n };\n }).sort(function(a, b) { return b.startedAt.localeCompare(a.startedAt); });\n var parts = project.split('/').filter(Boolean);\n return {\n project: project, sessions: sessions,\n displayName: project === '(no project)' ? '(no project)' : parts[parts.length-1] || project,\n totalCost: allE.reduce(function(a, e) { return a + (e.totalCost||0); }, 0),\n totalInputTokens: allE.reduce(function(a, e) { return a + (e.inputTokens||0); }, 0),\n totalOutputTokens: allE.reduce(function(a, e) { return a + (e.outputTokens||0); }, 0),\n lastActiveAt: sessions[0] ? sessions[0].startedAt : '',\n };\n }).sort(function(a, b) { return b.totalCost - a.totalCost; });\n return { totals: totals, grouped: grouped, entries: entries };\n }\n\n // \u2500\u2500 Render: remote mode \u2500\u2500\n function renderState(st, isLive) {\n var t = st.totals;\n var n = st.entries.length;\n sessionLine.textContent = n + ' call' + (n===1?'':'s') + ' \\u00B7 ' + st.grouped.length + ' project' + (st.grouped.length===1?'':'s') + (isLive ? '' : ' \\u00B7 cached');\n hdrTime.innerHTML = 'Updated ' + new Date().toLocaleTimeString();\n updateStatCards(t, n, st.grouped.length);\n renderProjects(st.grouped, null);\n renderTable(st.entries.slice().reverse());\n }\n\n // \u2500\u2500 Render: local mode \u2500\u2500\n function renderLocalState(d) {\n var sess = d.session, t = sess.totals, entries = sess.entries || [], grouped = d.grouped || [];\n var started = new Date(sess.startedAt);\n sessionLine.textContent = 'Session since ' + started.toLocaleTimeString() + ' \\u00B7 ' + entries.length + ' call' + (entries.length===1?'':'s');\n hdrTime.innerHTML = 'Updated ' + new Date().toLocaleTimeString() + '<br><span style=\"color:var(--text-dim);font-size:.64rem\">' + started.toLocaleDateString([], {weekday:'short', month:'short', day:'numeric'}) + '</span>';\n updateStatCards(t, entries.length, grouped.length);\n renderProjects(grouped, sess.sessionId);\n renderTable(entries.slice().reverse());\n }\n\n function updateStatCards(t, callCount, projCount) {\n document.getElementById('c-cost').textContent = fmtCost(t.totalCost);\n document.getElementById('c-in').textContent = fmt(t.inputTokens);\n document.getElementById('c-out').textContent = fmt(t.outputTokens);\n document.getElementById('c-cr').textContent = fmt(t.cacheReadTokens);\n document.getElementById('c-cw').textContent = fmt(t.cacheWriteTokens);\n document.getElementById('c-n').textContent = callCount;\n document.getElementById('c-proj-count').textContent = projCount + ' project' + (projCount === 1 ? '' : 's');\n }\n\n // \u2500\u2500 Chart data aggregators \u2500\u2500\n function aggregateByDate(entries) {\n var byDate = {};\n entries.forEach(function(e) {\n var date = e.timestamp ? e.timestamp.slice(0, 10) : '';\n if (!date) return;\n if (!byDate[date]) byDate[date] = 0;\n byDate[date] += e.totalCost || 0;\n });\n var result = [];\n var today = new Date();\n for (var i = 29; i >= 0; i--) {\n var d = new Date(today);\n d.setDate(d.getDate() - i);\n var key = d.toISOString().slice(0, 10);\n result.push({ date: key, label: dateFmt(key), cost: byDate[key] || 0 });\n }\n return result;\n }\n\n function aggregateByModel(entries) {\n var byModel = {};\n entries.forEach(function(e) {\n var m = mdl(e.model || 'unknown');\n if (!byModel[m]) byModel[m] = { count: 0, cost: 0, tokens: 0 };\n byModel[m].count++;\n byModel[m].cost += e.totalCost || 0;\n byModel[m].tokens += (e.inputTokens || 0) + (e.outputTokens || 0);\n });\n return Object.keys(byModel).map(function(m) {\n return { model: m, count: byModel[m].count, cost: byModel[m].cost, tokens: byModel[m].tokens };\n }).sort(function(a, b) { return b.cost - a.cost; });\n }\n\n function aggregateTokensByProject(grouped) {\n return grouped.slice(0, 8).map(function(g) {\n return {\n name: g.displayName,\n input: g.totalInputTokens || 0,\n output: g.totalOutputTokens || 0,\n };\n });\n }\n\n // \u2500\u2500 Chart rendering \u2500\u2500\n function updateCharts(entries, grouped) {\n if (!hasChartJs || !entries.length) return;\n\n // 1. Spending Trend (area line chart)\n var trendData = aggregateByDate(entries);\n var trendLabels = trendData.map(function(d) { return d.label; });\n var trendValues = trendData.map(function(d) { return d.cost; });\n\n if (!chartTrend) {\n var ctx1 = document.getElementById('chart-trend').getContext('2d');\n var gradient1 = ctx1.createLinearGradient(0, 0, 0, 220);\n gradient1.addColorStop(0, 'rgba(167,139,250,0.25)');\n gradient1.addColorStop(1, 'rgba(167,139,250,0.0)');\n chartTrend = new Chart(ctx1, {\n type: 'line',\n data: {\n labels: trendLabels,\n datasets: [{\n label: 'Daily Cost',\n data: trendValues,\n borderColor: '#a78bfa',\n backgroundColor: gradient1,\n fill: true,\n tension: 0.4,\n borderWidth: 2,\n pointRadius: 0,\n pointHoverRadius: 5,\n pointHoverBackgroundColor: '#a78bfa',\n pointHoverBorderColor: '#fff',\n pointHoverBorderWidth: 2,\n }]\n },\n options: {\n responsive: true, maintainAspectRatio: false,\n interaction: { mode: 'index', intersect: false },\n plugins: {\n legend: { display: false },\n tooltip: {\n backgroundColor: 'rgba(15,17,25,0.95)',\n borderColor: 'rgba(167,139,250,0.3)',\n borderWidth: 1,\n titleColor: '#f1f5f9',\n bodyColor: '#94a3b8',\n padding: 10,\n callbacks: {\n label: function(ctx) { return '\\$' + ctx.parsed.y.toFixed(4); }\n }\n }\n },\n scales: {\n x: {\n grid: { display: false },\n ticks: { maxTicksLimit: 7, font: { size: 10 } }\n },\n y: {\n grid: { color: 'rgba(255,255,255,0.03)' },\n ticks: { font: { size: 10 }, callback: function(v) { return '\\$' + v.toFixed(2); } }\n }\n }\n }\n });\n } else {\n chartTrend.data.labels = trendLabels;\n chartTrend.data.datasets[0].data = trendValues;\n chartTrend.update('none');\n }\n\n // 2. Cost by Project (doughnut)\n var projData = grouped.slice(0, 8).filter(function(g) { return g.totalCost > 0; });\n var projLabels = projData.map(function(g) { return g.displayName; });\n var projValues = projData.map(function(g) { return g.totalCost; });\n\n if (!chartProjCost) {\n var ctx2 = document.getElementById('chart-proj-cost').getContext('2d');\n chartProjCost = new Chart(ctx2, {\n type: 'doughnut',\n data: {\n labels: projLabels,\n datasets: [{\n data: projValues,\n backgroundColor: COLORS.slice(0, projValues.length),\n borderColor: 'rgba(6,8,14,0.8)',\n borderWidth: 2,\n hoverBorderColor: '#fff',\n hoverBorderWidth: 2,\n }]\n },\n options: {\n responsive: true, maintainAspectRatio: false,\n cutout: '62%',\n plugins: {\n legend: {\n position: 'right',\n labels: { font: { size: 10 }, padding: 8, boxWidth: 10 }\n },\n tooltip: {\n backgroundColor: 'rgba(15,17,25,0.95)',\n borderColor: 'rgba(167,139,250,0.3)',\n borderWidth: 1,\n padding: 10,\n callbacks: {\n label: function(ctx) { return ' ' + ctx.label + ': \\$' + ctx.parsed.toFixed(4); }\n }\n }\n }\n }\n });\n } else {\n chartProjCost.data.labels = projLabels;\n chartProjCost.data.datasets[0].data = projValues;\n chartProjCost.data.datasets[0].backgroundColor = COLORS.slice(0, projValues.length);\n chartProjCost.update('none');\n }\n\n // 3. Token Breakdown by Project (stacked horizontal bar)\n var tokenData = aggregateTokensByProject(grouped);\n var tokenLabels = tokenData.map(function(d) { return d.name; });\n if (!tokenLabels.length) tokenLabels = ['No data'];\n\n if (!chartTokens) {\n var ctx3 = document.getElementById('chart-tokens').getContext('2d');\n chartTokens = new Chart(ctx3, {\n type: 'bar',\n data: {\n labels: tokenLabels,\n datasets: [\n { label: 'Raw Input', data: tokenData.map(function(d) { return d.input; }), backgroundColor: '#a78bfa', borderRadius: 3 },\n { label: 'Output', data: tokenData.map(function(d) { return d.output; }), backgroundColor: '#38bdf8', borderRadius: 3 },\n ]\n },\n options: {\n responsive: true, maintainAspectRatio: false,\n indexAxis: 'y',\n plugins: {\n legend: { position: 'top', labels: { font: { size: 10 }, padding: 8, boxWidth: 10 } },\n tooltip: {\n backgroundColor: 'rgba(15,17,25,0.95)',\n borderColor: 'rgba(167,139,250,0.3)',\n borderWidth: 1,\n padding: 10,\n callbacks: {\n label: function(ctx) { return ' ' + ctx.dataset.label + ': ' + fmt(ctx.parsed.x); }\n }\n }\n },\n scales: {\n x: {\n stacked: true,\n grid: { color: 'rgba(255,255,255,0.03)' },\n ticks: { font: { size: 10 }, callback: function(v) { return fmt(v); } }\n },\n y: {\n stacked: true,\n grid: { display: false },\n ticks: { font: { size: 10 } }\n }\n }\n }\n });\n } else {\n chartTokens.data.labels = tokenLabels;\n chartTokens.data.datasets[0].data = tokenData.map(function(d) { return d.input; });\n chartTokens.data.datasets[1].data = tokenData.map(function(d) { return d.output; });\n chartTokens.update('none');\n }\n\n // 4. Model Distribution (doughnut)\n var modelData = aggregateByModel(entries);\n var modelLabels = modelData.map(function(d) { return d.model; });\n var modelValues = modelData.map(function(d) { return d.cost; });\n\n if (!chartModels) {\n var ctx4 = document.getElementById('chart-models').getContext('2d');\n chartModels = new Chart(ctx4, {\n type: 'doughnut',\n data: {\n labels: modelLabels,\n datasets: [{\n data: modelValues,\n backgroundColor: ['#a78bfa','#38bdf8','#34d399','#fbbf24','#f472b6','#f87171'],\n borderColor: 'rgba(6,8,14,0.8)',\n borderWidth: 2,\n hoverBorderColor: '#fff',\n hoverBorderWidth: 2,\n }]\n },\n options: {\n responsive: true, maintainAspectRatio: false,\n cutout: '62%',\n plugins: {\n legend: {\n position: 'right',\n labels: { font: { size: 10 }, padding: 8, boxWidth: 10 }\n },\n tooltip: {\n backgroundColor: 'rgba(15,17,25,0.95)',\n borderColor: 'rgba(167,139,250,0.3)',\n borderWidth: 1,\n padding: 10,\n callbacks: {\n label: function(ctx) { return ' ' + ctx.label + ': \\$' + ctx.parsed.toFixed(4) + ' (' + modelData[ctx.dataIndex].count + ' calls)'; }\n }\n }\n }\n }\n });\n } else {\n chartModels.data.labels = modelLabels;\n chartModels.data.datasets[0].data = modelValues;\n chartModels.update('none');\n }\n }\n\n // \u2500\u2500 Render projects \u2500\u2500\n function renderProjects(grouped, currentSessId) {\n var pl = document.getElementById('proj-list');\n if (!grouped || !grouped.length) {\n pl.innerHTML = '<div class=\"no-proj\">No project data yet.<br>Pass a <code>project</code> param to <code>log_usage</code> to track by folder.</div>';\n return;\n }\n var maxCost = Math.max.apply(null, grouped.map(function(g) { return g.totalCost; }).concat([0.001]));\n var totalCost = grouped.reduce(function(a, g) { return a + g.totalCost; }, 0);\n\n pl.innerHTML = grouped.map(function(g) {\n var isOpen = openProjects[g.project];\n var barPct = Math.max(4, Math.round(g.totalCost / maxCost * 100));\n var costPct = totalCost > 0 ? ((g.totalCost / totalCost) * 100).toFixed(1) : '0';\n var tier = g.totalCost / maxCost > 0.5 ? 'high' : (g.totalCost / maxCost > 0.15 ? 'mid' : 'low');\n var totalTokens = (g.totalInputTokens || 0) + (g.totalOutputTokens || 0);\n\n var sessHtml = (g.sessions || []).map(function(s) {\n var isActive = currentSessId && s.sessionId === currentSessId;\n return '<div class=\"sess-row\">' +\n '<div class=\"sess-dot' + (isActive ? ' active' : '') + '\"></div>' +\n '<span class=\"sess-time\">' + dtFmt(s.startedAt) + '</span>' +\n '<span class=\"sess-tokens\">' + fmt((s.totalInputTokens||0)+(s.totalOutputTokens||0)) + ' tok</span>' +\n '<span class=\"sess-calls\">' + s.entryCount + ' call' + (s.entryCount===1?'':'s') + '</span>' +\n '<span class=\"sess-cost\">' + fmtCost(s.totalCost) + '</span>' +\n '</div>';\n }).join('');\n\n return '<div class=\"proj-card' + (isOpen ? ' open' : '') + '\" data-proj=\"' + g.project + '\">' +\n '<div class=\"proj-hdr\" onclick=\"window._toggleProj(this.parentElement)\">' +\n '<div class=\"proj-icon tier-' + tier + '\">&#x1F4C1;</div>' +\n '<div class=\"proj-info\">' +\n '<div class=\"proj-name\">' + g.displayName + '</div>' +\n '<div class=\"proj-path\">' + g.project + '</div>' +\n '</div>' +\n '<div class=\"proj-metrics\">' +\n '<div class=\"proj-metric\"><div class=\"proj-metric-val cost\">' + fmtCost(g.totalCost) + '</div><div class=\"proj-metric-lbl\">cost (' + costPct + '%)</div></div>' +\n '<div class=\"proj-metric\"><div class=\"proj-metric-val tokens\">' + fmt(totalTokens) + '</div><div class=\"proj-metric-lbl\">tokens</div></div>' +\n '<div class=\"proj-metric\"><div class=\"proj-metric-val\" style=\"color:var(--text-sec)\">' + (g.sessions||[]).length + '</div><div class=\"proj-metric-lbl\">sessions</div></div>' +\n '</div>' +\n '<svg class=\"chevron\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" stroke-width=\"2.5\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" d=\"M9 5l7 7-7 7\"/></svg>' +\n '</div>' +\n '<div class=\"proj-bar-wrap\"><div class=\"proj-bar tier-' + tier + '\" style=\"width:' + barPct + '%\"></div></div>' +\n '<div class=\"proj-detail\">' +\n '<div class=\"proj-detail-grid\">' +\n '<div><div class=\"proj-detail-label\">Raw Input</div><div class=\"proj-detail-value\">' + fmt(g.totalInputTokens) + '</div></div>' +\n '<div><div class=\"proj-detail-label\">Output Tokens</div><div class=\"proj-detail-value\">' + fmt(g.totalOutputTokens) + '</div></div>' +\n '<div><div class=\"proj-detail-label\">Total Cost</div><div class=\"proj-detail-value\" style=\"color:var(--purple)\">' + fmtCost(g.totalCost) + '</div></div>' +\n '<div><div class=\"proj-detail-label\">Last Active</div><div class=\"proj-detail-value\">' + (g.lastActiveAt ? dtFmt(g.lastActiveAt) : '\\u2014') + '</div></div>' +\n '</div>' +\n '<div class=\"proj-sessions-title\">Sessions</div>' +\n sessHtml +\n '</div>' +\n '</div>';\n }).join('');\n }\n\n // \u2500\u2500 Pagination state \u2500\u2500\n var PAGE_SIZE = 10;\n var currentPage = 1;\n var allRows = [];\n\n function renderTable(rows) {\n allRows = rows || [];\n // When new data arrives, stay on page 1 if new entries were added, otherwise keep current page\n var totalPages = Math.max(1, Math.ceil(allRows.length / PAGE_SIZE));\n if (currentPage > totalPages) currentPage = totalPages;\n renderTablePage();\n }\n\n function renderTablePage() {\n var tbody = document.getElementById('tbody');\n var pager = document.getElementById('pager');\n\n if (!allRows.length) {\n tbody.innerHTML = '<tr><td colspan=\"8\"><div class=\"empty-cell\"><div class=\"empty-icon\">&#x25CE;</div><div class=\"empty-text\">Waiting for first log_usage call&hellip;</div></div></td></tr>';\n pager.style.display = 'none';\n return;\n }\n\n var totalPages = Math.ceil(allRows.length / PAGE_SIZE);\n var start = (currentPage - 1) * PAGE_SIZE;\n var end = Math.min(start + PAGE_SIZE, allRows.length);\n var pageRows = allRows.slice(start, end);\n\n tbody.innerHTML = pageRows.map(function(en, i) {\n var mc = mdlClass(en.model);\n var isFirst = (currentPage === 1 && i === 0);\n return '<tr class=\"' + (isFirst ? 'flash' : '') + '\">' +\n '<td class=\"dim\">' + hhmm(en.timestamp) + '</td>' +\n '<td><span class=\"model-tag ' + mc + '\">' + mdl(en.model) + '</span></td>' +\n '<td class=\"dim\" title=\"' + (en.project || '') + '\">' + projBase(en.project) + '</td>' +\n '<td class=\"' + (en.description ? 'sec' : 'dim') + '\">' + (en.description || '\\u2014') + '</td>' +\n '<td class=\"sec\">' + fmt(en.inputTokens) + '</td>' +\n '<td class=\"sec\">' + fmt(en.outputTokens) + '</td>' +\n '<td class=\"dim\">' + fmt(en.cacheReadTokens) + '/' + fmt(en.cacheWriteTokens) + '</td>' +\n '<td class=\"cost-cell\">' + fmtCost(en.totalCost) + '</td>' +\n '</tr>';\n }).join('');\n\n // Pagination controls\n if (totalPages <= 1) {\n pager.style.display = 'flex';\n document.getElementById('pager-info').textContent = 'Showing ' + allRows.length + ' of ' + allRows.length + ' calls';\n document.getElementById('pager-btns').innerHTML = '';\n return;\n }\n\n pager.style.display = 'flex';\n document.getElementById('pager-info').textContent = 'Showing ' + (start+1) + '\\u2013' + end + ' of ' + allRows.length + ' calls';\n\n var btns = '';\n // Prev button\n btns += '<button class=\"pager-btn nav\" ' + (currentPage <= 1 ? 'disabled' : '') + ' onclick=\"window._goPage(' + (currentPage-1) + ')\" title=\"Previous\">&#x2039;</button>';\n\n // Page number buttons with smart ellipsis\n var pages = buildPageNumbers(currentPage, totalPages);\n for (var i = 0; i < pages.length; i++) {\n if (pages[i] === '...') {\n btns += '<span class=\"pager-ellipsis\">&#x2026;</span>';\n } else {\n var p = pages[i];\n btns += '<button class=\"pager-btn' + (p === currentPage ? ' active' : '') + '\" onclick=\"window._goPage(' + p + ')\">' + p + '</button>';\n }\n }\n\n // Next button\n btns += '<button class=\"pager-btn nav\" ' + (currentPage >= totalPages ? 'disabled' : '') + ' onclick=\"window._goPage(' + (currentPage+1) + ')\" title=\"Next\">&#x203A;</button>';\n\n document.getElementById('pager-btns').innerHTML = btns;\n }\n\n function buildPageNumbers(current, total) {\n if (total <= 7) {\n var arr = [];\n for (var i = 1; i <= total; i++) arr.push(i);\n return arr;\n }\n var pages = [];\n pages.push(1);\n if (current > 3) pages.push('...');\n var rangeStart = Math.max(2, current - 1);\n var rangeEnd = Math.min(total - 1, current + 1);\n for (var j = rangeStart; j <= rangeEnd; j++) pages.push(j);\n if (current < total - 2) pages.push('...');\n pages.push(total);\n return pages;\n }\n\n window._goPage = function(page) {\n var totalPages = Math.ceil(allRows.length / PAGE_SIZE);\n if (page < 1 || page > totalPages) return;\n currentPage = page;\n renderTablePage();\n // Scroll table into view\n document.querySelector('.tbl-section').scrollIntoView({ behavior: 'smooth', block: 'start' });\n };\n\n // \u2500\u2500 Project toggle \u2500\u2500\n window._toggleProj = function(card) {\n var proj = card.getAttribute('data-proj');\n if (card.classList.contains('open')) {\n card.classList.remove('open');\n delete openProjects[proj];\n } else {\n card.classList.add('open');\n openProjects[proj] = true;\n }\n };\n\n})();\n</script>\n</body>\n</html>";
2
2
  //# sourceMappingURL=dashboard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,g+5CA66BnB,CAAC"}
1
+ {"version":3,"file":"dashboard.d.ts","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,cAAc,wihDAm+BnB,CAAC"}
package/dist/dashboard.js CHANGED
@@ -244,6 +244,18 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
244
244
  .pager-btn.nav { font-size: 0.82rem; width: 36px; }
245
245
  .pager-ellipsis { width: 24px; text-align: center; color: var(--text-dim); font-size: 0.7rem; }
246
246
 
247
+ /* ── FAQ ── */
248
+ .faq { max-width: 720px; margin: 32px auto 0; padding: 0 16px; }
249
+ .faq-title { font-size: 0.82rem; font-weight: 600; color: var(--text-sec); margin-bottom: 12px; }
250
+ .faq details { margin-bottom: 8px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface); }
251
+ .faq summary { cursor: pointer; padding: 10px 14px; font-size: 0.75rem; color: var(--text-sec); font-weight: 500; }
252
+ .faq summary:hover { color: var(--text); }
253
+ .faq .faq-body { padding: 0 14px 12px; font-size: 0.7rem; color: var(--text-dim); line-height: 1.6; }
254
+ .faq .faq-body code { background: rgba(167,139,250,0.1); padding: 1px 5px; border-radius: 3px; font-size: 0.68rem; }
255
+ .faq table { width: 100%; border-collapse: collapse; margin: 8px 0; font-size: 0.68rem; }
256
+ .faq th, .faq td { text-align: left; padding: 4px 8px; border-bottom: 1px solid var(--border); }
257
+ .faq th { color: var(--text-sec); font-weight: 500; }
258
+
247
259
  /* ── Footer ── */
248
260
  .footer { text-align: center; padding: 24px; font-size: 0.65rem; color: var(--text-dim); }
249
261
  .footer a { color: var(--purple); text-decoration: none; }
@@ -286,7 +298,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
286
298
  <div class="sec-title" id="sess-section-label">Current Session</div>
287
299
  <div class="stats-grid">
288
300
  <div class="stat-card"><div class="stat-lbl">Total Cost</div><div class="stat-val grad" id="c-cost">&mdash;</div><div class="stat-note" id="c-cost-note"></div></div>
289
- <div class="stat-card"><div class="stat-lbl">Input Tokens</div><div class="stat-val" id="c-in">&mdash;</div></div>
301
+ <div class="stat-card"><div class="stat-lbl">Raw Input</div><div class="stat-val" id="c-in">&mdash;</div></div>
290
302
  <div class="stat-card"><div class="stat-lbl">Output Tokens</div><div class="stat-val" id="c-out">&mdash;</div></div>
291
303
  <div class="stat-card"><div class="stat-lbl">Cache Read</div><div class="stat-val" id="c-cr">&mdash;</div></div>
292
304
  <div class="stat-card"><div class="stat-lbl">Cache Write</div><div class="stat-val" id="c-cw">&mdash;</div></div>
@@ -328,7 +340,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
328
340
  <div class="tbl-wrap">
329
341
  <table>
330
342
  <thead>
331
- <tr><th>Time</th><th>Model</th><th>Project</th><th>Description</th><th>Input</th><th>Output</th><th>Cache R/W</th><th>Cost</th></tr>
343
+ <tr><th>Time</th><th>Model</th><th>Project</th><th>Description</th><th>Raw Input</th><th>Output</th><th>Cache R/W</th><th>Cost</th></tr>
332
344
  </thead>
333
345
  <tbody id="tbody">
334
346
  <tr><td colspan="8"><div class="empty-cell"><div class="empty-icon">&#x25CE;</div><div class="empty-text">Waiting for first log_usage call&hellip;</div></div></td></tr>
@@ -341,6 +353,48 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
341
353
  </div>
342
354
  </div>
343
355
 
356
+ <div class="faq">
357
+ <div class="faq-title">FAQ &mdash; Understanding Your Token Usage</div>
358
+ <details>
359
+ <summary>What do the columns mean?</summary>
360
+ <div class="faq-body">
361
+ <table>
362
+ <tr><th>Column</th><th>Meaning</th></tr>
363
+ <tr><td><code>Raw Input</code></td><td>Non-cached input tokens &mdash; only the new tokens sent to the API that weren't in any cache. Often very small because Claude Code caches aggressively.</td></tr>
364
+ <tr><td><code>Output</code></td><td>Tokens generated by Claude in its response.</td></tr>
365
+ <tr><td><code>Cache R/W</code></td><td><b>R</b> = cache read (reused from previous turns), <b>W</b> = cache write (newly cached). These represent your conversation context, system prompt, and CLAUDE.md being efficiently reused.</td></tr>
366
+ <tr><td><code>Cost</code></td><td>Total cost for this call, calculated from all four token types at their respective rates.</td></tr>
367
+ </table>
368
+ </div>
369
+ </details>
370
+ <details>
371
+ <summary>Why is Raw Input so low (e.g. 6 tokens)?</summary>
372
+ <div class="faq-body">
373
+ Claude Code uses <b>prompt caching</b>. Your system prompt, CLAUDE.md, tool definitions, and conversation history are all cached. On each turn, only the brand-new user message is "raw input" &mdash; everything else is a cheap cache read. This is normal and saves you money.
374
+ </div>
375
+ </details>
376
+ <details>
377
+ <summary>How is cost calculated?</summary>
378
+ <div class="faq-body">
379
+ Each token type has a different rate per million tokens:
380
+ <table>
381
+ <tr><th>Token Type</th><th>Sonnet</th><th>Opus</th></tr>
382
+ <tr><td>Raw Input</td><td>$3.00/M</td><td>$15.00/M</td></tr>
383
+ <tr><td>Cache Write</td><td>$3.75/M</td><td>$18.75/M</td></tr>
384
+ <tr><td>Cache Read</td><td>$0.30/M</td><td>$1.50/M</td></tr>
385
+ <tr><td>Output</td><td>$15.00/M</td><td>$75.00/M</td></tr>
386
+ </table>
387
+ <b>Cost</b> = (Raw Input &times; rate) + (Cache Write &times; rate) + (Cache Read &times; rate) + (Output &times; rate)<br>
388
+ Cache reads are 10x cheaper than raw input &mdash; caching saves significant cost.
389
+ </div>
390
+ </details>
391
+ <details>
392
+ <summary>What is the total context size per turn?</summary>
393
+ <div class="faq-body">
394
+ Total context = <code>Raw Input + Cache Read + Cache Write</code>. For example, if Raw Input is 6, Cache Read is 60K, and Cache Write is 13K, your total context was ~73K tokens. The breakdown shows that 99.99% was efficiently cached.
395
+ </div>
396
+ </details>
397
+ </div>
344
398
  <div class="footer">Token Counter MCP &middot; Real-time usage tracking</div>
345
399
  </div>
346
400
 
@@ -681,7 +735,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
681
735
  data: {
682
736
  labels: tokenLabels,
683
737
  datasets: [
684
- { label: 'Input', data: tokenData.map(function(d) { return d.input; }), backgroundColor: '#a78bfa', borderRadius: 3 },
738
+ { label: 'Raw Input', data: tokenData.map(function(d) { return d.input; }), backgroundColor: '#a78bfa', borderRadius: 3 },
685
739
  { label: 'Output', data: tokenData.map(function(d) { return d.output; }), backgroundColor: '#38bdf8', borderRadius: 3 },
686
740
  ]
687
741
  },
@@ -813,7 +867,7 @@ export const DASHBOARD_HTML = `<!DOCTYPE html>
813
867
  '<div class="proj-bar-wrap"><div class="proj-bar tier-' + tier + '" style="width:' + barPct + '%"></div></div>' +
814
868
  '<div class="proj-detail">' +
815
869
  '<div class="proj-detail-grid">' +
816
- '<div><div class="proj-detail-label">Input Tokens</div><div class="proj-detail-value">' + fmt(g.totalInputTokens) + '</div></div>' +
870
+ '<div><div class="proj-detail-label">Raw Input</div><div class="proj-detail-value">' + fmt(g.totalInputTokens) + '</div></div>' +
817
871
  '<div><div class="proj-detail-label">Output Tokens</div><div class="proj-detail-value">' + fmt(g.totalOutputTokens) + '</div></div>' +
818
872
  '<div><div class="proj-detail-label">Total Cost</div><div class="proj-detail-value" style="color:var(--purple)">' + fmtCost(g.totalCost) + '</div></div>' +
819
873
  '<div><div class="proj-detail-label">Last Active</div><div class="proj-detail-value">' + (g.lastActiveAt ? dtFmt(g.lastActiveAt) : '\\u2014') + '</div></div>' +
@@ -1 +1 @@
1
- {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA,mBAAmB;AACnB,0FAA0F;AAE1F,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QA66BtB,CAAC"}
1
+ {"version":3,"file":"dashboard.js","sourceRoot":"","sources":["../src/dashboard.ts"],"names":[],"mappings":"AAAA,mBAAmB;AACnB,0FAA0F;AAE1F,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;QAm+BtB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AA2MA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CA2B9C"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAwMA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CA2B9C"}
package/dist/setup.js CHANGED
@@ -74,14 +74,12 @@ function writeStopHookBash() {
74
74
  "try:",
75
75
  ' with open(offset_file, "w") as f: f.write(str(len(lines)))',
76
76
  "except: pass",
77
- "total_input = input_tokens + cache_create + cache_read",
78
- "if total_input > 0 or output_tokens > 0:",
77
+ "if input_tokens > 0 or cache_create > 0 or cache_read > 0 or output_tokens > 0:",
79
78
  " print(json.dumps({",
80
- ' "input_tokens": total_input,',
79
+ ' "input_tokens": input_tokens,',
81
80
  ' "output_tokens": output_tokens,',
82
81
  ' "cache_creation_input_tokens": cache_create,',
83
82
  ' "cache_read_input_tokens": cache_read,',
84
- ' "raw_input_tokens": input_tokens,',
85
83
  ' "model": model or "claude-sonnet-4-6",',
86
84
  " }))",
87
85
  "PYEOF",
@@ -129,14 +127,13 @@ function writeStopHookPowershell() {
129
127
  " $outputTok += [int]($u.output_tokens -as [int])",
130
128
  " }",
131
129
  " $allLines.Count.ToString() | Set-Content -Path $offsetFile -Encoding UTF8 -ErrorAction SilentlyContinue",
132
- " $totalInput = $inputTok + $cacheCreate + $cacheRead",
133
- " if ($totalInput -gt 0 -or $outputTok -gt 0) {",
130
+ " if ($inputTok -gt 0 -or $cacheCreate -gt 0 -or $cacheRead -gt 0 -or $outputTok -gt 0) {",
134
131
  " if (-not $model) { $model = 'claude-sonnet-4-6' }",
135
132
  ' $portFile = Join-Path $env:USERPROFILE ".claude\\token-counter\\dashboard-port.txt"',
136
133
  ' $dashPort = if (Test-Path $portFile) { (Get-Content $portFile -Raw).Trim() } else { "8899" }',
137
134
  ' $projDir = Split-Path (Split-Path $transcript) -Leaf',
138
135
  " $projectPath = ($projDir -replace '^-','/' -replace '-','/')",
139
- ' $body = "{`"input_tokens`":$totalInput,`"output_tokens`":$outputTok,`"cache_creation_input_tokens`":$cacheCreate,`"cache_read_input_tokens`":$cacheRead,`"raw_input_tokens`":$inputTok,`"model`":`"$model`",`"description`":`"auto`",`"project`":`"$projectPath`"}"',
136
+ ' $body = "{`"input_tokens`":$inputTok,`"output_tokens`":$outputTok,`"cache_creation_input_tokens`":$cacheCreate,`"cache_read_input_tokens`":$cacheRead,`"model`":`"$model`",`"description`":`"auto`",`"project`":`"$projectPath`"}"',
140
137
  ' Invoke-RestMethod -Method Post -Uri "http://localhost:$dashPort/log" -ContentType "application/json" -Body $body -ErrorAction SilentlyContinue | Out-Null',
141
138
  " }",
142
139
  " } catch {}",
package/dist/setup.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "token-counter-mcp",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "MCP server that accurately counts and tracks every Claude Code token — input, output, cache, and planning",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",