ccclub 0.2.34 → 0.2.36

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 +69 -14
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -18,16 +18,28 @@ import { randomBytes } from "crypto";
18
18
  import { execSync } from "child_process";
19
19
 
20
20
  // ../shared/dist/constants.js
21
- var BLOCK_DURATION_HOURS = 5;
21
+ var BLOCK_DURATION_HOURS = 1;
22
22
  var BLOCK_DURATION_MS = BLOCK_DURATION_HOURS * 60 * 60 * 1e3;
23
23
  var DEFAULT_API_URL = "https://ccclub.dev";
24
24
  var CLAUDE_PROJECTS_DIR = ".claude/projects";
25
25
  var CCCLUB_CONFIG_DIR = ".ccclub";
26
+ var PLAN_PRICES = {
27
+ pro: 20,
28
+ max100: 100,
29
+ max200: 200,
30
+ api: 0
31
+ };
32
+ var PLAN_LABELS = {
33
+ pro: "Pro $20",
34
+ max100: "Max $100",
35
+ max200: "Max $200",
36
+ api: "API"
37
+ };
26
38
  var MODEL_PRICING = {
27
- // Opus 4.5+ (reduced pricing)
39
+ // Opus 4.5+
28
40
  "claude-opus-4-6": { input: 5, output: 25, cacheCreation: 6.25, cacheRead: 0.5 },
29
41
  "claude-opus-4-5-20251101": { input: 5, output: 25, cacheCreation: 6.25, cacheRead: 0.5 },
30
- // Opus 4.1 (original pricing)
42
+ // Opus 4.0–4.1
31
43
  "claude-opus-4-1-20250805": { input: 15, output: 75, cacheCreation: 18.75, cacheRead: 1.5 },
32
44
  // Sonnet
33
45
  "claude-sonnet-4-5-20250929": { input: 3, output: 15, cacheCreation: 3.75, cacheRead: 0.3 },
@@ -37,8 +49,23 @@ var MODEL_PRICING = {
37
49
  "claude-haiku-4-5-20251001": { input: 1, output: 5, cacheCreation: 1.25, cacheRead: 0.1 },
38
50
  "claude-3-5-haiku-20241022": { input: 0.8, output: 4, cacheCreation: 1, cacheRead: 0.08 }
39
51
  };
52
+ var FAMILY_FALLBACK = {
53
+ opus: MODEL_PRICING["claude-opus-4-6"],
54
+ sonnet: MODEL_PRICING["claude-sonnet-4-5-20250929"],
55
+ haiku: MODEL_PRICING["claude-haiku-4-5-20251001"]
56
+ };
57
+ function getPricing(model) {
58
+ if (MODEL_PRICING[model])
59
+ return MODEL_PRICING[model];
60
+ const lower = model.toLowerCase();
61
+ for (const family of Object.keys(FAMILY_FALLBACK)) {
62
+ if (lower.includes(family))
63
+ return FAMILY_FALLBACK[family];
64
+ }
65
+ return FAMILY_FALLBACK.sonnet;
66
+ }
40
67
  function calculateCost(model, inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens) {
41
- const pricing = MODEL_PRICING[model] || MODEL_PRICING["claude-sonnet-4-5-20250929"];
68
+ const pricing = getPricing(model);
42
69
  return (inputTokens * pricing.input + outputTokens * pricing.output + cacheCreationTokens * pricing.cacheCreation + cacheReadTokens * pricing.cacheRead) / 1e6;
43
70
  }
44
71
 
@@ -751,10 +778,19 @@ function printGroup(data, code, period, config, showCache = false) {
751
778
  ${data.group.name}`));
752
779
  console.log(chalk5.dim(` ${period.toUpperCase()} \xB7 ${data.start.slice(0, 10)} \u2192 ${data.end.slice(0, 10)} \xB7 ${data.group.memberCount} members
753
780
  `));
781
+ const hasPlan = data.rankings.some((r) => r.plan);
782
+ const head = ["#", "Name", "Tokens", "Cost"];
783
+ const widths = [5, 20, 10, 12];
784
+ if (hasPlan) {
785
+ head.push("Plan \xB7 ROI");
786
+ widths.push(16);
787
+ }
788
+ head.push("Chats");
789
+ widths.push(8);
754
790
  const table = new Table({
755
- head: ["#", "Name", "Tokens", "Cost", "Chats"].map((h) => chalk5.cyan(h)),
791
+ head: head.map((h) => chalk5.cyan(h)),
756
792
  style: { head: [], border: [] },
757
- colWidths: [5, 20, 10, 12, 8]
793
+ colWidths: widths
758
794
  });
759
795
  for (const entry of data.rankings) {
760
796
  const isMe = entry.userId === config.userId;
@@ -763,13 +799,29 @@ function printGroup(data, code, period, config, showCache = false) {
763
799
  const id = (s) => s;
764
800
  const c = isMe ? chalk5.green : entry.rank === 1 ? chalk5.yellow : id;
765
801
  const nameC = isMe ? chalk5.green.bold : entry.rank === 1 ? chalk5.yellow.bold : id;
766
- table.push([
802
+ const row = [
767
803
  `${marker}${c(String(entry.rank))}`,
768
804
  nameC(entry.displayName),
769
805
  c(formatTokens(tokens)),
770
- c(`$${entry.costUSD.toFixed(2)}`),
771
- c(String(entry.chatCount))
772
- ]);
806
+ c(`$${entry.costUSD.toFixed(2)}`)
807
+ ];
808
+ if (hasPlan) {
809
+ if (entry.plan && entry.plan !== "api") {
810
+ const price = PLAN_PRICES[entry.plan];
811
+ const label = PLAN_LABELS[entry.plan] || entry.plan;
812
+ const monthly = entry.monthlyCostUSD || 0;
813
+ const roi = price > 0 ? Math.round(monthly / price * 100) : 0;
814
+ const roiStr = `${roi}%`;
815
+ const roiC = roi >= 100 ? chalk5.green.bold(roiStr) : roi >= 50 ? chalk5.yellow(roiStr) : chalk5.dim(roiStr);
816
+ row.push(`${c(label)} ${roiC}`);
817
+ } else if (entry.plan === "api") {
818
+ row.push(c("API"));
819
+ } else {
820
+ row.push(chalk5.dim("\u2014"));
821
+ }
822
+ }
823
+ row.push(c(String(entry.chatCount)));
824
+ table.push(row);
773
825
  }
774
826
  console.log(table.toString());
775
827
  console.log(chalk5.dim(` Dashboard: ${config.apiUrl}/g/${code}`));
@@ -839,7 +891,7 @@ import chalk6 from "chalk";
839
891
  import ora5 from "ora";
840
892
  async function profileCommand(options) {
841
893
  const config = await requireConfig();
842
- const hasUpdate = options.name !== void 0 || options.avatar !== void 0 || options.public || options.private;
894
+ const hasUpdate = options.name !== void 0 || options.avatar !== void 0 || options.public || options.private || options.plan !== void 0;
843
895
  if (!hasUpdate) {
844
896
  const spinner2 = ora5("Fetching profile...").start();
845
897
  try {
@@ -857,6 +909,7 @@ async function profileCommand(options) {
857
909
  console.log(` Name: ${profile.displayName}`);
858
910
  console.log(` Avatar: ${profile.avatar || chalk6.dim("(default)")}`);
859
911
  console.log(` Visibility: ${profile.visibility === "public" ? chalk6.green("public") : chalk6.dim("private")}`);
912
+ console.log(` Plan: ${profile.plan ? PLAN_LABELS[profile.plan] || profile.plan : chalk6.dim("(not set)")}`);
860
913
  console.log();
861
914
  } catch (err) {
862
915
  spinner2.fail(`Error: ${err instanceof Error ? err.message : err}`);
@@ -868,6 +921,7 @@ async function profileCommand(options) {
868
921
  if (options.avatar !== void 0) body.avatar = options.avatar;
869
922
  if (options.public) body.visibility = "public";
870
923
  if (options.private) body.visibility = "private";
924
+ if (options.plan !== void 0) body.plan = options.plan;
871
925
  const spinner = ora5("Updating profile...").start();
872
926
  try {
873
927
  const res = await fetch(`${config.apiUrl}/api/profile`, {
@@ -893,6 +947,7 @@ async function profileCommand(options) {
893
947
  console.log(` Name: ${profile.displayName}`);
894
948
  console.log(` Avatar: ${profile.avatar || chalk6.dim("(default)")}`);
895
949
  console.log(` Visibility: ${profile.visibility === "public" ? chalk6.green("public") : chalk6.dim("private")}`);
950
+ console.log(` Plan: ${profile.plan ? PLAN_LABELS[profile.plan] || profile.plan : chalk6.dim("(not set)")}`);
896
951
  console.log();
897
952
  } catch (err) {
898
953
  spinner.fail(`Error: ${err instanceof Error ? err.message : err}`);
@@ -903,7 +958,7 @@ async function profileCommand(options) {
903
958
  import chalk7 from "chalk";
904
959
  async function showDataCommand() {
905
960
  console.log(chalk7.bold("\n What CCClub uploads:\n"));
906
- console.log(chalk7.dim(" Only aggregated 5-hour block summaries. No conversation content,"));
961
+ console.log(chalk7.dim(" Only aggregated 1-hour block summaries. No conversation content,"));
907
962
  console.log(chalk7.dim(" no file paths, no project names, no session details.\n"));
908
963
  const { entries, humanTurns } = await collectUsageEntries();
909
964
  const blocks = aggregateToBlocks(entries, humanTurns);
@@ -998,7 +1053,7 @@ async function hookCommand() {
998
1053
  }
999
1054
 
1000
1055
  // src/index.ts
1001
- var VERSION = "0.2.34";
1056
+ var VERSION = "0.2.36";
1002
1057
  startUpdateCheck(VERSION);
1003
1058
  var program = new Command();
1004
1059
  program.name("ccclub").description("CCClub - Compare Claude Code usage with friends").version(VERSION);
@@ -1007,7 +1062,7 @@ program.command("join").description("Join a friend's group").argument("<invite-c
1007
1062
  program.command("sync").description("Sync local usage data to server").option("-s, --silent", "No output (used by auto-sync hook)").option("-f, --full", "Force full re-sync of all data").action(syncCommand);
1008
1063
  program.command("rank", { isDefault: true, hidden: true }).description("Show leaderboard").option("-p, --period <period>", "daily | weekly | monthly | all-time", "daily").option("-g, --group <code>", "Group invite code").option("--global", "Show global public ranking").option("--cache", "Include cache tokens in count").action(rankCommand);
1009
1064
  program.command("-p weekly|monthly|all-time", { hidden: false }).description("Switch period (default: daily)");
1010
- program.command("profile").description("View or update your profile").option("-n, --name <name>", "Set display name").option("--avatar <url>", "Set avatar URL (empty string to reset)").option("--public", "Set profile visibility to public").option("--private", "Set profile visibility to private").action(profileCommand);
1065
+ program.command("profile").description("View or update your profile").option("-n, --name <name>", "Set display name").option("--avatar <url>", "Set avatar URL (empty string to reset)").option("--public", "Set profile visibility to public").option("--private", "Set profile visibility to private").option("--plan <plan>", "Set subscription plan (pro, max100, max200, api, or empty to clear)").action(profileCommand);
1011
1066
  program.command("create").description("Create a new group").action(createGroupCommand);
1012
1067
  program.command("show-data").description("Show exactly what data CCClub uploads (privacy audit)").action(showDataCommand);
1013
1068
  program.command("hook").description("Set up Claude Code hook for auto-sync on session end").action(hookCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccclub",
3
- "version": "0.2.34",
3
+ "version": "0.2.36",
4
4
  "type": "module",
5
5
  "description": "See how much Claude Code you and your friends are using",
6
6
  "bin": {