cchubber 0.5.4 → 0.5.5

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 CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "cchubber",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "What you spent. Why you spent it. Is that normal. — Claude Code usage diagnosis with beautiful HTML reports.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "cchubber": "./src/cli/index.js"
8
8
  },
9
9
  "scripts": {
10
- "start": "node src/cli/index.js"
10
+ "start": "node src/cli/index.js",
11
+ "test": "node test/smoke.js",
12
+ "release": "bash release.sh"
11
13
  },
12
14
  "keywords": [
13
15
  "claude",
@@ -1064,6 +1064,7 @@ ${cacheHealth.totalCacheBreaks > 0 ? `
1064
1064
  // Community leaderboard — stats fetched at generation time, embedded as JSON
1065
1065
  var MY_RATIO = ${cacheHealth.efficiencyRatio || 0};
1066
1066
  var MY_GRADE = '${cacheHealth.grade?.letter || '?'}';
1067
+ var MY_COST = '${(() => { const c = totalCost; return c<10?'<10':c<50?'10-50':c<200?'50-200':c<500?'200-500':c<1000?'500-1K':c<5000?'1K-5K':'5K+'; })()}';
1067
1068
  var gradeColors = {A:'#10b981',B:'#22d3ee',C:'#f59e0b',D:'#f97316',F:'#ef4444'};
1068
1069
  var stats = ${JSON.stringify(report.communityStats || null)};
1069
1070
 
@@ -1125,20 +1126,24 @@ ${cacheHealth.totalCacheBreaks > 0 ? `
1125
1126
  });
1126
1127
 
1127
1128
  // Leaderboard table — re-graded, with user's position injected
1129
+ // Filter: minimum activity to qualify (exclude <$10 and $10-50 users — too little data for meaningful grade)
1130
+ var validCosts = {'50-200':1,'200-500':1,'500-1K':1,'1K-5K':1,'5K+':1};
1128
1131
  var tbody = document.getElementById('leaderboard-body');
1129
- var sorted = recent.filter(function(r){return r.ratio}).sort(function(a,b){return (a.ratio||9999)-(b.ratio||9999)});
1132
+ var sorted = recent.filter(function(r){return r.ratio && (validCosts[r.cost] || r.cost===MY_COST)}).sort(function(a,b){return (a.ratio||9999)-(b.ratio||9999)});
1130
1133
 
1131
- // Find where the user would rank
1134
+ // Insert user's own entry into the sorted list at the right position
1135
+ var myEntry = {ratio:MY_RATIO, grade:MY_GRADE, cost:MY_COST, opus:${report.modelRouting?.opusPct ?? 'null'}, country:'You', os:'${process.platform}', isMe:true};
1132
1136
  var myRank = sorted.findIndex(function(e){return (e.ratio||0) >= MY_RATIO});
1133
1137
  if(myRank < 0) myRank = sorted.length;
1138
+ sorted.splice(myRank, 0, myEntry);
1134
1139
 
1135
1140
  // Show top 10 + user's position (if not in top 10)
1136
1141
  var showIndices = [];
1137
1142
  for(var si=0;si<Math.min(10,sorted.length);si++) showIndices.push(si);
1138
1143
  var userInTop = myRank < 10;
1139
- if(!userInTop && myRank < sorted.length){
1144
+ if(!userInTop){
1140
1145
  showIndices.push(-1); // separator
1141
- if(myRank > 0) showIndices.push(myRank-1);
1146
+ if(myRank > 1) showIndices.push(myRank-1);
1142
1147
  showIndices.push(myRank);
1143
1148
  if(myRank+1 < sorted.length) showIndices.push(myRank+1);
1144
1149
  }
@@ -1147,28 +1152,28 @@ ${cacheHealth.totalCacheBreaks > 0 ? `
1147
1152
  for(var si2=0;si2<showIndices.length;si2++){
1148
1153
  var idx = showIndices[si2];
1149
1154
  if(idx === -1){
1150
- html += '<tr><td colspan="6" class="px-8 py-1 text-center text-[10px] text-[#908fa0]">···</td></tr>';
1155
+ html += '<tr><td colspan="6" class="px-8 py-1 text-center text-[10px] text-[#908fa0]">...</td></tr>';
1151
1156
  continue;
1152
1157
  }
1153
1158
  var entry = sorted[idx];
1154
1159
  if(!entry) continue;
1155
- var g = getGrade(entry);
1156
- var isMe = idx === myRank;
1160
+ var g = entry.isMe ? MY_GRADE : getGrade(entry);
1161
+ var isMe = entry.isMe;
1157
1162
  var rowStyle = isMe ? 'background:rgba(192,193,255,0.08);border-left:3px solid #c0c1ff;' : '';
1158
1163
  html += '<tr style="border-bottom:1px solid rgba(70,69,84,0.1);'+rowStyle+'">';
1159
1164
  html += '<td class="px-8 py-3 text-sm font-mono '+(isMe?'text-[#c0c1ff] font-bold':'text-[#908fa0]')+'">#'+(idx+1)+(isMe?' ← you':'')+'</td>';
1160
1165
  html += '<td class="px-4 py-3 text-sm font-bold text-center" style="color:'+gradeColors[g]+'">'+g+'</td>';
1161
1166
  html += '<td class="px-4 py-3 text-sm font-mono text-[#c7c4d7] text-right">'+(entry.ratio||'?')+':1</td>';
1162
1167
  html += '<td class="px-4 py-3 text-sm font-mono text-[#908fa0]">'+(entry.cost||'?')+'</td>';
1163
- html += '<td class="px-4 py-3 text-sm font-mono text-[#c7c4d7] text-right">'+(entry.opus||'?')+'%</td>';
1164
- html += '<td class="px-8 py-3 text-sm text-[#908fa0]">'+(entry.country||'?')+'</td>';
1168
+ html += '<td class="px-4 py-3 text-sm font-mono text-[#c7c4d7] text-right">'+(entry.opus!=null?entry.opus:'?')+'%</td>';
1169
+ html += '<td class="px-8 py-3 text-sm text-[#908fa0]">'+(isMe?'You':(entry.country||'?'))+'</td>';
1165
1170
  html += '</tr>';
1166
1171
  }
1167
1172
  tbody.innerHTML = html;
1168
1173
 
1169
1174
  // Update percentile text with rank
1170
1175
  document.getElementById('community-percentile').innerHTML =
1171
- "You are <strong style=\\"color:#c0c1ff\\">#"+(myRank+1)+" of "+sorted.length+"</strong> users by cache efficiency. Better than <strong style=\\"color:#c0c1ff\\">"+pctile+"%</strong>";
1176
+ "You are <strong style=\\"color:#c0c1ff\\">#"+(myRank+1)+" of "+(sorted.length)+"</strong> users by cache efficiency. Better than <strong style=\\"color:#c0c1ff\\">"+pctile+"%</strong>";
1172
1177
  })(stats);
1173
1178
  })();
1174
1179
  </script>