cchubber 0.5.1 → 0.5.3
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 +69 -23
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,29 +1122,51 @@ ${cacheHealth.totalCacheBreaks > 0 ? `
|
|
|
1098
1122
|
}
|
|
1099
1123
|
});
|
|
1100
1124
|
|
|
1101
|
-
// Leaderboard table
|
|
1125
|
+
// Leaderboard table — re-graded, with user's position injected
|
|
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
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1128
|
+
|
|
1129
|
+
// Find where the user would rank
|
|
1130
|
+
var myRank = sorted.findIndex(function(e){return (e.ratio||0) >= MY_RATIO});
|
|
1131
|
+
if(myRank < 0) myRank = sorted.length;
|
|
1132
|
+
|
|
1133
|
+
// Show top 10 + user's position (if not in top 10)
|
|
1134
|
+
var showIndices = [];
|
|
1135
|
+
for(var si=0;si<Math.min(10,sorted.length);si++) showIndices.push(si);
|
|
1136
|
+
var userInTop = myRank < 10;
|
|
1137
|
+
if(!userInTop && myRank < sorted.length){
|
|
1138
|
+
showIndices.push(-1); // separator
|
|
1139
|
+
if(myRank > 0) showIndices.push(myRank-1);
|
|
1140
|
+
showIndices.push(myRank);
|
|
1141
|
+
if(myRank+1 < sorted.length) showIndices.push(myRank+1);
|
|
1142
|
+
}
|
|
1109
1143
|
|
|
1110
1144
|
var html = '';
|
|
1111
|
-
|
|
1112
|
-
var
|
|
1113
|
-
|
|
1145
|
+
for(var si2=0;si2<showIndices.length;si2++){
|
|
1146
|
+
var idx = showIndices[si2];
|
|
1147
|
+
if(idx === -1){
|
|
1148
|
+
html += '<tr><td colspan="6" class="px-8 py-1 text-center text-[10px] text-[#908fa0]">···</td></tr>';
|
|
1149
|
+
continue;
|
|
1150
|
+
}
|
|
1151
|
+
var entry = sorted[idx];
|
|
1152
|
+
if(!entry) continue;
|
|
1153
|
+
var g = getGrade(entry);
|
|
1154
|
+
var isMe = idx === myRank;
|
|
1155
|
+
var rowStyle = isMe ? 'background:rgba(192,193,255,0.08);border-left:3px solid #c0c1ff;' : '';
|
|
1114
1156
|
html += '<tr style="border-bottom:1px solid rgba(70,69,84,0.1);'+rowStyle+'">';
|
|
1115
|
-
html += '<td class="px-8 py-3 text-sm font-mono text-[#908fa0]">#'+(
|
|
1116
|
-
html += '<td class="px-4 py-3 text-sm font-bold text-center" style="color:'+gradeColors[
|
|
1157
|
+
html += '<td class="px-8 py-3 text-sm font-mono '+(isMe?'text-[#c0c1ff] font-bold':'text-[#908fa0]')+'">#'+(idx+1)+(isMe?' ← you':'')+'</td>';
|
|
1158
|
+
html += '<td class="px-4 py-3 text-sm font-bold text-center" style="color:'+gradeColors[g]+'">'+g+'</td>';
|
|
1117
1159
|
html += '<td class="px-4 py-3 text-sm font-mono text-[#c7c4d7] text-right">'+(entry.ratio||'?')+':1</td>';
|
|
1118
1160
|
html += '<td class="px-4 py-3 text-sm font-mono text-[#908fa0]">'+(entry.cost||'?')+'</td>';
|
|
1119
1161
|
html += '<td class="px-4 py-3 text-sm font-mono text-[#c7c4d7] text-right">'+(entry.opus||'?')+'%</td>';
|
|
1120
1162
|
html += '<td class="px-8 py-3 text-sm text-[#908fa0]">'+(entry.country||'?')+'</td>';
|
|
1121
1163
|
html += '</tr>';
|
|
1122
|
-
}
|
|
1164
|
+
}
|
|
1123
1165
|
tbody.innerHTML = html;
|
|
1166
|
+
|
|
1167
|
+
// Update percentile text with rank
|
|
1168
|
+
document.getElementById('community-percentile').innerHTML =
|
|
1169
|
+
'You\'re <strong style="color:#c0c1ff">#'+(myRank+1)+' of '+sorted.length+'</strong> users by cache efficiency. Better than <strong style="color:#c0c1ff">'+pctile+'%</strong>';
|
|
1124
1170
|
})(stats);
|
|
1125
1171
|
})();
|
|
1126
1172
|
</script>
|