ccclub 0.2.65 → 0.2.67

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.
Files changed (2) hide show
  1. package/dist/index.js +151 -124
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command, Option } from "commander";
6
6
  // src/commands/init.ts
7
7
  import { createInterface } from "readline/promises";
8
8
  import { stdin, stdout } from "process";
9
- import chalk3 from "chalk";
9
+ import chalk4 from "chalk";
10
10
  import ora2 from "ora";
11
11
 
12
12
  // src/config.ts
@@ -209,7 +209,7 @@ import { readFile as readFile4, writeFile as writeFile3 } from "fs/promises";
209
209
  import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync } from "fs";
210
210
  import { join as join4 } from "path";
211
211
  import { homedir as homedir4 } from "os";
212
- import chalk from "chalk";
212
+ import chalk2 from "chalk";
213
213
  import ora from "ora";
214
214
 
215
215
  // src/collector.ts
@@ -364,6 +364,33 @@ function aggregateToBlocks(entries, humanTurns = []) {
364
364
  return blocks;
365
365
  }
366
366
 
367
+ // src/fetch-error.ts
368
+ import chalk from "chalk";
369
+ function formatFetchError(err) {
370
+ if (!(err instanceof Error)) return String(err);
371
+ const causes = [err.message];
372
+ let current = err.cause;
373
+ while (current instanceof Error) {
374
+ causes.push(current.message);
375
+ current = current.cause;
376
+ }
377
+ const detail = causes.join(": ");
378
+ const lower = detail.toLowerCase();
379
+ const isSSL = lower.includes("ssl") || lower.includes("tls") || lower.includes("cert") || lower.includes("packet length too long") || lower.includes("protocol version") || lower.includes("err_ssl") || lower.includes("unable to verify") || lower.includes("self.signed") || lower.includes("certificate");
380
+ if (isSSL) {
381
+ const rootCause = causes.length > 1 ? causes[causes.length - 1] : detail;
382
+ return [
383
+ rootCause,
384
+ "",
385
+ chalk.dim(" This looks like a TLS/SSL issue. Try:"),
386
+ chalk.dim(" 1. Update Node.js to the latest LTS (v20.x+ or v22.x)"),
387
+ chalk.dim(" 2. Check if a corporate proxy/firewall is intercepting HTTPS"),
388
+ chalk.dim(" 3. Run with NODE_DEBUG=fetch for more details")
389
+ ].join("\n");
390
+ }
391
+ return causes.length > 1 ? causes[causes.length - 1] : detail;
392
+ }
393
+
367
394
  // src/commands/sync.ts
368
395
  var SYNC_FORMAT_VERSION = "6";
369
396
  function getSyncVersionPath() {
@@ -465,17 +492,17 @@ async function doSync(firstSync = false, silent = false) {
465
492
  const totalCost = blocksToSync.reduce((s, b) => s + b.costUSD, 0);
466
493
  if (spinner) {
467
494
  spinner.succeed(`Synced ${data.synced} blocks`);
468
- log(chalk.dim(` Tokens: ${totalTokens.toLocaleString()} Cost: $${totalCost.toFixed(4)}`));
495
+ log(chalk2.dim(` Tokens: ${totalTokens.toLocaleString()} Cost: $${totalCost.toFixed(4)}`));
469
496
  }
470
497
  } catch (err) {
471
- if (spinner) spinner.fail(`Sync error: ${err instanceof Error ? err.message : err}`);
498
+ if (spinner) spinner.fail(`Sync error: ${formatFetchError(err)}`);
472
499
  if (silent) throw err;
473
500
  }
474
501
  }
475
502
 
476
503
  // src/global-install.ts
477
504
  import { exec } from "child_process";
478
- import chalk2 from "chalk";
505
+ import chalk3 from "chalk";
479
506
  function run(cmd) {
480
507
  return new Promise((resolve) => {
481
508
  exec(cmd, (err, stdout5) => resolve(err ? "" : stdout5.trim()));
@@ -484,13 +511,13 @@ function run(cmd) {
484
511
  async function ensureGlobalInstall() {
485
512
  const globalList = await run("npm list -g ccclub --depth=0");
486
513
  if (globalList.includes("ccclub@")) return;
487
- console.log(chalk2.dim("\n Installing ccclub globally so you can run it directly..."));
514
+ console.log(chalk3.dim("\n Installing ccclub globally so you can run it directly..."));
488
515
  const result = await run("npm install -g ccclub");
489
516
  if (result) {
490
- console.log(chalk2.green(" Done!") + chalk2.dim(" You can now use ") + chalk2.white("ccclub") + chalk2.dim(" directly."));
517
+ console.log(chalk3.green(" Done!") + chalk3.dim(" You can now use ") + chalk3.white("ccclub") + chalk3.dim(" directly."));
491
518
  } else {
492
- console.log(chalk2.dim(" Could not auto-install. Run manually:"));
493
- console.log(chalk2.white(" npm install -g ccclub"));
519
+ console.log(chalk3.dim(" Could not auto-install. Run manually:"));
520
+ console.log(chalk3.white(" npm install -g ccclub"));
494
521
  }
495
522
  }
496
523
 
@@ -498,24 +525,24 @@ async function ensureGlobalInstall() {
498
525
  async function initCommand() {
499
526
  const existing = await loadConfig();
500
527
  if (existing) {
501
- console.log(chalk3.yellow("Already initialized!"));
528
+ console.log(chalk4.yellow("Already initialized!"));
502
529
  console.log(` User: ${existing.displayName}`);
503
530
  console.log(` Groups: ${existing.groups.join(", ") || "(none)"}`);
504
531
  if (!isHookInstalled()) {
505
532
  const hookOk = await installHook();
506
- if (hookOk) console.log(chalk3.green(" Auto-sync hook installed!"));
533
+ if (hookOk) console.log(chalk4.green(" Auto-sync hook installed!"));
507
534
  }
508
- console.log(chalk3.dim('\n Run "ccclub" to see the leaderboard'));
535
+ console.log(chalk4.dim('\n Run "ccclub" to see the leaderboard'));
509
536
  return;
510
537
  }
511
538
  const rl = createInterface({ input: stdin, output: stdout });
512
539
  try {
513
540
  const defaultName = getDefaultDisplayName();
514
- const prompt = defaultName ? chalk3.bold(`Your display name (${defaultName}): `) : chalk3.bold("Your display name: ");
541
+ const prompt = defaultName ? chalk4.bold(`Your display name (${defaultName}): `) : chalk4.bold("Your display name: ");
515
542
  const input = await rl.question(prompt);
516
543
  const displayName = input.trim() || defaultName || "";
517
544
  if (!displayName) {
518
- console.error(chalk3.red("Name cannot be empty"));
545
+ console.error(chalk4.red("Name cannot be empty"));
519
546
  return;
520
547
  }
521
548
  const spinner = ora2("Setting up...").start();
@@ -530,7 +557,7 @@ async function initCommand() {
530
557
  signal: AbortSignal.timeout(15e3)
531
558
  });
532
559
  } catch (err) {
533
- spinner.fail(`Setup failed: ${err instanceof Error ? err.message : err}`);
560
+ spinner.fail(`Setup failed: ${formatFetchError(err)}`);
534
561
  return;
535
562
  }
536
563
  if (!res.ok) {
@@ -549,15 +576,15 @@ async function initCommand() {
549
576
  const hookOk = await installHook();
550
577
  spinner.succeed("ccclub initialized!");
551
578
  console.log("");
552
- console.log(chalk3.bold(" Your invite code:"));
553
- console.log(chalk3.cyan.bold(`
579
+ console.log(chalk4.bold(" Your invite code:"));
580
+ console.log(chalk4.cyan.bold(`
554
581
  ${data.groupCode}
555
582
  `));
556
- console.log(chalk3.dim(" Share with friends: ") + chalk3.white(`npx ccclub join ${data.groupCode}`));
583
+ console.log(chalk4.dim(" Share with friends: ") + chalk4.white(`npx ccclub join ${data.groupCode}`));
557
584
  if (hookOk) {
558
- console.log(chalk3.dim(" Auto-sync: on (via Claude Code hook)"));
585
+ console.log(chalk4.dim(" Auto-sync: on (via Claude Code hook)"));
559
586
  } else {
560
- console.log(chalk3.dim(' Tip: run "ccclub hook" to set up auto-sync'));
587
+ console.log(chalk4.dim(' Tip: run "ccclub hook" to set up auto-sync'));
561
588
  }
562
589
  console.log("");
563
590
  await doSync(true);
@@ -569,26 +596,26 @@ async function initCommand() {
569
596
  }
570
597
  function printQuickStart(groupCode) {
571
598
  console.log("");
572
- console.log(chalk3.bold(" What's next?"));
599
+ console.log(chalk4.bold(" What's next?"));
573
600
  console.log("");
574
- console.log(chalk3.dim(" See the leaderboard:"));
575
- console.log(chalk3.white(" ccclub"));
601
+ console.log(chalk4.dim(" See the leaderboard:"));
602
+ console.log(chalk4.white(" ccclub"));
576
603
  console.log("");
577
- console.log(chalk3.dim(" Yesterday / 7 days / 30 days / all time:"));
578
- console.log(chalk3.white(" ccclub -d 1"));
604
+ console.log(chalk4.dim(" Yesterday / 7 days / 30 days / all time:"));
605
+ console.log(chalk4.white(" ccclub -d 1"));
579
606
  console.log("");
580
- console.log(chalk3.dim(" Open the dashboard in browser:"));
581
- console.log(chalk3.white(` https://ccclub.dev/g/${groupCode}`));
607
+ console.log(chalk4.dim(" Open the dashboard in browser:"));
608
+ console.log(chalk4.white(` https://ccclub.dev/g/${groupCode}`));
582
609
  console.log("");
583
- console.log(chalk3.dim(" Check what data gets uploaded:"));
584
- console.log(chalk3.white(" ccclub show-data"));
610
+ console.log(chalk4.dim(" Check what data gets uploaded:"));
611
+ console.log(chalk4.white(" ccclub show-data"));
585
612
  console.log("");
586
613
  }
587
614
 
588
615
  // src/commands/join.ts
589
616
  import { createInterface as createInterface2 } from "readline/promises";
590
617
  import { stdin as stdin2, stdout as stdout2 } from "process";
591
- import chalk4 from "chalk";
618
+ import chalk5 from "chalk";
592
619
  import ora3 from "ora";
593
620
  async function joinCommand(inviteCode) {
594
621
  let config = await loadConfig();
@@ -602,11 +629,11 @@ async function joinCommand(inviteCode) {
602
629
  const rl = createInterface2({ input: stdin2, output: stdout2 });
603
630
  try {
604
631
  const defaultName = getDefaultDisplayName();
605
- const prompt = defaultName ? chalk4.bold(`Your display name (${defaultName}): `) : chalk4.bold("Your display name: ");
632
+ const prompt = defaultName ? chalk5.bold(`Your display name (${defaultName}): `) : chalk5.bold("Your display name: ");
606
633
  const input = await rl.question(prompt);
607
634
  displayName = input.trim() || defaultName || "";
608
635
  if (!displayName) {
609
- console.error(chalk4.red("Name cannot be empty"));
636
+ console.error(chalk5.red("Name cannot be empty"));
610
637
  return;
611
638
  }
612
639
  } finally {
@@ -624,7 +651,7 @@ async function joinCommand(inviteCode) {
624
651
  signal: AbortSignal.timeout(15e3)
625
652
  });
626
653
  } catch (err) {
627
- spinner.fail(`Join failed: ${err instanceof Error ? err.message : err}`);
654
+ spinner.fail(`Join failed: ${formatFetchError(err)}`);
628
655
  return;
629
656
  }
630
657
  if (!res.ok) {
@@ -655,21 +682,21 @@ async function joinCommand(inviteCode) {
655
682
  await ensureGlobalInstall();
656
683
  }
657
684
  console.log("");
658
- console.log(chalk4.bold(" What's next?"));
685
+ console.log(chalk5.bold(" What's next?"));
659
686
  console.log("");
660
- console.log(chalk4.dim(" See the leaderboard:"));
661
- console.log(chalk4.white(" ccclub"));
687
+ console.log(chalk5.dim(" See the leaderboard:"));
688
+ console.log(chalk5.white(" ccclub"));
662
689
  console.log("");
663
- console.log(chalk4.dim(" Yesterday / 7 days / 30 days / all time:"));
664
- console.log(chalk4.white(" ccclub -d 1"));
690
+ console.log(chalk5.dim(" Yesterday / 7 days / 30 days / all time:"));
691
+ console.log(chalk5.white(" ccclub -d 1"));
665
692
  console.log("");
666
- console.log(chalk4.dim(" Open the dashboard in browser:"));
667
- console.log(chalk4.white(` https://ccclub.dev/g/${data.groupCode}`));
693
+ console.log(chalk5.dim(" Open the dashboard in browser:"));
694
+ console.log(chalk5.white(` https://ccclub.dev/g/${data.groupCode}`));
668
695
  console.log("");
669
696
  }
670
697
 
671
698
  // src/commands/rank.ts
672
- import chalk5 from "chalk";
699
+ import chalk6 from "chalk";
673
700
  import Table from "cli-table3";
674
701
  import ora4 from "ora";
675
702
 
@@ -726,11 +753,11 @@ async function rankCommand(options) {
726
753
  Usage: ccclub -d <period>
727
754
 
728
755
  Options:
729
- ${chalk5.white("ccclub -d 1")} Yesterday
730
- ${chalk5.white("ccclub -d 7")} Last 7 days
731
- ${chalk5.white("ccclub -d 30")} Last 30 days
732
- ${chalk5.white("ccclub -d all")} All time
733
- ${chalk5.white("ccclub")} Today (default)
756
+ ${chalk6.white("ccclub -d 1")} Yesterday
757
+ ${chalk6.white("ccclub -d 7")} Last 7 days
758
+ ${chalk6.white("ccclub -d 30")} Last 30 days
759
+ ${chalk6.white("ccclub -d all")} All time
760
+ ${chalk6.white("ccclub")} Today (default)
734
761
  `;
735
762
  if (options.days) {
736
763
  if (options.days === true) {
@@ -740,7 +767,7 @@ async function rankCommand(options) {
740
767
  const DAYS_MAP = { "1": "yesterday", "7": "weekly", "30": "monthly", "all": "all-time" };
741
768
  const mapped = DAYS_MAP[options.days];
742
769
  if (!mapped) {
743
- console.log(chalk5.red(`
770
+ console.log(chalk6.red(`
744
771
  Unknown value: -d ${options.days}`));
745
772
  console.log(DAYS_HINT);
746
773
  return;
@@ -764,7 +791,7 @@ async function rankCommand(options) {
764
791
  codes = config.groups.length > 0 ? config.groups : [];
765
792
  }
766
793
  if (codes.length === 0) {
767
- console.log(chalk5.red("No group found. Run 'ccclub init' or 'ccclub join <code>' first."));
794
+ console.log(chalk6.red("No group found. Run 'ccclub init' or 'ccclub join <code>' first."));
768
795
  return;
769
796
  }
770
797
  const spinner = ora4("Loading leaderboard...").start();
@@ -776,7 +803,7 @@ async function rankCommand(options) {
776
803
  const res = await fetch(url, { signal: AbortSignal.timeout(15e3) });
777
804
  if (!res.ok) {
778
805
  if (i === 0) spinner.stop();
779
- console.log(chalk5.red(`
806
+ console.log(chalk6.red(`
780
807
  Couldn't load leaderboard for ${code}`));
781
808
  continue;
782
809
  }
@@ -787,13 +814,13 @@ async function rankCommand(options) {
787
814
  await printActivity(config.apiUrl, code, range);
788
815
  if (i < codes.length - 1) console.log("");
789
816
  }
790
- console.log(chalk5.dim("\n Tokens = input + output ") + chalk5.yellow("(cache excluded)") + chalk5.dim(". Use ") + chalk5.white("--cache") + chalk5.dim(" to include cache tokens."));
817
+ console.log(chalk6.dim("\n Tokens = input + output ") + chalk6.yellow("(cache excluded)") + chalk6.dim(". Use ") + chalk6.white("--cache") + chalk6.dim(" to include cache tokens."));
791
818
  const update = await getUpdateResult();
792
819
  if (update) {
793
- console.log(chalk5.yellow("\n Update available") + chalk5.dim(`: ${update.current} \u2192 ${update.latest} Run `) + chalk5.cyan("npm i -g ccclub@latest"));
820
+ console.log(chalk6.yellow("\n Update available") + chalk6.dim(`: ${update.current} \u2192 ${update.latest} Run `) + chalk6.cyan("npm i -g ccclub@latest"));
794
821
  }
795
822
  } catch (err) {
796
- spinner.fail(`Error: ${err instanceof Error ? err.message : err}`);
823
+ spinner.fail(`Error: ${formatFetchError(err)}`);
797
824
  }
798
825
  }
799
826
  function formatTokens(n) {
@@ -813,16 +840,16 @@ function formatTokens(n) {
813
840
  }
814
841
  function printGroup(data, code, period, config, showCache = false) {
815
842
  if (data.rankings.length === 0) {
816
- console.log(chalk5.bold(`
843
+ console.log(chalk6.bold(`
817
844
  ${data.group.name}`));
818
- console.log(chalk5.yellow(" No data for this period yet"));
819
- console.log(chalk5.dim(" Sync your data first: ccclub sync"));
845
+ console.log(chalk6.yellow(" No data for this period yet"));
846
+ console.log(chalk6.dim(" Sync your data first: ccclub sync"));
820
847
  return;
821
848
  }
822
- console.log(chalk5.bold(`
849
+ console.log(chalk6.bold(`
823
850
  ${data.group.name}`));
824
851
  const periodLabel = { daily: "TODAY", yesterday: "YESTERDAY", weekly: "7 DAYS", monthly: "30 DAYS", "all-time": "ALL TIME" };
825
- console.log(chalk5.dim(` ${periodLabel[period] || period.toUpperCase()} \xB7 ${data.start.slice(0, 10)} \u2192 ${data.end.slice(0, 10)} \xB7 ${data.group.memberCount} members
852
+ console.log(chalk6.dim(` ${periodLabel[period] || period.toUpperCase()} \xB7 ${data.start.slice(0, 10)} \u2192 ${data.end.slice(0, 10)} \xB7 ${data.group.memberCount} members
826
853
  `));
827
854
  const hasPlan = data.rankings.some((r) => r.plan);
828
855
  const head = ["#", "Name", "Cost", "Tokens"];
@@ -834,17 +861,17 @@ function printGroup(data, code, period, config, showCache = false) {
834
861
  head.push("Chats", "$/Chat");
835
862
  widths.push(8, 9);
836
863
  const table = new Table({
837
- head: head.map((h) => chalk5.cyan(h)),
864
+ head: head.map((h) => chalk6.cyan(h)),
838
865
  style: { head: [], border: [] },
839
866
  colWidths: widths
840
867
  });
841
868
  for (const entry of data.rankings) {
842
869
  const isMe = entry.userId === config.userId;
843
870
  const tokens = showCache ? entry.totalTokens : entry.inputTokens + entry.outputTokens;
844
- const marker = isMe ? chalk5.green("\u2192") : " ";
871
+ const marker = isMe ? chalk6.green("\u2192") : " ";
845
872
  const id = (s) => s;
846
- const c = isMe ? chalk5.green : entry.rank === 1 ? chalk5.yellow : id;
847
- const nameC = isMe ? chalk5.green.bold : entry.rank === 1 ? chalk5.yellow.bold : id;
873
+ const c = isMe ? chalk6.green : entry.rank === 1 ? chalk6.yellow : id;
874
+ const nameC = isMe ? chalk6.green.bold : entry.rank === 1 ? chalk6.yellow.bold : id;
848
875
  const row = [
849
876
  `${marker}${c(String(entry.rank))}`,
850
877
  nameC(entry.displayName),
@@ -857,24 +884,24 @@ function printGroup(data, code, period, config, showCache = false) {
857
884
  const monthly = entry.monthlyCostUSD || 0;
858
885
  const roi = price > 0 ? Math.round(monthly / price * 100) : 0;
859
886
  const roiStr = `$${price}/${roi}%`;
860
- const roiC = roi >= 100 ? chalk5.green.bold(roiStr) : roi >= 50 ? chalk5.yellow(roiStr) : chalk5.dim(roiStr);
887
+ const roiC = roi >= 100 ? chalk6.green.bold(roiStr) : roi >= 50 ? chalk6.yellow(roiStr) : chalk6.dim(roiStr);
861
888
  row.push(roiC);
862
889
  } else if (entry.plan === "api") {
863
- row.push(chalk5.dim("API"));
890
+ row.push(chalk6.dim("API"));
864
891
  } else {
865
- row.push(chalk5.dim("\u2014"));
892
+ row.push(chalk6.dim("\u2014"));
866
893
  }
867
894
  }
868
895
  row.push(c(String(entry.chatCount)));
869
- row.push(entry.chatCount > 0 ? c(`$${(entry.costUSD / entry.chatCount).toFixed(2)}`) : chalk5.dim("\u2014"));
896
+ row.push(entry.chatCount > 0 ? c(`$${(entry.costUSD / entry.chatCount).toFixed(2)}`) : chalk6.dim("\u2014"));
870
897
  table.push(row);
871
898
  }
872
899
  console.log(table.toString());
873
- console.log(chalk5.dim(` Dashboard: ${config.apiUrl}/g/${code}`));
900
+ console.log(chalk6.dim(` Dashboard: ${config.apiUrl}/g/${code}`));
874
901
  if (hasPlan) {
875
902
  const me = data.rankings.find((r) => r.userId === config.userId);
876
903
  if (me && !me.plan) {
877
- console.log(chalk5.dim(" Set your plan: ") + chalk5.white("ccclub profile --plan pro|max100|max200|api"));
904
+ console.log(chalk6.dim(" Set your plan: ") + chalk6.white("ccclub profile --plan pro|max100|max200|api"));
878
905
  }
879
906
  }
880
907
  }
@@ -907,7 +934,7 @@ async function printActivity(apiUrl, code, range) {
907
934
  }
908
935
  }
909
936
  if (globalMax === 0) globalMax = 1;
910
- console.log(chalk5.dim(`
937
+ console.log(chalk6.dim(`
911
938
  Activity (${range})`));
912
939
  for (let i = 0; i < active.length; i++) {
913
940
  const user = active[i];
@@ -934,7 +961,7 @@ async function printActivity(apiUrl, code, range) {
934
961
  name = [...name].slice(0, cut).join("");
935
962
  }
936
963
  const pad = " ".repeat(Math.max(0, maxWidth - displayWidth));
937
- console.log(` ${chalk5.dim(name + pad)} ${spark} ${chalk5.dim("$" + total.toFixed(2))}`);
964
+ console.log(` ${chalk6.dim(name + pad)} ${spark} ${chalk6.dim("$" + total.toFixed(2))}`);
938
965
  }
939
966
  const axisArr = new Array(bucketCount).fill(" ");
940
967
  if (range === "24h" || range === "yesterday") {
@@ -957,13 +984,13 @@ async function printActivity(apiUrl, code, range) {
957
984
  for (let c = 0; c < label.length && b + c < bucketCount; c++) axisArr[b + c] = label[c];
958
985
  }
959
986
  }
960
- console.log(chalk5.dim(" " + " ".repeat(12) + " " + axisArr.join("")));
987
+ console.log(chalk6.dim(" " + " ".repeat(12) + " " + axisArr.join("")));
961
988
  } catch {
962
989
  }
963
990
  }
964
991
 
965
992
  // src/commands/profile.ts
966
- import chalk6 from "chalk";
993
+ import chalk7 from "chalk";
967
994
  import ora5 from "ora";
968
995
  async function profileCommand(options) {
969
996
  const config = await requireConfig();
@@ -981,15 +1008,15 @@ async function profileCommand(options) {
981
1008
  }
982
1009
  const profile = await res.json();
983
1010
  spinner2.stop();
984
- console.log(chalk6.bold("\n Your Profile"));
1011
+ console.log(chalk7.bold("\n Your Profile"));
985
1012
  console.log(` Name: ${profile.displayName}`);
986
- console.log(` Avatar: ${profile.avatar || chalk6.dim("(default)")}`);
987
- console.log(` Visibility: ${profile.visibility === "public" ? chalk6.green("public") : chalk6.dim("private")}`);
988
- console.log(` Plan: ${profile.plan ? PLAN_LABELS[profile.plan] || profile.plan : chalk6.dim("(not set)")}`);
989
- console.log(` URL: ${profile.url || chalk6.dim("(not set)")}`);
1013
+ console.log(` Avatar: ${profile.avatar || chalk7.dim("(default)")}`);
1014
+ console.log(` Visibility: ${profile.visibility === "public" ? chalk7.green("public") : chalk7.dim("private")}`);
1015
+ console.log(` Plan: ${profile.plan ? PLAN_LABELS[profile.plan] || profile.plan : chalk7.dim("(not set)")}`);
1016
+ console.log(` URL: ${profile.url || chalk7.dim("(not set)")}`);
990
1017
  console.log();
991
1018
  } catch (err) {
992
- spinner2.fail(`Error: ${err instanceof Error ? err.message : err}`);
1019
+ spinner2.fail(`Error: ${formatFetchError(err)}`);
993
1020
  }
994
1021
  return;
995
1022
  }
@@ -997,10 +1024,10 @@ async function profileCommand(options) {
997
1024
  if (options.plan !== void 0) {
998
1025
  const p = options.plan.toLowerCase();
999
1026
  if (p && p !== "none" && !validPlans.includes(p)) {
1000
- console.log(chalk6.red(`
1027
+ console.log(chalk7.red(`
1001
1028
  Invalid plan: "${options.plan}"`));
1002
- console.log(chalk6.dim(" Valid options: ") + chalk6.white("pro") + chalk6.dim(" ($20), ") + chalk6.white("max100") + chalk6.dim(" ($100), ") + chalk6.white("max200") + chalk6.dim(" ($200), ") + chalk6.white("api"));
1003
- console.log(chalk6.dim(" To clear: ") + chalk6.white("ccclub profile --plan none"));
1029
+ console.log(chalk7.dim(" Valid options: ") + chalk7.white("pro") + chalk7.dim(" ($20), ") + chalk7.white("max100") + chalk7.dim(" ($100), ") + chalk7.white("max200") + chalk7.dim(" ($200), ") + chalk7.white("api"));
1030
+ console.log(chalk7.dim(" To clear: ") + chalk7.white("ccclub profile --plan none"));
1004
1031
  return;
1005
1032
  }
1006
1033
  }
@@ -1032,60 +1059,60 @@ async function profileCommand(options) {
1032
1059
  config.displayName = body.displayName;
1033
1060
  await saveConfig(config);
1034
1061
  }
1035
- console.log(chalk6.green("\n Profile updated!"));
1062
+ console.log(chalk7.green("\n Profile updated!"));
1036
1063
  console.log(` Name: ${profile.displayName}`);
1037
- console.log(` Avatar: ${profile.avatar || chalk6.dim("(default)")}`);
1038
- console.log(` Visibility: ${profile.visibility === "public" ? chalk6.green("public") : chalk6.dim("private")}`);
1039
- console.log(` Plan: ${profile.plan ? PLAN_LABELS[profile.plan] || profile.plan : chalk6.dim("(not set)")}`);
1040
- console.log(` URL: ${profile.url || chalk6.dim("(not set)")}`);
1064
+ console.log(` Avatar: ${profile.avatar || chalk7.dim("(default)")}`);
1065
+ console.log(` Visibility: ${profile.visibility === "public" ? chalk7.green("public") : chalk7.dim("private")}`);
1066
+ console.log(` Plan: ${profile.plan ? PLAN_LABELS[profile.plan] || profile.plan : chalk7.dim("(not set)")}`);
1067
+ console.log(` URL: ${profile.url || chalk7.dim("(not set)")}`);
1041
1068
  console.log();
1042
1069
  } catch (err) {
1043
- spinner.fail(`Error: ${err instanceof Error ? err.message : err}`);
1070
+ spinner.fail(`Error: ${formatFetchError(err)}`);
1044
1071
  }
1045
1072
  }
1046
1073
 
1047
1074
  // src/commands/show-data.ts
1048
- import chalk7 from "chalk";
1075
+ import chalk8 from "chalk";
1049
1076
  async function showDataCommand() {
1050
- console.log(chalk7.bold("\n What ccclub uploads:\n"));
1051
- console.log(chalk7.dim(" Only aggregated 30-minute block summaries. No conversation content,"));
1052
- console.log(chalk7.dim(" no file paths, no project names, no session details.\n"));
1077
+ console.log(chalk8.bold("\n What ccclub uploads:\n"));
1078
+ console.log(chalk8.dim(" Only aggregated 30-minute block summaries. No conversation content,"));
1079
+ console.log(chalk8.dim(" no file paths, no project names, no session details.\n"));
1053
1080
  const { entries, humanTurns } = await collectUsageEntries();
1054
1081
  const blocks = aggregateToBlocks(entries, humanTurns);
1055
1082
  if (blocks.length === 0) {
1056
- console.log(chalk7.yellow(" No usage data found in ~/.claude/projects/"));
1083
+ console.log(chalk8.yellow(" No usage data found in ~/.claude/projects/"));
1057
1084
  return;
1058
1085
  }
1059
- console.log(chalk7.dim(` Total entries found: ${entries.length}`));
1060
- console.log(chalk7.dim(` Aggregated into: ${blocks.length} blocks
1086
+ console.log(chalk8.dim(` Total entries found: ${entries.length}`));
1087
+ console.log(chalk8.dim(` Aggregated into: ${blocks.length} blocks
1061
1088
  `));
1062
1089
  const recent = blocks.slice(-5);
1063
- console.log(chalk7.bold(" Last 5 blocks (this is exactly what gets uploaded):\n"));
1090
+ console.log(chalk8.bold(" Last 5 blocks (this is exactly what gets uploaded):\n"));
1064
1091
  for (const block of recent) {
1065
- console.log(chalk7.cyan(` ${block.blockStart.slice(0, 16)} \u2192 ${block.blockEnd.slice(11, 16)}`));
1066
- console.log(chalk7.dim(` input: ${block.inputTokens.toLocaleString()} output: ${block.outputTokens.toLocaleString()} cache_create: ${block.cacheCreationTokens.toLocaleString()} cache_read: ${block.cacheReadTokens.toLocaleString()}`));
1067
- console.log(chalk7.dim(` cost: $${block.costUSD.toFixed(4)} calls: ${block.entryCount} models: ${block.models.join(", ")}`));
1092
+ console.log(chalk8.cyan(` ${block.blockStart.slice(0, 16)} \u2192 ${block.blockEnd.slice(11, 16)}`));
1093
+ console.log(chalk8.dim(` input: ${block.inputTokens.toLocaleString()} output: ${block.outputTokens.toLocaleString()} cache_create: ${block.cacheCreationTokens.toLocaleString()} cache_read: ${block.cacheReadTokens.toLocaleString()}`));
1094
+ console.log(chalk8.dim(` cost: $${block.costUSD.toFixed(4)} calls: ${block.entryCount} models: ${block.models.join(", ")}`));
1068
1095
  }
1069
1096
  const totalInput = blocks.reduce((s, b) => s + b.inputTokens, 0);
1070
1097
  const totalOutput = blocks.reduce((s, b) => s + b.outputTokens, 0);
1071
1098
  const totalCost = blocks.reduce((s, b) => s + b.costUSD, 0);
1072
- console.log(chalk7.bold(`
1099
+ console.log(chalk8.bold(`
1073
1100
  All-time total: ${(totalInput + totalOutput).toLocaleString()} tokens \xB7 $${totalCost.toFixed(2)}`));
1074
- console.log(chalk7.dim(` input: ${totalInput.toLocaleString()} output: ${totalOutput.toLocaleString()}`));
1101
+ console.log(chalk8.dim(` input: ${totalInput.toLocaleString()} output: ${totalOutput.toLocaleString()}`));
1075
1102
  }
1076
1103
 
1077
1104
  // src/commands/group.ts
1078
1105
  import { createInterface as createInterface3 } from "readline/promises";
1079
1106
  import { stdin as stdin3, stdout as stdout3 } from "process";
1080
- import chalk8 from "chalk";
1107
+ import chalk9 from "chalk";
1081
1108
  import ora6 from "ora";
1082
1109
  async function createGroupCommand() {
1083
1110
  const config = await requireConfig();
1084
1111
  const rl = createInterface3({ input: stdin3, output: stdout3 });
1085
1112
  try {
1086
- const name = await rl.question(chalk8.bold("Group name: "));
1113
+ const name = await rl.question(chalk9.bold("Group name: "));
1087
1114
  if (!name.trim()) {
1088
- console.error(chalk8.red("Name cannot be empty"));
1115
+ console.error(chalk9.red("Name cannot be empty"));
1089
1116
  return;
1090
1117
  }
1091
1118
  const spinner = ora6("Creating group...").start();
@@ -1101,7 +1128,7 @@ async function createGroupCommand() {
1101
1128
  signal: AbortSignal.timeout(15e3)
1102
1129
  });
1103
1130
  } catch (err) {
1104
- spinner.fail(`Failed: ${err instanceof Error ? err.message : err}`);
1131
+ spinner.fail(`Failed: ${formatFetchError(err)}`);
1105
1132
  return;
1106
1133
  }
1107
1134
  if (!res.ok) {
@@ -1115,10 +1142,10 @@ async function createGroupCommand() {
1115
1142
  await saveConfig(config);
1116
1143
  }
1117
1144
  spinner.succeed(`Created "${data.groupName}"`);
1118
- console.log(chalk8.bold(`
1119
- Invite code: `) + chalk8.cyan.bold(data.groupCode));
1120
- console.log(chalk8.dim(` Share: npx ccclub join ${data.groupCode}`));
1121
- console.log(chalk8.dim(` Dashboard: ${config.apiUrl}/g/${data.groupCode}`));
1145
+ console.log(chalk9.bold(`
1146
+ Invite code: `) + chalk9.cyan.bold(data.groupCode));
1147
+ console.log(chalk9.dim(` Share: npx ccclub join ${data.groupCode}`));
1148
+ console.log(chalk9.dim(` Dashboard: ${config.apiUrl}/g/${data.groupCode}`));
1122
1149
  } finally {
1123
1150
  rl.close();
1124
1151
  }
@@ -1127,34 +1154,34 @@ async function createGroupCommand() {
1127
1154
  // src/commands/leave.ts
1128
1155
  import { createInterface as createInterface4 } from "readline/promises";
1129
1156
  import { stdin as stdin4, stdout as stdout4 } from "process";
1130
- import chalk9 from "chalk";
1157
+ import chalk10 from "chalk";
1131
1158
  import ora7 from "ora";
1132
1159
  async function leaveCommand(code) {
1133
1160
  const config = await requireConfig();
1134
1161
  if (config.groups.length === 0) {
1135
- console.log(chalk9.yellow(" You're not in any groups."));
1162
+ console.log(chalk10.yellow(" You're not in any groups."));
1136
1163
  return;
1137
1164
  }
1138
1165
  let targetCode;
1139
1166
  if (code) {
1140
1167
  targetCode = code.toUpperCase();
1141
1168
  if (!config.groups.includes(targetCode)) {
1142
- console.log(chalk9.red(` You're not in group ${targetCode}`));
1169
+ console.log(chalk10.red(` You're not in group ${targetCode}`));
1143
1170
  return;
1144
1171
  }
1145
1172
  } else if (config.groups.length === 1) {
1146
1173
  targetCode = config.groups[0];
1147
1174
  } else {
1148
- console.log(chalk9.bold("\n Your groups:\n"));
1175
+ console.log(chalk10.bold("\n Your groups:\n"));
1149
1176
  for (let i = 0; i < config.groups.length; i++) {
1150
1177
  console.log(` ${i + 1}. ${config.groups[i]}`);
1151
1178
  }
1152
1179
  const rl2 = createInterface4({ input: stdin4, output: stdout4 });
1153
1180
  try {
1154
- const input = await rl2.question(chalk9.bold("\n Leave which group? (number): "));
1181
+ const input = await rl2.question(chalk10.bold("\n Leave which group? (number): "));
1155
1182
  const idx = parseInt(input.trim(), 10) - 1;
1156
1183
  if (isNaN(idx) || idx < 0 || idx >= config.groups.length) {
1157
- console.log(chalk9.red(" Invalid selection."));
1184
+ console.log(chalk10.red(" Invalid selection."));
1158
1185
  return;
1159
1186
  }
1160
1187
  targetCode = config.groups[idx];
@@ -1164,9 +1191,9 @@ async function leaveCommand(code) {
1164
1191
  }
1165
1192
  const rl = createInterface4({ input: stdin4, output: stdout4 });
1166
1193
  try {
1167
- const answer = await rl.question(chalk9.bold(` Leave group ${targetCode}? [y/N] `));
1194
+ const answer = await rl.question(chalk10.bold(` Leave group ${targetCode}? [y/N] `));
1168
1195
  if (answer.trim().toLowerCase() !== "y") {
1169
- console.log(chalk9.dim(" Cancelled."));
1196
+ console.log(chalk10.dim(" Cancelled."));
1170
1197
  return;
1171
1198
  }
1172
1199
  } finally {
@@ -1193,30 +1220,30 @@ async function leaveCommand(code) {
1193
1220
  await saveConfig(config);
1194
1221
  spinner.succeed(`Left "${data.groupName}"`);
1195
1222
  } catch (err) {
1196
- spinner.fail(`Failed: ${err instanceof Error ? err.message : err}`);
1223
+ spinner.fail(`Failed: ${formatFetchError(err)}`);
1197
1224
  }
1198
1225
  }
1199
1226
 
1200
1227
  // src/commands/hook.ts
1201
- import chalk10 from "chalk";
1228
+ import chalk11 from "chalk";
1202
1229
  async function hookCommand() {
1203
1230
  if (isHookInstalled()) {
1204
- console.log(chalk10.green(" Auto-sync is already set up."));
1205
- console.log(chalk10.dim(" Usage syncs automatically when sessions end."));
1231
+ console.log(chalk11.green(" Auto-sync is already set up."));
1232
+ console.log(chalk11.dim(" Usage syncs automatically when sessions end."));
1206
1233
  return;
1207
1234
  }
1208
1235
  const ok = await installHook();
1209
1236
  if (ok) {
1210
- console.log(chalk10.green(" Auto-sync installed!"));
1211
- console.log(chalk10.dim(" Usage will sync automatically when sessions end."));
1237
+ console.log(chalk11.green(" Auto-sync installed!"));
1238
+ console.log(chalk11.dim(" Usage will sync automatically when sessions end."));
1212
1239
  } else {
1213
- console.log(chalk10.red(" Failed to set up auto-sync."));
1214
- console.log(chalk10.dim(" Try running the command again, or see https://ccclub.dev for help."));
1240
+ console.log(chalk11.red(" Failed to set up auto-sync."));
1241
+ console.log(chalk11.dim(" Try running the command again, or see https://ccclub.dev for help."));
1215
1242
  }
1216
1243
  }
1217
1244
 
1218
1245
  // src/index.ts
1219
- var VERSION = "0.2.65";
1246
+ var VERSION = "0.2.67";
1220
1247
  startUpdateCheck(VERSION);
1221
1248
  var program = new Command();
1222
1249
  program.name("ccclub").description("Claude Code leaderboard among friends").version(VERSION, "-v, -V, --version");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccclub",
3
- "version": "0.2.65",
3
+ "version": "0.2.67",
4
4
  "type": "module",
5
5
  "description": "Claude Code leaderboard among friends",
6
6
  "bin": {