cchubber 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/analyzers/cache-health.js +2 -2
- package/src/cli/index.js +7 -10
- package/src/renderers/html-report.js +39 -19
package/package.json
CHANGED
|
@@ -83,7 +83,7 @@ function calculateGrade(allTimeRatio, breaks, days, dailyFromJSONL, cacheHitRate
|
|
|
83
83
|
// Based on token-optimizer's methodology (multi-signal composite)
|
|
84
84
|
// but adapted for post-hoc analysis with the data CC Hubber has.
|
|
85
85
|
|
|
86
|
-
// --- Signal 1: Cache hit rate (
|
|
86
|
+
// --- Signal 1: Cache hit rate (15%) ---
|
|
87
87
|
// What % of input tokens came from cache. Higher = better.
|
|
88
88
|
// Thresholds from token-optimizer: >=80% = 100, >=60% = 80, >=40% = 55
|
|
89
89
|
let hitRateScore;
|
|
@@ -93,7 +93,7 @@ function calculateGrade(allTimeRatio, breaks, days, dailyFromJSONL, cacheHitRate
|
|
|
93
93
|
else if (cacheHitRate >= 40) hitRateScore = 40;
|
|
94
94
|
else hitRateScore = 15;
|
|
95
95
|
|
|
96
|
-
// --- Signal 2: Efficiency ratio (
|
|
96
|
+
// --- Signal 2: Efficiency ratio (40%) ---
|
|
97
97
|
// Cache reads per output token. Measures how much redundant data
|
|
98
98
|
// is re-read per unit of work. Lower = more efficient.
|
|
99
99
|
// Calibrated against 33 real users (median ~680).
|
package/src/cli/index.js
CHANGED
|
@@ -42,7 +42,8 @@ const flags = {
|
|
|
42
42
|
})(),
|
|
43
43
|
days: (() => {
|
|
44
44
|
const idx = args.indexOf('--days') !== -1 ? args.indexOf('--days') : args.indexOf('-d');
|
|
45
|
-
|
|
45
|
+
const val = idx !== -1 && args[idx + 1] ? parseInt(args[idx + 1], 10) : 30;
|
|
46
|
+
return isNaN(val) ? 30 : val;
|
|
46
47
|
})(),
|
|
47
48
|
};
|
|
48
49
|
|
|
@@ -140,19 +141,15 @@ async function main() {
|
|
|
140
141
|
if (modelRouting.available) console.log(` ✓ Model routing: ${modelRouting.opusPct}% Opus, ${modelRouting.sonnetPct}% Sonnet`);
|
|
141
142
|
console.log(` ✓ ${projectBreakdown.length} projects detected`);
|
|
142
143
|
|
|
143
|
-
// Fetch community stats for leaderboard (non-blocking, fails silently)
|
|
144
|
+
// Fetch community stats for leaderboard (non-blocking, 5s timeout, fails silently)
|
|
144
145
|
let communityStats = null;
|
|
145
146
|
try {
|
|
146
|
-
const
|
|
147
|
+
const controller = new AbortController();
|
|
148
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
149
|
+
const res = await fetch('https://cchubber-telemetry.asmirkhan087.workers.dev/stats-public', { signal: controller.signal });
|
|
150
|
+
clearTimeout(timeout);
|
|
147
151
|
if (res.ok) communityStats = await res.json();
|
|
148
152
|
} catch {}
|
|
149
|
-
// Fallback: try the private key if public isn't configured
|
|
150
|
-
if (!communityStats) {
|
|
151
|
-
try {
|
|
152
|
-
const res = await fetch('https://cchubber-telemetry.asmirkhan087.workers.dev/stats?key=cchubber_x9k_private');
|
|
153
|
-
if (res.ok) communityStats = await res.json();
|
|
154
|
-
} catch {}
|
|
155
|
-
}
|
|
156
153
|
if (communityStats) console.log(` ✓ Community data: ${communityStats.totalReports} users from ${Object.keys(communityStats.countries || {}).length} countries`);
|
|
157
154
|
else console.log(' ○ Community data unavailable (offline)');
|
|
158
155
|
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
function esc(s) {
|
|
2
|
+
if (!s) return '';
|
|
3
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''');
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
export function renderHTML(report) {
|
|
2
7
|
const { costAnalysis, cacheHealth, anomalies, inflection, sessionIntel, modelRouting, projectBreakdown, claudeMdStack, oauthUsage, recommendations, generatedAt } = report;
|
|
3
8
|
|
|
@@ -474,13 +479,13 @@ ${inflection && inflection.multiplier >= 1.5 ? `
|
|
|
474
479
|
return `<div class="p-4 bg-[#0d0e0f] rounded-r-lg flex items-start gap-4" style="border-left:3px solid ${sev.border}">
|
|
475
480
|
<div class="flex-1 min-w-0">
|
|
476
481
|
<div class="flex items-start justify-between gap-4">
|
|
477
|
-
<p class="text-[13px] font-semibold text-[#e3e2e3]">${r.title}</p>
|
|
482
|
+
<p class="text-[13px] font-semibold text-[#e3e2e3]">${esc(r.title)}</p>
|
|
478
483
|
<div class="flex items-center gap-2 shrink-0">
|
|
479
484
|
${r.savings ? `<span class="text-[10px] font-mono px-2 py-0.5 rounded" style="background:${sev.border}18;color:${sev.text}">${r.savings}</span>` : ''}
|
|
480
485
|
${r.severity !== 'positive' ? `<button data-clip="${b64}" onclick="var t=atob(this.dataset.clip);navigator.clipboard.writeText(t);this.textContent='Copied!';var b=this;setTimeout(function(){b.textContent='Fix with Claude'},1500)" class="text-[10px] font-mono px-2 py-0.5 rounded border border-[rgba(70,69,84,0.3)] text-[#908fa0] cursor-pointer hover:text-[#e3e2e3] hover:border-[rgba(70,69,84,0.6)] transition-colors">Fix with Claude</button>` : ''}
|
|
481
486
|
</div>
|
|
482
487
|
</div>
|
|
483
|
-
<p class="text-[11px] text-[#908fa0] mt-1 leading-relaxed">${r.action}</p>
|
|
488
|
+
<p class="text-[11px] text-[#908fa0] mt-1 leading-relaxed">${esc(r.action)}</p>
|
|
484
489
|
</div>
|
|
485
490
|
</div>`;
|
|
486
491
|
}).join('')}
|
|
@@ -619,7 +624,7 @@ ${anomalies.hasAnomalies ? `
|
|
|
619
624
|
</thead>
|
|
620
625
|
<tbody class="divide-y divide-[rgba(70,69,84,0.15)]">
|
|
621
626
|
${claudeMdStack.globalSections.map(s => `<tr class="tbl-row">
|
|
622
|
-
<td class="px-8 py-3 text-sm text-[#e3e2e3]">${s.name}</td>
|
|
627
|
+
<td class="px-8 py-3 text-sm text-[#e3e2e3]">${esc(s.name)}</td>
|
|
623
628
|
<td class="px-8 py-3 font-mono text-sm text-[#c7c4d7] text-right">${s.lines}</td>
|
|
624
629
|
<td class="px-8 py-3 font-mono text-sm text-[#c7c4d7] text-right">${s.tokens.toLocaleString()}</td>
|
|
625
630
|
</tr>`).join('')}
|
|
@@ -1066,25 +1071,44 @@ ${cacheHealth.totalCacheBreaks > 0 ? `
|
|
|
1066
1071
|
if(!sec || !stats.totalReports) return;
|
|
1067
1072
|
sec.style.display = '';
|
|
1068
1073
|
|
|
1069
|
-
//
|
|
1070
|
-
|
|
1071
|
-
|
|
1074
|
+
// Re-grade function — applies current grading logic to stored ratios
|
|
1075
|
+
// so old telemetry entries get accurate grades, not the inflated v0.4 grades
|
|
1076
|
+
function regrade(ratio){
|
|
1077
|
+
// Simplified: ratio signal dominates (40%), assume stable trend (70), good hit rate (85), no breaks (100)
|
|
1078
|
+
var rs;
|
|
1079
|
+
if(ratio<=200)rs=100;else if(ratio<=350)rs=85;else if(ratio<=500)rs=70;
|
|
1080
|
+
else if(ratio<=650)rs=55;else if(ratio<=800)rs=42;else if(ratio<=1000)rs=30;
|
|
1081
|
+
else if(ratio<=1500)rs=18;else if(ratio<=2000)rs=10;else rs=3;
|
|
1082
|
+
var composite=Math.round(85*0.15+rs*0.40+70*0.30+100*0.15);
|
|
1083
|
+
if(rs<=5)composite=Math.min(composite,38);
|
|
1084
|
+
if(composite>=75)return'A';if(composite>=60)return'B';if(composite>=45)return'C';if(composite>=30)return'D';return'F';
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// Count + geographic spread (don't show raw user count — looks small early on)
|
|
1088
|
+
var countries = stats.countries ? Object.keys(stats.countries) : [];
|
|
1089
|
+
document.getElementById('community-count').textContent = countries.length + ' countries';
|
|
1072
1090
|
|
|
1073
1091
|
// Calculate percentile from recent entries
|
|
1074
1092
|
var recent = stats.recent || [];
|
|
1075
1093
|
var ratios = recent.map(function(r){return r.ratio||9999}).sort(function(a,b){return a-b});
|
|
1076
1094
|
var betterThan = ratios.filter(function(r){return r > MY_RATIO}).length;
|
|
1077
|
-
var pctile =
|
|
1095
|
+
var pctile = ratios.length > 0 ? Math.round(betterThan / ratios.length * 100) : 0;
|
|
1078
1096
|
document.getElementById('community-percentile').innerHTML =
|
|
1079
1097
|
'Your cache ratio of <strong style="color:#e3e2e3">' + MY_RATIO + ':1</strong> is better than <strong style="color:#c0c1ff">' + pctile + '%</strong> of CC Hubber users';
|
|
1080
1098
|
|
|
1081
|
-
// Grade distribution
|
|
1082
|
-
|
|
1083
|
-
|
|
1099
|
+
// Grade distribution — use stored grade for v0.5+ entries, regrade older ones
|
|
1100
|
+
function getGrade(entry){
|
|
1101
|
+
var v = entry.v || entry.version || '0.3.3';
|
|
1102
|
+
var parts = v.split('.'); var isNew = (parseInt(parts[0]||0)>0) || (parseInt(parts[1]||0)>=5);
|
|
1103
|
+
return (isNew && entry.grade) ? entry.grade : (entry.ratio ? regrade(entry.ratio) : (entry.grade || 'C'));
|
|
1104
|
+
}
|
|
1105
|
+
var regraded = {A:0,B:0,C:0,D:0,F:0};
|
|
1106
|
+
recent.forEach(function(r){regraded[getGrade(r)]++});
|
|
1107
|
+
var gTotal = Object.values(regraded).reduce(function(s,v){return s+v},0);
|
|
1084
1108
|
var bar = document.getElementById('grade-dist-bar');
|
|
1085
1109
|
var labels = document.getElementById('grade-dist-labels');
|
|
1086
1110
|
['A','B','C','D','F'].forEach(function(g){
|
|
1087
|
-
var count =
|
|
1111
|
+
var count = regraded[g] || 0;
|
|
1088
1112
|
var pct = gTotal > 0 ? (count/gTotal*100) : 0;
|
|
1089
1113
|
if(pct > 0){
|
|
1090
1114
|
var seg = document.createElement('div');
|
|
@@ -1098,22 +1122,18 @@ ${cacheHealth.totalCacheBreaks > 0 ? `
|
|
|
1098
1122
|
}
|
|
1099
1123
|
});
|
|
1100
1124
|
|
|
1101
|
-
// Leaderboard table
|
|
1125
|
+
// Leaderboard table — re-graded
|
|
1102
1126
|
var tbody = document.getElementById('leaderboard-body');
|
|
1103
1127
|
var sorted = recent.filter(function(r){return r.ratio}).sort(function(a,b){return (a.ratio||9999)-(b.ratio||9999)});
|
|
1104
|
-
var myRank = -1;
|
|
1105
|
-
sorted.forEach(function(entry, i){
|
|
1106
|
-
if(myRank<0 && (entry.ratio||9999) >= MY_RATIO) myRank = i;
|
|
1107
|
-
});
|
|
1108
|
-
if(myRank<0) myRank = sorted.length;
|
|
1109
1128
|
|
|
1110
1129
|
var html = '';
|
|
1111
1130
|
sorted.forEach(function(entry, i){
|
|
1112
|
-
var
|
|
1131
|
+
var g = getGrade(entry);
|
|
1132
|
+
var isMe = Math.abs((entry.ratio||0) - MY_RATIO) < 50;
|
|
1113
1133
|
var rowStyle = isMe ? 'background:rgba(192,193,255,0.06);border-left:2px solid #c0c1ff;' : '';
|
|
1114
1134
|
html += '<tr style="border-bottom:1px solid rgba(70,69,84,0.1);'+rowStyle+'">';
|
|
1115
1135
|
html += '<td class="px-8 py-3 text-sm font-mono text-[#908fa0]">#'+(i+1)+'</td>';
|
|
1116
|
-
html += '<td class="px-4 py-3 text-sm font-bold text-center" style="color:'+gradeColors[
|
|
1136
|
+
html += '<td class="px-4 py-3 text-sm font-bold text-center" style="color:'+gradeColors[g]+'">'+g+'</td>';
|
|
1117
1137
|
html += '<td class="px-4 py-3 text-sm font-mono text-[#c7c4d7] text-right">'+(entry.ratio||'?')+':1</td>';
|
|
1118
1138
|
html += '<td class="px-4 py-3 text-sm font-mono text-[#908fa0]">'+(entry.cost||'?')+'</td>';
|
|
1119
1139
|
html += '<td class="px-4 py-3 text-sm font-mono text-[#c7c4d7] text-right">'+(entry.opus||'?')+'%</td>';
|