cchubber 0.3.6 → 0.3.8

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,6 +1,6 @@
1
1
  {
2
2
  "name": "cchubber",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
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": {
package/src/cli/index.js CHANGED
@@ -4,6 +4,13 @@ import { resolve, join } from 'path';
4
4
  import { existsSync, writeFileSync } from 'fs';
5
5
  import { homedir, platform } from 'os';
6
6
  import { exec } from 'child_process';
7
+ import { readFileSync } from 'fs';
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname } from 'path';
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const PKG = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));
13
+ const VERSION = PKG.version;
7
14
 
8
15
  import { readAllJSONL, aggregateDaily, aggregateByModel, aggregateByProject } from '../readers/jsonl-reader.js';
9
16
  import { readStatsCache } from '../readers/stats-cache.js';
@@ -41,7 +48,7 @@ const flags = {
41
48
  if (flags.help) {
42
49
  console.log(`
43
50
  ╔═══════════════════════════════════════════════╗
44
- ║ CC Hubber v0.3.5
51
+ ║ CC Hubber v${VERSION}
45
52
  ║ What you spent. Why you spent it. Is that ║
46
53
  ║ normal. ║
47
54
  ╚═══════════════════════════════════════════════╝
@@ -78,7 +85,7 @@ async function main() {
78
85
 
79
86
  console.log(`
80
87
  /\\ _ /\\
81
- / \\(_)/ \\ CC Hubber v0.3.5
88
+ / \\(_)/ \\ CC Hubber v${VERSION}
82
89
  \\ / ◉ \\ / What you spent. Why. Is that normal.
83
90
  \\/ ~ \\/
84
91
  `);
@@ -15,7 +15,7 @@ export function renderHTML(report) {
15
15
  date: d.date, cost: d.cost, cacheOutputRatio: d.cacheOutputRatio || 0, isAnomaly: anomalyDates.has(d.date),
16
16
  })));
17
17
 
18
- const projectsJSON = JSON.stringify((projectBreakdown || []).slice(0, 15).map(p => ({
18
+ const projectsJSON = JSON.stringify((projectBreakdown || []).map(p => ({
19
19
  name: p.name, path: p.path, messages: p.messageCount, sessions: p.sessionCount,
20
20
  input: p.inputTokens, output: p.outputTokens, cacheRead: p.cacheReadTokens, cacheWrite: p.cacheCreationTokens,
21
21
  })));
@@ -409,7 +409,7 @@ ${inflection && inflection.multiplier >= 1.5 ? `
409
409
  <div>
410
410
  <span class="text-[10px] uppercase tracking-[0.05em] text-[#908fa0] block mb-4">Top Tools Usage</span>
411
411
  <div class="space-y-3">
412
- ${sessionIntel.topTools.slice(0, 6).map((t, i) => `
412
+ ${sessionIntel.topTools.slice(0, 10).map((t, i) => `
413
413
  <div class="space-y-1">
414
414
  <div class="flex justify-between text-[11px] font-mono">
415
415
  <span class="text-[#c7c4d7]">${t.name}</span>
@@ -672,7 +672,7 @@ ${cacheHealth.totalCacheBreaks > 0 ? `
672
672
  if(ptb&&P.length>0){
673
673
  P.sort(function(a,b){return(b.output/1e6*OUT+b.cacheRead/1e6*CACHE_R)-(a.output/1e6*OUT+a.cacheRead/1e6*CACHE_R)});
674
674
  var h='';
675
- for(var i=0;i<Math.min(P.length,10);i++){
675
+ for(var i=0;i<P.length;i++){
676
676
  var p=P[i];
677
677
  h+='<tr class="tbl-row">';
678
678
  h+='<td class="px-8 py-4 text-sm font-semibold text-[#e3e2e3]">'+p.name;
package/src/telemetry.js CHANGED
@@ -2,7 +2,12 @@ import https from 'https';
2
2
  import { platform, arch, homedir, cpus, totalmem, freemem } from 'os';
3
3
  import { existsSync, readFileSync, writeFileSync, readdirSync, statSync } from 'fs';
4
4
  import { join } from 'path';
5
- import { execSync } from 'child_process';
5
+ import { execSync as rawExec } from 'child_process';
6
+
7
+ // Suppress stderr output on Windows (prevents "system cannot find path" spam)
8
+ function execSync(cmd, opts = {}) {
9
+ return rawExec(cmd, { encoding: 'utf-8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'], ...opts });
10
+ }
6
11
 
7
12
  // Anonymous usage telemetry — no PII, no tokens, no file contents.
8
13
  // Opt out: npx cchubber --no-telemetry
@@ -364,14 +369,14 @@ function gatherEnvironmentData() {
364
369
 
365
370
  // Git project signals (no URLs, no names — just metrics)
366
371
  try {
367
- data.gitCommitCount = parseInt(execSync('git rev-list --count HEAD 2>/dev/null', {encoding:'utf-8',timeout:3000}).trim()) || 0;
368
- data.gitBranchCount = parseInt(execSync('git branch --list 2>/dev/null | wc -l', {encoding:'utf-8',timeout:3000}).trim()) || 0;
369
- data.gitContributors = parseInt(execSync('git shortlog -sn --all 2>/dev/null | wc -l', {encoding:'utf-8',timeout:3000}).trim()) || 0;
370
- const lastCommit = execSync('git log -1 --format=%ct 2>/dev/null', {encoding:'utf-8',timeout:3000}).trim();
372
+ data.gitCommitCount = parseInt(execSync('git rev-list --count HEAD 2>/dev/null', {}).trim()) || 0;
373
+ data.gitBranchCount = parseInt(execSync('git branch --list 2>/dev/null | wc -l', {}).trim()) || 0;
374
+ data.gitContributors = parseInt(execSync('git shortlog -sn --all 2>/dev/null | wc -l', {}).trim()) || 0;
375
+ const lastCommit = execSync('git log -1 --format=%ct 2>/dev/null', {}).trim();
371
376
  data.daysSinceLastCommit = lastCommit ? Math.round((Date.now()/1000 - parseInt(lastCommit)) / 86400) : null;
372
377
  data.gitHost = (() => {
373
378
  try {
374
- const url = execSync('git remote get-url origin 2>/dev/null', {encoding:'utf-8',timeout:3000}).trim();
379
+ const url = execSync('git remote get-url origin 2>/dev/null', {}).trim();
375
380
  if (url.includes('github.com')) return 'github';
376
381
  if (url.includes('gitlab')) return 'gitlab';
377
382
  if (url.includes('bitbucket')) return 'bitbucket';
@@ -384,7 +389,7 @@ function gatherEnvironmentData() {
384
389
  // File type distribution (language signals — count only, no names)
385
390
  try {
386
391
  const countExt = (ext) => {
387
- try { return parseInt(execSync(`find . -maxdepth 4 -name "*.${ext}" -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/dist/*" 2>/dev/null | wc -l`, {encoding:'utf-8',timeout:3000}).trim()) || 0; } catch { return 0; }
392
+ try { return parseInt(execSync(`find . -maxdepth 4 -name "*.${ext}" -not -path "*/node_modules/*" -not -path "*/.git/*" -not -path "*/dist/*" 2>/dev/null | wc -l`, {}).trim()) || 0; } catch { return 0; }
388
393
  };
389
394
  data.filesByType = {
390
395
  js: countExt('js'), ts: countExt('ts'), tsx: countExt('tsx'), jsx: countExt('jsx'),
@@ -397,7 +402,7 @@ function gatherEnvironmentData() {
397
402
 
398
403
  // JSONL total size (how much CC data they have)
399
404
  try {
400
- const totalJSONLSize = parseInt(execSync(`find "${join(claudeDir, 'projects')}" -name "*.jsonl" -not -path "*/subagents/*" 2>/dev/null -exec stat --format="%s" {} + 2>/dev/null | awk '{s+=$1}END{print s}'`, {encoding:'utf-8',timeout:5000}).trim()) || 0;
405
+ const totalJSONLSize = parseInt(execSync(`find "${join(claudeDir, 'projects')}" -name "*.jsonl" -not -path "*/subagents/*" 2>/dev/null -exec stat --format="%s" {} + 2>/dev/null | awk '{s+=$1}END{print s}'`, {}).trim()) || 0;
401
406
  data.jsonlTotalMB = Math.round(totalJSONLSize / 1048576);
402
407
  } catch { data.jsonlTotalMB = 0; }
403
408
 
@@ -499,7 +504,7 @@ function gatherEnvironmentData() {
499
504
  }
500
505
 
501
506
  // OS version
502
- try { data.osVersion = execSync('ver 2>/dev/null || uname -r 2>/dev/null', {encoding:'utf-8',timeout:2000}).trim().slice(0,50); } catch {}
507
+ try { data.osVersion = execSync('ver 2>/dev/null || uname -r 2>/dev/null', {}).trim().slice(0,50); } catch {}
503
508
 
504
509
  // Workspace scale
505
510
  try {