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.
- package/dist/index.js +73 -76
- 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
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
336
|
+
const displayName = await rl.question(chalk2.bold("Your display name: "));
|
|
358
337
|
if (!displayName.trim()) {
|
|
359
|
-
console.error(
|
|
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(
|
|
387
|
-
console.log(
|
|
365
|
+
console.log(chalk2.bold(" Your invite code:"));
|
|
366
|
+
console.log(chalk2.cyan.bold(`
|
|
388
367
|
${data.groupCode}
|
|
389
368
|
`));
|
|
390
|
-
console.log(
|
|
369
|
+
console.log(chalk2.dim(" Share with friends: ") + chalk2.white(`npx ccclub join ${data.groupCode}`));
|
|
391
370
|
if (heartbeatOk) {
|
|
392
|
-
console.log(
|
|
371
|
+
console.log(chalk2.dim(" Heartbeat: installed (syncs every hour)"));
|
|
393
372
|
} else {
|
|
394
|
-
console.log(
|
|
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
|
|
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(
|
|
398
|
+
displayName = await rl.question(chalk3.bold("Your display name: "));
|
|
421
399
|
if (!displayName.trim()) {
|
|
422
|
-
console.error(
|
|
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(
|
|
437
|
+
console.log(chalk3.dim(`
|
|
460
438
|
Dashboard: ${apiUrl}/g/${data.groupCode}`));
|
|
461
|
-
console.log(
|
|
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
|
|
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(
|
|
494
|
-
console.log(
|
|
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(
|
|
474
|
+
console.log(chalk4.bold(`
|
|
498
475
|
${data.group.name}`));
|
|
499
|
-
console.log(
|
|
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) =>
|
|
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 ?
|
|
509
|
-
const name = isMe ?
|
|
510
|
-
const rankColor = entry.rank <= 3 ?
|
|
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(
|
|
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
|
|
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(
|
|
522
|
+
console.log(chalk5.bold("\n Your Profile"));
|
|
546
523
|
console.log(` Name: ${profile.displayName}`);
|
|
547
|
-
console.log(` Avatar: ${profile.avatar ||
|
|
548
|
-
console.log(` Visibility: ${profile.visibility === "public" ?
|
|
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(
|
|
557
|
+
console.log(chalk5.green("\n Profile updated!"));
|
|
581
558
|
console.log(` Name: ${profile.displayName}`);
|
|
582
|
-
console.log(` Avatar: ${profile.avatar ||
|
|
583
|
-
console.log(` Visibility: ${profile.visibility === "public" ?
|
|
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
|
|
568
|
+
import chalk6 from "chalk";
|
|
592
569
|
async function showDataCommand() {
|
|
593
|
-
console.log(
|
|
594
|
-
console.log(
|
|
595
|
-
console.log(
|
|
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(
|
|
576
|
+
console.log(chalk6.yellow(" No usage data found in ~/.claude/projects/"));
|
|
600
577
|
return;
|
|
601
578
|
}
|
|
602
|
-
console.log(
|
|
603
|
-
console.log(
|
|
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(
|
|
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(
|
|
609
|
-
console.log(
|
|
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(
|
|
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
|
|
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(
|
|
603
|
+
const name = await rl.question(chalk7.bold("Group name: "));
|
|
627
604
|
if (!name.trim()) {
|
|
628
|
-
console.error(
|
|
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(
|
|
652
|
-
Invite code: `) +
|
|
653
|
-
console.log(
|
|
654
|
-
console.log(
|
|
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.
|
|
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);
|