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.
- package/dist/index.js +151 -124
- 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
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
517
|
+
console.log(chalk3.green(" Done!") + chalk3.dim(" You can now use ") + chalk3.white("ccclub") + chalk3.dim(" directly."));
|
|
491
518
|
} else {
|
|
492
|
-
console.log(
|
|
493
|
-
console.log(
|
|
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(
|
|
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(
|
|
533
|
+
if (hookOk) console.log(chalk4.green(" Auto-sync hook installed!"));
|
|
507
534
|
}
|
|
508
|
-
console.log(
|
|
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 ?
|
|
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(
|
|
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
|
|
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(
|
|
553
|
-
console.log(
|
|
579
|
+
console.log(chalk4.bold(" Your invite code:"));
|
|
580
|
+
console.log(chalk4.cyan.bold(`
|
|
554
581
|
${data.groupCode}
|
|
555
582
|
`));
|
|
556
|
-
console.log(
|
|
583
|
+
console.log(chalk4.dim(" Share with friends: ") + chalk4.white(`npx ccclub join ${data.groupCode}`));
|
|
557
584
|
if (hookOk) {
|
|
558
|
-
console.log(
|
|
585
|
+
console.log(chalk4.dim(" Auto-sync: on (via Claude Code hook)"));
|
|
559
586
|
} else {
|
|
560
|
-
console.log(
|
|
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(
|
|
599
|
+
console.log(chalk4.bold(" What's next?"));
|
|
573
600
|
console.log("");
|
|
574
|
-
console.log(
|
|
575
|
-
console.log(
|
|
601
|
+
console.log(chalk4.dim(" See the leaderboard:"));
|
|
602
|
+
console.log(chalk4.white(" ccclub"));
|
|
576
603
|
console.log("");
|
|
577
|
-
console.log(
|
|
578
|
-
console.log(
|
|
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(
|
|
581
|
-
console.log(
|
|
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(
|
|
584
|
-
console.log(
|
|
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
|
|
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 ?
|
|
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(
|
|
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
|
|
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(
|
|
685
|
+
console.log(chalk5.bold(" What's next?"));
|
|
659
686
|
console.log("");
|
|
660
|
-
console.log(
|
|
661
|
-
console.log(
|
|
687
|
+
console.log(chalk5.dim(" See the leaderboard:"));
|
|
688
|
+
console.log(chalk5.white(" ccclub"));
|
|
662
689
|
console.log("");
|
|
663
|
-
console.log(
|
|
664
|
-
console.log(
|
|
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(
|
|
667
|
-
console.log(
|
|
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
|
|
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
|
-
${
|
|
730
|
-
${
|
|
731
|
-
${
|
|
732
|
-
${
|
|
733
|
-
${
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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(
|
|
843
|
+
console.log(chalk6.bold(`
|
|
817
844
|
${data.group.name}`));
|
|
818
|
-
console.log(
|
|
819
|
-
console.log(
|
|
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(
|
|
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(
|
|
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) =>
|
|
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 ?
|
|
871
|
+
const marker = isMe ? chalk6.green("\u2192") : " ";
|
|
845
872
|
const id = (s) => s;
|
|
846
|
-
const c = isMe ?
|
|
847
|
-
const nameC = isMe ?
|
|
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 ?
|
|
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(
|
|
890
|
+
row.push(chalk6.dim("API"));
|
|
864
891
|
} else {
|
|
865
|
-
row.push(
|
|
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)}`) :
|
|
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(
|
|
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(
|
|
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(
|
|
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(` ${
|
|
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(
|
|
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
|
|
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(
|
|
1011
|
+
console.log(chalk7.bold("\n Your Profile"));
|
|
985
1012
|
console.log(` Name: ${profile.displayName}`);
|
|
986
|
-
console.log(` Avatar: ${profile.avatar ||
|
|
987
|
-
console.log(` Visibility: ${profile.visibility === "public" ?
|
|
988
|
-
console.log(` Plan: ${profile.plan ? PLAN_LABELS[profile.plan] || profile.plan :
|
|
989
|
-
console.log(` URL: ${profile.url ||
|
|
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
|
|
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(
|
|
1027
|
+
console.log(chalk7.red(`
|
|
1001
1028
|
Invalid plan: "${options.plan}"`));
|
|
1002
|
-
console.log(
|
|
1003
|
-
console.log(
|
|
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(
|
|
1062
|
+
console.log(chalk7.green("\n Profile updated!"));
|
|
1036
1063
|
console.log(` Name: ${profile.displayName}`);
|
|
1037
|
-
console.log(` Avatar: ${profile.avatar ||
|
|
1038
|
-
console.log(` Visibility: ${profile.visibility === "public" ?
|
|
1039
|
-
console.log(` Plan: ${profile.plan ? PLAN_LABELS[profile.plan] || profile.plan :
|
|
1040
|
-
console.log(` URL: ${profile.url ||
|
|
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
|
|
1070
|
+
spinner.fail(`Error: ${formatFetchError(err)}`);
|
|
1044
1071
|
}
|
|
1045
1072
|
}
|
|
1046
1073
|
|
|
1047
1074
|
// src/commands/show-data.ts
|
|
1048
|
-
import
|
|
1075
|
+
import chalk8 from "chalk";
|
|
1049
1076
|
async function showDataCommand() {
|
|
1050
|
-
console.log(
|
|
1051
|
-
console.log(
|
|
1052
|
-
console.log(
|
|
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(
|
|
1083
|
+
console.log(chalk8.yellow(" No usage data found in ~/.claude/projects/"));
|
|
1057
1084
|
return;
|
|
1058
1085
|
}
|
|
1059
|
-
console.log(
|
|
1060
|
-
console.log(
|
|
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(
|
|
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(
|
|
1066
|
-
console.log(
|
|
1067
|
-
console.log(
|
|
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(
|
|
1099
|
+
console.log(chalk8.bold(`
|
|
1073
1100
|
All-time total: ${(totalInput + totalOutput).toLocaleString()} tokens \xB7 $${totalCost.toFixed(2)}`));
|
|
1074
|
-
console.log(
|
|
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
|
|
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(
|
|
1113
|
+
const name = await rl.question(chalk9.bold("Group name: "));
|
|
1087
1114
|
if (!name.trim()) {
|
|
1088
|
-
console.error(
|
|
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
|
|
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(
|
|
1119
|
-
Invite code: `) +
|
|
1120
|
-
console.log(
|
|
1121
|
-
console.log(
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
1194
|
+
const answer = await rl.question(chalk10.bold(` Leave group ${targetCode}? [y/N] `));
|
|
1168
1195
|
if (answer.trim().toLowerCase() !== "y") {
|
|
1169
|
-
console.log(
|
|
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
|
|
1223
|
+
spinner.fail(`Failed: ${formatFetchError(err)}`);
|
|
1197
1224
|
}
|
|
1198
1225
|
}
|
|
1199
1226
|
|
|
1200
1227
|
// src/commands/hook.ts
|
|
1201
|
-
import
|
|
1228
|
+
import chalk11 from "chalk";
|
|
1202
1229
|
async function hookCommand() {
|
|
1203
1230
|
if (isHookInstalled()) {
|
|
1204
|
-
console.log(
|
|
1205
|
-
console.log(
|
|
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(
|
|
1211
|
-
console.log(
|
|
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(
|
|
1214
|
-
console.log(
|
|
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.
|
|
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");
|