ccclub 0.1.2 → 0.1.4

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 +73 -76
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } 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 chalk2 from "chalk";
10
10
  import ora2 from "ora";
11
11
 
12
12
  // src/config.ts
@@ -133,30 +133,10 @@ async function installHeartbeat() {
133
133
  return true;
134
134
  }
135
135
 
136
- // src/global-install.ts
137
- import { execFile as execFile2 } from "child_process";
138
- import chalk from "chalk";
139
- async function ensureGlobalInstall() {
140
- const isInstalled = await new Promise((resolve) => {
141
- execFile2("ccclub", ["--version"], (err) => resolve(!err));
142
- });
143
- if (isInstalled) return;
144
- console.log(chalk.dim("\n Installing ccclub globally so you can run it directly..."));
145
- const ok = await new Promise((resolve) => {
146
- execFile2("npm", ["install", "-g", "ccclub"], (err) => resolve(!err));
147
- });
148
- if (ok) {
149
- console.log(chalk.green(" \u2713 Installed!") + chalk.dim(" You can now use ") + chalk.white("ccclub rank") + chalk.dim(" directly."));
150
- } else {
151
- console.log(chalk.dim(" Could not install globally. You can do it manually:"));
152
- console.log(chalk.dim(" npm install -g ccclub"));
153
- }
154
- }
155
-
156
136
  // src/commands/sync.ts
157
137
  import { readFile as readFile3, writeFile as writeFile3 } from "fs/promises";
158
138
  import { existsSync as existsSync3 } from "fs";
159
- import chalk2 from "chalk";
139
+ import chalk from "chalk";
160
140
  import ora from "ora";
161
141
 
162
142
  // src/collector.ts
@@ -334,7 +314,7 @@ async function doSync(firstSync = false, silent = false) {
334
314
  const totalCost = blocksToSync.reduce((s, b) => s + b.costUSD, 0);
335
315
  if (spinner) {
336
316
  spinner.succeed(`Synced ${data.synced} blocks`);
337
- log(chalk2.dim(` Tokens: ${totalTokens.toLocaleString()} Cost: $${totalCost.toFixed(4)}`));
317
+ log(chalk.dim(` Tokens: ${totalTokens.toLocaleString()} Cost: $${totalCost.toFixed(4)}`));
338
318
  }
339
319
  } catch (err) {
340
320
  if (spinner) spinner.fail(`Sync error: ${err instanceof Error ? err.message : err}`);
@@ -345,18 +325,17 @@ async function doSync(firstSync = false, silent = false) {
345
325
  async function initCommand() {
346
326
  const existing = await loadConfig();
347
327
  if (existing) {
348
- console.log(chalk3.yellow("Already initialized!"));
328
+ console.log(chalk2.yellow("Already initialized!"));
349
329
  console.log(` User: ${existing.displayName}`);
350
330
  console.log(` Groups: ${existing.groups.join(", ") || "(none)"}`);
351
- console.log(chalk3.dim('\n Run "ccclub rank" to see rankings'));
352
- await ensureGlobalInstall();
331
+ console.log(chalk2.dim('\n Run "ccclub rank" to see rankings'));
353
332
  return;
354
333
  }
355
334
  const rl = createInterface({ input: stdin, output: stdout });
356
335
  try {
357
- const displayName = await rl.question(chalk3.bold("Your display name: "));
336
+ const displayName = await rl.question(chalk2.bold("Your display name: "));
358
337
  if (!displayName.trim()) {
359
- console.error(chalk3.red("Name cannot be empty"));
338
+ console.error(chalk2.red("Name cannot be empty"));
360
339
  return;
361
340
  }
362
341
  const spinner = ora2("Setting up...").start();
@@ -383,19 +362,18 @@ async function initCommand() {
383
362
  const heartbeatOk = await installHeartbeat();
384
363
  spinner.succeed("CCClub initialized!");
385
364
  console.log("");
386
- console.log(chalk3.bold(" Your invite code:"));
387
- console.log(chalk3.cyan.bold(`
365
+ console.log(chalk2.bold(" Your invite code:"));
366
+ console.log(chalk2.cyan.bold(`
388
367
  ${data.groupCode}
389
368
  `));
390
- console.log(chalk3.dim(" Share with friends: ") + chalk3.white(`npx ccclub join ${data.groupCode}`));
369
+ console.log(chalk2.dim(" Share with friends: ") + chalk2.white(`npx ccclub join ${data.groupCode}`));
391
370
  if (heartbeatOk) {
392
- console.log(chalk3.dim(" Heartbeat: installed (syncs every hour)"));
371
+ console.log(chalk2.dim(" Heartbeat: installed (syncs every hour)"));
393
372
  } else {
394
- console.log(chalk3.dim(' Tip: run "ccclub sync" periodically to update data'));
373
+ console.log(chalk2.dim(' Tip: run "ccclub sync" periodically to update data'));
395
374
  }
396
375
  console.log("");
397
376
  await doSync(true);
398
- await ensureGlobalInstall();
399
377
  } finally {
400
378
  rl.close();
401
379
  }
@@ -404,7 +382,7 @@ async function initCommand() {
404
382
  // src/commands/join.ts
405
383
  import { createInterface as createInterface2 } from "readline/promises";
406
384
  import { stdin as stdin2, stdout as stdout2 } from "process";
407
- import chalk4 from "chalk";
385
+ import chalk3 from "chalk";
408
386
  import ora3 from "ora";
409
387
  async function joinCommand(inviteCode) {
410
388
  let config = await loadConfig();
@@ -417,9 +395,9 @@ async function joinCommand(inviteCode) {
417
395
  } else {
418
396
  const rl = createInterface2({ input: stdin2, output: stdout2 });
419
397
  try {
420
- displayName = await rl.question(chalk4.bold("Your display name: "));
398
+ displayName = await rl.question(chalk3.bold("Your display name: "));
421
399
  if (!displayName.trim()) {
422
- console.error(chalk4.red("Name cannot be empty"));
400
+ console.error(chalk3.red("Name cannot be empty"));
423
401
  return;
424
402
  }
425
403
  displayName = displayName.trim();
@@ -456,18 +434,17 @@ async function joinCommand(inviteCode) {
456
434
  await installHeartbeat();
457
435
  }
458
436
  spinner.succeed(`Joined "${data.groupName}"!`);
459
- console.log(chalk4.dim(`
437
+ console.log(chalk3.dim(`
460
438
  Dashboard: ${apiUrl}/g/${data.groupCode}`));
461
- console.log(chalk4.dim(' Run "ccclub rank" to see rankings'));
439
+ console.log(chalk3.dim(' Run "ccclub rank" to see rankings'));
462
440
  if (!config) {
463
441
  console.log("");
464
442
  await doSync(true);
465
443
  }
466
- await ensureGlobalInstall();
467
444
  }
468
445
 
469
446
  // src/commands/rank.ts
470
- import chalk5 from "chalk";
447
+ import chalk4 from "chalk";
471
448
  import Table from "cli-table3";
472
449
  import ora4 from "ora";
473
450
  async function rankCommand(options) {
@@ -490,24 +467,24 @@ async function rankCommand(options) {
490
467
  const data = await res.json();
491
468
  spinner.stop();
492
469
  if (data.rankings.length === 0) {
493
- console.log(chalk5.yellow("\n No rankings data for this period"));
494
- console.log(chalk5.dim(' Run "ccclub sync" to upload your usage data'));
470
+ console.log(chalk4.yellow("\n No rankings data for this period"));
471
+ console.log(chalk4.dim(' Run "ccclub sync" to upload your usage data'));
495
472
  return;
496
473
  }
497
- console.log(chalk5.bold(`
474
+ console.log(chalk4.bold(`
498
475
  ${data.group.name}`));
499
- console.log(chalk5.dim(` ${period.toUpperCase()} \xB7 ${data.start.slice(0, 10)} \u2192 ${data.end.slice(0, 10)} \xB7 ${data.group.memberCount} members
476
+ console.log(chalk4.dim(` ${period.toUpperCase()} \xB7 ${data.start.slice(0, 10)} \u2192 ${data.end.slice(0, 10)} \xB7 ${data.group.memberCount} members
500
477
  `));
501
478
  const table = new Table({
502
- head: ["#", "Name", "Tokens", "Cost", "Calls"].map((h) => chalk5.cyan(h)),
479
+ head: ["#", "Name", "Tokens", "Cost", "Calls"].map((h) => chalk4.cyan(h)),
503
480
  style: { head: [], border: [] },
504
481
  colWidths: [5, 20, 16, 12, 8]
505
482
  });
506
483
  for (const entry of data.rankings) {
507
484
  const isMe = entry.userId === config.userId;
508
- const marker = isMe ? chalk5.green("\u2192") : " ";
509
- const name = isMe ? chalk5.green.bold(entry.displayName) : entry.displayName;
510
- const rankColor = entry.rank <= 3 ? chalk5.yellow : chalk5.white;
485
+ const marker = isMe ? chalk4.green("\u2192") : " ";
486
+ const name = isMe ? chalk4.green.bold(entry.displayName) : entry.displayName;
487
+ const rankColor = entry.rank <= 3 ? chalk4.yellow : chalk4.white;
511
488
  table.push([
512
489
  `${marker}${rankColor(String(entry.rank))}`,
513
490
  name,
@@ -517,7 +494,7 @@ async function rankCommand(options) {
517
494
  ]);
518
495
  }
519
496
  console.log(table.toString());
520
- console.log(chalk5.dim(`
497
+ console.log(chalk4.dim(`
521
498
  Dashboard: ${config.apiUrl}/g/${groupCode}`));
522
499
  } catch (err) {
523
500
  spinner.fail(`Error: ${err instanceof Error ? err.message : err}`);
@@ -525,7 +502,7 @@ async function rankCommand(options) {
525
502
  }
526
503
 
527
504
  // src/commands/profile.ts
528
- import chalk6 from "chalk";
505
+ import chalk5 from "chalk";
529
506
  import ora5 from "ora";
530
507
  async function profileCommand(options) {
531
508
  const config = await requireConfig();
@@ -542,10 +519,10 @@ async function profileCommand(options) {
542
519
  }
543
520
  const profile = await res.json();
544
521
  spinner2.stop();
545
- console.log(chalk6.bold("\n Your Profile"));
522
+ console.log(chalk5.bold("\n Your Profile"));
546
523
  console.log(` Name: ${profile.displayName}`);
547
- console.log(` Avatar: ${profile.avatar || chalk6.dim("(default)")}`);
548
- console.log(` Visibility: ${profile.visibility === "public" ? chalk6.green("public") : chalk6.dim("private")}`);
524
+ console.log(` Avatar: ${profile.avatar || chalk5.dim("(default)")}`);
525
+ console.log(` Visibility: ${profile.visibility === "public" ? chalk5.green("public") : chalk5.dim("private")}`);
549
526
  console.log();
550
527
  } catch (err) {
551
528
  spinner2.fail(`Error: ${err instanceof Error ? err.message : err}`);
@@ -577,10 +554,10 @@ async function profileCommand(options) {
577
554
  config.displayName = body.displayName;
578
555
  await saveConfig(config);
579
556
  }
580
- console.log(chalk6.green("\n Profile updated!"));
557
+ console.log(chalk5.green("\n Profile updated!"));
581
558
  console.log(` Name: ${profile.displayName}`);
582
- console.log(` Avatar: ${profile.avatar || chalk6.dim("(default)")}`);
583
- console.log(` Visibility: ${profile.visibility === "public" ? chalk6.green("public") : chalk6.dim("private")}`);
559
+ console.log(` Avatar: ${profile.avatar || chalk5.dim("(default)")}`);
560
+ console.log(` Visibility: ${profile.visibility === "public" ? chalk5.green("public") : chalk5.dim("private")}`);
584
561
  console.log();
585
562
  } catch (err) {
586
563
  spinner.fail(`Error: ${err instanceof Error ? err.message : err}`);
@@ -588,44 +565,44 @@ async function profileCommand(options) {
588
565
  }
589
566
 
590
567
  // src/commands/show-data.ts
591
- import chalk7 from "chalk";
568
+ import chalk6 from "chalk";
592
569
  async function showDataCommand() {
593
- console.log(chalk7.bold("\n What CCClub uploads:\n"));
594
- console.log(chalk7.dim(" Only aggregated 5-hour block summaries. No conversation content,"));
595
- console.log(chalk7.dim(" no file paths, no project names, no session details.\n"));
570
+ console.log(chalk6.bold("\n What CCClub uploads:\n"));
571
+ console.log(chalk6.dim(" Only aggregated 5-hour block summaries. No conversation content,"));
572
+ console.log(chalk6.dim(" no file paths, no project names, no session details.\n"));
596
573
  const entries = await collectUsageEntries();
597
574
  const blocks = aggregateToBlocks(entries);
598
575
  if (blocks.length === 0) {
599
- console.log(chalk7.yellow(" No usage data found in ~/.claude/projects/"));
576
+ console.log(chalk6.yellow(" No usage data found in ~/.claude/projects/"));
600
577
  return;
601
578
  }
602
- console.log(chalk7.dim(` Total entries found: ${entries.length}`));
603
- console.log(chalk7.dim(` Aggregated into: ${blocks.length} blocks
579
+ console.log(chalk6.dim(` Total entries found: ${entries.length}`));
580
+ console.log(chalk6.dim(` Aggregated into: ${blocks.length} blocks
604
581
  `));
605
582
  const recent = blocks.slice(-5);
606
- console.log(chalk7.bold(" Last 5 blocks (this is exactly what gets uploaded):\n"));
583
+ console.log(chalk6.bold(" Last 5 blocks (this is exactly what gets uploaded):\n"));
607
584
  for (const block of recent) {
608
- console.log(chalk7.cyan(` ${block.blockStart.slice(0, 16)} \u2192 ${block.blockEnd.slice(11, 16)}`));
609
- console.log(chalk7.dim(` tokens: ${block.totalTokens.toLocaleString()} cost: $${block.costUSD.toFixed(4)} calls: ${block.entryCount} models: ${block.models.join(", ")}`));
585
+ console.log(chalk6.cyan(` ${block.blockStart.slice(0, 16)} \u2192 ${block.blockEnd.slice(11, 16)}`));
586
+ console.log(chalk6.dim(` tokens: ${block.totalTokens.toLocaleString()} cost: $${block.costUSD.toFixed(4)} calls: ${block.entryCount} models: ${block.models.join(", ")}`));
610
587
  }
611
588
  const totalTokens = blocks.reduce((s, b) => s + b.totalTokens, 0);
612
589
  const totalCost = blocks.reduce((s, b) => s + b.costUSD, 0);
613
- console.log(chalk7.bold(`
590
+ console.log(chalk6.bold(`
614
591
  All-time total: ${totalTokens.toLocaleString()} tokens \xB7 $${totalCost.toFixed(2)}`));
615
592
  }
616
593
 
617
594
  // src/commands/group.ts
618
595
  import { createInterface as createInterface3 } from "readline/promises";
619
596
  import { stdin as stdin3, stdout as stdout3 } from "process";
620
- import chalk8 from "chalk";
597
+ import chalk7 from "chalk";
621
598
  import ora6 from "ora";
622
599
  async function createGroupCommand() {
623
600
  const config = await requireConfig();
624
601
  const rl = createInterface3({ input: stdin3, output: stdout3 });
625
602
  try {
626
- const name = await rl.question(chalk8.bold("Group name: "));
603
+ const name = await rl.question(chalk7.bold("Group name: "));
627
604
  if (!name.trim()) {
628
- console.error(chalk8.red("Name cannot be empty"));
605
+ console.error(chalk7.red("Name cannot be empty"));
629
606
  return;
630
607
  }
631
608
  const spinner = ora6("Creating group...").start();
@@ -648,18 +625,38 @@ async function createGroupCommand() {
648
625
  await saveConfig(config);
649
626
  }
650
627
  spinner.succeed(`Created "${data.groupName}"`);
651
- console.log(chalk8.bold(`
652
- Invite code: `) + chalk8.cyan.bold(data.groupCode));
653
- console.log(chalk8.dim(` Share: npx ccclub join ${data.groupCode}`));
654
- console.log(chalk8.dim(` Dashboard: ${config.apiUrl}/g/${data.groupCode}`));
628
+ console.log(chalk7.bold(`
629
+ Invite code: `) + chalk7.cyan.bold(data.groupCode));
630
+ console.log(chalk7.dim(` Share: npx ccclub join ${data.groupCode}`));
631
+ console.log(chalk7.dim(` Dashboard: ${config.apiUrl}/g/${data.groupCode}`));
655
632
  } finally {
656
633
  rl.close();
657
634
  }
658
635
  }
659
636
 
637
+ // src/global-install.ts
638
+ import { exec } from "child_process";
639
+ import chalk8 from "chalk";
640
+ function run(cmd) {
641
+ return new Promise((resolve) => {
642
+ exec(cmd, (err) => resolve(!err));
643
+ });
644
+ }
645
+ async function ensureGlobalInstall() {
646
+ if (await run("ccclub --version")) return;
647
+ console.log(chalk8.dim("\n Installing ccclub globally so you can run it directly..."));
648
+ if (await run("npm install -g ccclub")) {
649
+ console.log(chalk8.green(" Done!") + chalk8.dim(" You can now use ") + chalk8.white("ccclub rank") + chalk8.dim(" directly."));
650
+ } else {
651
+ console.log(chalk8.dim(" Could not auto-install. Run manually:"));
652
+ console.log(chalk8.white(" npm install -g ccclub"));
653
+ }
654
+ }
655
+
660
656
  // src/index.ts
661
657
  var program = new Command();
662
- program.name("ccclub").description("CCClub - Compare Claude Code usage with friends").version("0.1.2");
658
+ program.name("ccclub").description("CCClub - Compare Claude Code usage with friends").version("0.1.4");
659
+ program.hook("postAction", () => ensureGlobalInstall());
663
660
  program.command("init").description("Initialize CCClub (one-time setup)").action(initCommand);
664
661
  program.command("join").description("Join a friend's group").argument("<invite-code>", "6-character invite code").action(joinCommand);
665
662
  program.command("sync").description("Sync local usage data to server").option("-s, --silent", "No output (used by heartbeat)").action(syncCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccclub",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "type": "module",
5
5
  "description": "See how much Claude Code you and your friends are using",
6
6
  "bin": {