ccgather 1.2.0 → 1.3.0
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 +271 -140
- package/package.json +51 -51
package/dist/index.js
CHANGED
|
@@ -61,7 +61,7 @@ var init_config = __esm({
|
|
|
61
61
|
"use strict";
|
|
62
62
|
import_conf = __toESM(require("conf"));
|
|
63
63
|
defaults = {
|
|
64
|
-
apiUrl: "https://ccgather.
|
|
64
|
+
apiUrl: "https://ccgather.com/api",
|
|
65
65
|
autoSync: false,
|
|
66
66
|
syncInterval: 60,
|
|
67
67
|
verbose: false
|
|
@@ -106,26 +106,6 @@ async function syncUsage(payload) {
|
|
|
106
106
|
async function getStatus() {
|
|
107
107
|
return fetchApi("/cli/status");
|
|
108
108
|
}
|
|
109
|
-
async function verifyToken(token) {
|
|
110
|
-
try {
|
|
111
|
-
const apiUrl = getApiUrl();
|
|
112
|
-
const response = await fetch(`${apiUrl}/cli/verify`, {
|
|
113
|
-
method: "POST",
|
|
114
|
-
headers: {
|
|
115
|
-
"Content-Type": "application/json",
|
|
116
|
-
Authorization: `Bearer ${token}`
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
const data = await response.json();
|
|
120
|
-
if (!response.ok) {
|
|
121
|
-
return { success: false, error: data.error || "Invalid token" };
|
|
122
|
-
}
|
|
123
|
-
return { success: true, data };
|
|
124
|
-
} catch (error2) {
|
|
125
|
-
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
126
|
-
return { success: false, error: message };
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
109
|
var init_api = __esm({
|
|
130
110
|
"src/lib/api.ts"() {
|
|
131
111
|
"use strict";
|
|
@@ -436,7 +416,7 @@ __export(auth_exports, {
|
|
|
436
416
|
});
|
|
437
417
|
async function auth(options) {
|
|
438
418
|
const config = getConfig();
|
|
439
|
-
console.log(import_chalk5.default.bold("\n\u{
|
|
419
|
+
console.log(import_chalk5.default.bold("\n\u{1F510} CCgather Authentication\n"));
|
|
440
420
|
const existingToken = config.get("apiToken");
|
|
441
421
|
if (existingToken) {
|
|
442
422
|
const { overwrite } = await import_inquirer2.default.prompt([
|
|
@@ -452,50 +432,122 @@ async function auth(options) {
|
|
|
452
432
|
return;
|
|
453
433
|
}
|
|
454
434
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
435
|
+
if (options.token) {
|
|
436
|
+
await authenticateWithToken(options.token);
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const apiUrl = getApiUrl();
|
|
440
|
+
const spinner = (0, import_ora7.default)("Initializing authentication...").start();
|
|
441
|
+
try {
|
|
442
|
+
const response = await fetch(`${apiUrl}/cli/auth/device`, {
|
|
443
|
+
method: "POST"
|
|
444
|
+
});
|
|
445
|
+
if (!response.ok) {
|
|
446
|
+
spinner.fail(import_chalk5.default.red("Failed to initialize authentication"));
|
|
447
|
+
console.log(import_chalk5.default.red("\nPlease check your internet connection and try again."));
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
const deviceData = await response.json();
|
|
451
|
+
spinner.stop();
|
|
452
|
+
console.log(import_chalk5.default.gray(" Opening browser for authentication...\n"));
|
|
453
|
+
console.log(import_chalk5.default.gray(" If browser doesn't open, visit:"));
|
|
454
|
+
console.log(` \u{1F517} ${import_chalk5.default.cyan.underline(deviceData.verification_uri_complete)}`);
|
|
455
|
+
console.log();
|
|
456
|
+
try {
|
|
457
|
+
await (0, import_open.default)(deviceData.verification_uri_complete);
|
|
458
|
+
} catch {
|
|
459
|
+
console.log(import_chalk5.default.yellow(" Could not open browser automatically."));
|
|
460
|
+
console.log(import_chalk5.default.yellow(" Please open the URL above manually."));
|
|
461
|
+
}
|
|
462
|
+
const pollSpinner = (0, import_ora7.default)("Waiting for authorization...").start();
|
|
463
|
+
const startTime = Date.now();
|
|
464
|
+
const expiresAt = startTime + deviceData.expires_in * 1e3;
|
|
465
|
+
const pollInterval = Math.max(deviceData.interval * 1e3, 5e3);
|
|
466
|
+
while (Date.now() < expiresAt) {
|
|
467
|
+
await sleep(pollInterval);
|
|
468
|
+
try {
|
|
469
|
+
const pollResponse = await fetch(
|
|
470
|
+
`${apiUrl}/cli/auth/device/poll?device_code=${deviceData.device_code}`
|
|
471
|
+
);
|
|
472
|
+
const pollData = await pollResponse.json();
|
|
473
|
+
if (pollData.status === "authorized" && pollData.token) {
|
|
474
|
+
pollSpinner.succeed(import_chalk5.default.green("Authentication successful!"));
|
|
475
|
+
config.set("apiToken", pollData.token);
|
|
476
|
+
config.set("userId", pollData.userId);
|
|
477
|
+
config.set("username", pollData.username);
|
|
478
|
+
console.log(import_chalk5.default.gray(`
|
|
479
|
+
Welcome, ${import_chalk5.default.white(pollData.username)}!`));
|
|
480
|
+
console.log(import_chalk5.default.gray("\nYou can now submit your usage data:"));
|
|
481
|
+
console.log(import_chalk5.default.cyan(" npx ccgather submit\n"));
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
if (pollData.status === "expired" || pollData.status === "used") {
|
|
485
|
+
pollSpinner.fail(import_chalk5.default.red("Authentication expired or already used"));
|
|
486
|
+
console.log(import_chalk5.default.gray('\nPlease run "ccgather auth" to try again.\n'));
|
|
487
|
+
process.exit(1);
|
|
469
488
|
}
|
|
489
|
+
const remaining = Math.ceil((expiresAt - Date.now()) / 1e3);
|
|
490
|
+
pollSpinner.text = `Waiting for authorization... (${remaining}s remaining)`;
|
|
491
|
+
} catch {
|
|
470
492
|
}
|
|
471
|
-
|
|
472
|
-
|
|
493
|
+
}
|
|
494
|
+
pollSpinner.fail(import_chalk5.default.red("Authentication timed out"));
|
|
495
|
+
console.log(import_chalk5.default.gray('\nPlease run "ccgather auth" to try again.\n'));
|
|
496
|
+
process.exit(1);
|
|
497
|
+
} catch (error2) {
|
|
498
|
+
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
499
|
+
console.log(import_chalk5.default.red(`
|
|
500
|
+
Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`));
|
|
501
|
+
process.exit(1);
|
|
473
502
|
}
|
|
503
|
+
}
|
|
504
|
+
async function authenticateWithToken(token) {
|
|
505
|
+
const config = getConfig();
|
|
506
|
+
const apiUrl = getApiUrl();
|
|
474
507
|
const spinner = (0, import_ora7.default)("Verifying token...").start();
|
|
475
|
-
|
|
476
|
-
|
|
508
|
+
try {
|
|
509
|
+
const response = await fetch(`${apiUrl}/cli/verify`, {
|
|
510
|
+
method: "POST",
|
|
511
|
+
headers: {
|
|
512
|
+
"Content-Type": "application/json",
|
|
513
|
+
Authorization: `Bearer ${token}`
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
if (!response.ok) {
|
|
517
|
+
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
518
|
+
const errorData = await response.json().catch(() => ({}));
|
|
519
|
+
console.log(import_chalk5.default.red(`Error: ${errorData.error || "Invalid token"}`));
|
|
520
|
+
console.log(import_chalk5.default.gray("\nMake sure your token is correct and try again."));
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
const data = await response.json();
|
|
524
|
+
config.set("apiToken", token);
|
|
525
|
+
config.set("userId", data.userId);
|
|
526
|
+
config.set("username", data.username);
|
|
527
|
+
spinner.succeed(import_chalk5.default.green("Authentication successful!"));
|
|
528
|
+
console.log(import_chalk5.default.gray(`
|
|
529
|
+
Welcome, ${import_chalk5.default.white(data.username)}!`));
|
|
530
|
+
console.log(import_chalk5.default.gray("\nNext step: Submit your usage data:"));
|
|
531
|
+
console.log(import_chalk5.default.cyan(" npx ccgather submit\n"));
|
|
532
|
+
} catch (error2) {
|
|
477
533
|
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
478
|
-
console.log(import_chalk5.default.red(`
|
|
479
|
-
|
|
534
|
+
console.log(import_chalk5.default.red(`
|
|
535
|
+
Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`));
|
|
480
536
|
process.exit(1);
|
|
481
537
|
}
|
|
482
|
-
config.set("apiToken", token);
|
|
483
|
-
config.set("userId", result.data?.userId);
|
|
484
|
-
spinner.succeed(import_chalk5.default.green("Authentication successful!"));
|
|
485
|
-
console.log(import_chalk5.default.gray(`
|
|
486
|
-
Welcome, ${import_chalk5.default.white(result.data?.username)}!`));
|
|
487
|
-
console.log(import_chalk5.default.gray("\nNext step: Sync your usage data:"));
|
|
488
|
-
console.log(import_chalk5.default.cyan(" npx ccgather sync\n"));
|
|
489
538
|
}
|
|
490
|
-
|
|
539
|
+
function sleep(ms) {
|
|
540
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
541
|
+
}
|
|
542
|
+
var import_chalk5, import_ora7, import_inquirer2, import_open;
|
|
491
543
|
var init_auth = __esm({
|
|
492
544
|
"src/commands/auth.ts"() {
|
|
493
545
|
"use strict";
|
|
494
546
|
import_chalk5 = __toESM(require("chalk"));
|
|
495
547
|
import_ora7 = __toESM(require("ora"));
|
|
496
548
|
import_inquirer2 = __toESM(require("inquirer"));
|
|
549
|
+
import_open = __toESM(require("open"));
|
|
497
550
|
init_config();
|
|
498
|
-
init_api();
|
|
499
551
|
}
|
|
500
552
|
});
|
|
501
553
|
|
|
@@ -531,13 +583,13 @@ function mapSubscriptionToCCPlan(subscriptionType) {
|
|
|
531
583
|
if (type === "max" || type.includes("max")) {
|
|
532
584
|
return "max";
|
|
533
585
|
}
|
|
534
|
-
if (type === "team" || type === "enterprise") {
|
|
535
|
-
return "team";
|
|
536
|
-
}
|
|
537
586
|
if (type === "pro") {
|
|
538
587
|
return "pro";
|
|
539
588
|
}
|
|
540
|
-
|
|
589
|
+
if (type === "free") {
|
|
590
|
+
return "free";
|
|
591
|
+
}
|
|
592
|
+
return type;
|
|
541
593
|
}
|
|
542
594
|
function readCredentials() {
|
|
543
595
|
const credentialsPath = getCredentialsPath();
|
|
@@ -624,11 +676,19 @@ function estimateCost(model, inputTokens, outputTokens) {
|
|
|
624
676
|
const outputCost = outputTokens / 1e6 * price.output;
|
|
625
677
|
return Math.round((inputCost + outputCost) * 100) / 100;
|
|
626
678
|
}
|
|
627
|
-
function scanUsageData() {
|
|
679
|
+
function scanUsageData(options = {}) {
|
|
628
680
|
const projectsDir = getClaudeProjectsDir();
|
|
629
681
|
if (!fs2.existsSync(projectsDir)) {
|
|
630
682
|
return null;
|
|
631
683
|
}
|
|
684
|
+
const days = options.days ?? 30;
|
|
685
|
+
let cutoffDate = null;
|
|
686
|
+
if (days > 0) {
|
|
687
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
688
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
689
|
+
cutoff.setHours(0, 0, 0, 0);
|
|
690
|
+
cutoffDate = cutoff.toISOString();
|
|
691
|
+
}
|
|
632
692
|
let totalInputTokens = 0;
|
|
633
693
|
let totalOutputTokens = 0;
|
|
634
694
|
let totalCacheRead = 0;
|
|
@@ -661,6 +721,9 @@ function scanUsageData() {
|
|
|
661
721
|
try {
|
|
662
722
|
const event = JSON.parse(line);
|
|
663
723
|
if (event.type === "assistant" && event.message?.usage) {
|
|
724
|
+
if (cutoffDate && event.timestamp && event.timestamp < cutoffDate) {
|
|
725
|
+
continue;
|
|
726
|
+
}
|
|
664
727
|
const usage = event.message.usage;
|
|
665
728
|
const model = event.message.model || "unknown";
|
|
666
729
|
const inputTokens = usage.input_tokens || 0;
|
|
@@ -773,8 +836,8 @@ function writeCCGatherJson(data) {
|
|
|
773
836
|
}
|
|
774
837
|
fs2.writeFileSync(jsonPath, JSON.stringify(data, null, 2));
|
|
775
838
|
}
|
|
776
|
-
function scanAndSave() {
|
|
777
|
-
const data = scanUsageData();
|
|
839
|
+
function scanAndSave(options = {}) {
|
|
840
|
+
const data = scanUsageData(options);
|
|
778
841
|
if (data) {
|
|
779
842
|
writeCCGatherJson(data);
|
|
780
843
|
}
|
|
@@ -876,10 +939,10 @@ function getRankMedal(rank) {
|
|
|
876
939
|
function getCCplanBadge(ccplan) {
|
|
877
940
|
if (!ccplan) return "";
|
|
878
941
|
const badges = {
|
|
879
|
-
max: `${colors.max("\u{
|
|
880
|
-
pro: `${colors.pro("\
|
|
881
|
-
team: `${colors.team("\u{
|
|
882
|
-
free: `${colors.free("\
|
|
942
|
+
max: `${colors.max("\u{1F680} MAX")}`,
|
|
943
|
+
pro: `${colors.pro("\u26A1 PRO")}`,
|
|
944
|
+
team: `${colors.team("\u{1F465} TEAM")}`,
|
|
945
|
+
free: `${colors.free("\u26AA FREE")}`
|
|
883
946
|
};
|
|
884
947
|
return badges[ccplan.toLowerCase()] || "";
|
|
885
948
|
}
|
|
@@ -904,9 +967,7 @@ function getLevelInfo(tokens) {
|
|
|
904
967
|
function createWelcomeBox(user) {
|
|
905
968
|
const levelInfo = user.level && user.levelName && user.levelIcon ? `${user.levelIcon} Level ${user.level} \u2022 ${user.levelName}` : "";
|
|
906
969
|
const ccplanBadge = user.ccplan ? getCCplanBadge(user.ccplan) : "";
|
|
907
|
-
const lines = [
|
|
908
|
-
`\u{1F44B} ${colors.white.bold(`Welcome back, ${user.username}!`)}`
|
|
909
|
-
];
|
|
970
|
+
const lines = [`\u{1F44B} ${colors.white.bold(`Welcome back, ${user.username}!`)}`];
|
|
910
971
|
if (levelInfo || ccplanBadge) {
|
|
911
972
|
lines.push(`${levelInfo}${ccplanBadge ? ` ${ccplanBadge}` : ""}`);
|
|
912
973
|
}
|
|
@@ -1001,34 +1062,21 @@ function calculateDaysTracked(data) {
|
|
|
1001
1062
|
}
|
|
1002
1063
|
return 1;
|
|
1003
1064
|
}
|
|
1004
|
-
async function
|
|
1005
|
-
try {
|
|
1006
|
-
const { execSync } = await import("child_process");
|
|
1007
|
-
try {
|
|
1008
|
-
const username = execSync("git config --get user.name", { encoding: "utf-8" }).trim();
|
|
1009
|
-
if (username) return username;
|
|
1010
|
-
} catch {
|
|
1011
|
-
}
|
|
1012
|
-
try {
|
|
1013
|
-
const remote = execSync("git remote get-url origin", { encoding: "utf-8" }).trim();
|
|
1014
|
-
const match = remote.match(/github\.com[:/]([^/]+)/);
|
|
1015
|
-
if (match) return match[1];
|
|
1016
|
-
} catch {
|
|
1017
|
-
}
|
|
1018
|
-
} catch {
|
|
1019
|
-
}
|
|
1020
|
-
return null;
|
|
1021
|
-
}
|
|
1022
|
-
async function submitToServer(username, data) {
|
|
1065
|
+
async function submitToServer(data) {
|
|
1023
1066
|
const apiUrl = getApiUrl();
|
|
1067
|
+
const config = getConfig();
|
|
1068
|
+
const apiToken = config.get("apiToken");
|
|
1069
|
+
if (!apiToken) {
|
|
1070
|
+
return { success: false, error: "Not authenticated. Please run 'ccgather auth' first." };
|
|
1071
|
+
}
|
|
1024
1072
|
try {
|
|
1025
1073
|
const response = await fetch(`${apiUrl}/cli/submit`, {
|
|
1026
1074
|
method: "POST",
|
|
1027
1075
|
headers: {
|
|
1028
|
-
"Content-Type": "application/json"
|
|
1076
|
+
"Content-Type": "application/json",
|
|
1077
|
+
Authorization: `Bearer ${apiToken}`
|
|
1029
1078
|
},
|
|
1030
1079
|
body: JSON.stringify({
|
|
1031
|
-
username,
|
|
1032
1080
|
totalTokens: data.totalTokens,
|
|
1033
1081
|
totalSpent: data.totalCost,
|
|
1034
1082
|
inputTokens: data.inputTokens,
|
|
@@ -1052,29 +1100,21 @@ async function submitToServer(username, data) {
|
|
|
1052
1100
|
}
|
|
1053
1101
|
}
|
|
1054
1102
|
async function submit(options) {
|
|
1055
|
-
printCompactHeader("1.2.
|
|
1103
|
+
printCompactHeader("1.2.1");
|
|
1056
1104
|
console.log(header("Submit Usage Data", "\u{1F4E4}"));
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
|
|
1105
|
+
if (!isAuthenticated()) {
|
|
1106
|
+
console.log(`
|
|
1107
|
+
${error("Not authenticated.")}`);
|
|
1108
|
+
console.log(` ${colors.muted("Please run:")} ${colors.white("npx ccgather auth")}
|
|
1109
|
+
`);
|
|
1110
|
+
process.exit(1);
|
|
1111
|
+
}
|
|
1112
|
+
const config = getConfig();
|
|
1113
|
+
const username = config.get("username");
|
|
1063
1114
|
if (username) {
|
|
1064
1115
|
console.log(`
|
|
1065
|
-
${colors.muted("
|
|
1116
|
+
${colors.muted("Logged in as:")} ${colors.white(username)}`);
|
|
1066
1117
|
}
|
|
1067
|
-
const inquirer4 = await import("inquirer");
|
|
1068
|
-
const { confirmedUsername } = await inquirer4.default.prompt([
|
|
1069
|
-
{
|
|
1070
|
-
type: "input",
|
|
1071
|
-
name: "confirmedUsername",
|
|
1072
|
-
message: colors.muted("GitHub username:"),
|
|
1073
|
-
default: username || "",
|
|
1074
|
-
validate: (input) => input.trim().length > 0 || "Username is required"
|
|
1075
|
-
}
|
|
1076
|
-
]);
|
|
1077
|
-
username = confirmedUsername.trim();
|
|
1078
1118
|
let usageData = null;
|
|
1079
1119
|
let dataSource = "";
|
|
1080
1120
|
const ccgatherData = readCCGatherJson();
|
|
@@ -1083,7 +1123,9 @@ async function submit(options) {
|
|
|
1083
1123
|
dataSource = "ccgather.json";
|
|
1084
1124
|
console.log(`
|
|
1085
1125
|
${success(`Found ${dataSource}`)}`);
|
|
1086
|
-
console.log(
|
|
1126
|
+
console.log(
|
|
1127
|
+
` ${colors.dim(`Last scanned: ${new Date(ccgatherData.lastScanned).toLocaleString()}`)}`
|
|
1128
|
+
);
|
|
1087
1129
|
if (usageData.ccplan) {
|
|
1088
1130
|
console.log(` ${colors.dim("CCplan:")} ${colors.primary(usageData.ccplan.toUpperCase())}`);
|
|
1089
1131
|
}
|
|
@@ -1091,6 +1133,7 @@ async function submit(options) {
|
|
|
1091
1133
|
if (!usageData) {
|
|
1092
1134
|
const ccJsonPath = findCcJson();
|
|
1093
1135
|
if (ccJsonPath) {
|
|
1136
|
+
const inquirer4 = await import("inquirer");
|
|
1094
1137
|
const { useCcJson } = await inquirer4.default.prompt([
|
|
1095
1138
|
{
|
|
1096
1139
|
type: "confirm",
|
|
@@ -1132,6 +1175,25 @@ async function submit(options) {
|
|
|
1132
1175
|
console.log(`
|
|
1133
1176
|
${success(`Using ${dataSource}`)}`);
|
|
1134
1177
|
}
|
|
1178
|
+
if (!usageData.ccplan) {
|
|
1179
|
+
const inquirer4 = await import("inquirer");
|
|
1180
|
+
const { selectedCCplan } = await inquirer4.default.prompt([
|
|
1181
|
+
{
|
|
1182
|
+
type: "list",
|
|
1183
|
+
name: "selectedCCplan",
|
|
1184
|
+
message: colors.muted("Select your Claude plan:"),
|
|
1185
|
+
choices: [
|
|
1186
|
+
{ name: "\u{1F680} Max", value: "max" },
|
|
1187
|
+
{ name: "\u26A1 Pro", value: "pro" },
|
|
1188
|
+
{ name: "\u26AA Free", value: "free" },
|
|
1189
|
+
{ name: "\u{1F465} Team / Enterprise", value: "team" },
|
|
1190
|
+
{ name: "\u23ED\uFE0F Skip", value: null }
|
|
1191
|
+
],
|
|
1192
|
+
default: "free"
|
|
1193
|
+
}
|
|
1194
|
+
]);
|
|
1195
|
+
usageData.ccplan = selectedCCplan;
|
|
1196
|
+
}
|
|
1135
1197
|
console.log();
|
|
1136
1198
|
const summaryLines = [
|
|
1137
1199
|
`${colors.muted("Total Cost")} ${colors.success(formatCost(usageData.totalCost))}`,
|
|
@@ -1139,11 +1201,14 @@ async function submit(options) {
|
|
|
1139
1201
|
`${colors.muted("Days Tracked")} ${colors.warning(usageData.daysTracked.toString())}`
|
|
1140
1202
|
];
|
|
1141
1203
|
if (usageData.ccplan) {
|
|
1142
|
-
summaryLines.push(
|
|
1204
|
+
summaryLines.push(
|
|
1205
|
+
`${colors.muted("CCplan")} ${colors.cyan(usageData.ccplan.toUpperCase())}`
|
|
1206
|
+
);
|
|
1143
1207
|
}
|
|
1144
1208
|
console.log(createBox(summaryLines));
|
|
1145
1209
|
console.log();
|
|
1146
1210
|
if (!options.yes) {
|
|
1211
|
+
const inquirer4 = await import("inquirer");
|
|
1147
1212
|
const { confirmSubmit } = await inquirer4.default.prompt([
|
|
1148
1213
|
{
|
|
1149
1214
|
type: "confirm",
|
|
@@ -1163,27 +1228,31 @@ async function submit(options) {
|
|
|
1163
1228
|
text: "Submitting to CCgather...",
|
|
1164
1229
|
color: "cyan"
|
|
1165
1230
|
}).start();
|
|
1166
|
-
const result = await submitToServer(
|
|
1231
|
+
const result = await submitToServer(usageData);
|
|
1167
1232
|
if (result.success) {
|
|
1168
1233
|
submitSpinner.succeed(colors.success("Successfully submitted to CCgather!"));
|
|
1169
1234
|
console.log();
|
|
1170
1235
|
const successLines = [
|
|
1171
1236
|
`${colors.success("\u2713")} ${colors.white.bold("Submission Complete!")}`,
|
|
1172
1237
|
"",
|
|
1173
|
-
`${colors.muted("Profile:")} ${link(result.profileUrl || `https://ccgather.
|
|
1238
|
+
`${colors.muted("Profile:")} ${link(result.profileUrl || `https://ccgather.com/u/${username}`)}`
|
|
1174
1239
|
];
|
|
1175
1240
|
if (result.rank) {
|
|
1176
1241
|
successLines.push(`${colors.muted("Rank:")} ${colors.warning(`#${result.rank}`)}`);
|
|
1177
1242
|
}
|
|
1178
1243
|
console.log(createBox(successLines));
|
|
1179
1244
|
console.log();
|
|
1180
|
-
console.log(` ${colors.dim("View leaderboard:")} ${link("https://ccgather.
|
|
1245
|
+
console.log(` ${colors.dim("View leaderboard:")} ${link("https://ccgather.com/leaderboard")}`);
|
|
1181
1246
|
console.log();
|
|
1182
1247
|
} else {
|
|
1183
1248
|
submitSpinner.fail(colors.error("Failed to submit"));
|
|
1184
1249
|
console.log(`
|
|
1185
|
-
${error(result.error || "Unknown error")}
|
|
1186
|
-
|
|
1250
|
+
${error(result.error || "Unknown error")}`);
|
|
1251
|
+
if (result.error?.includes("auth") || result.error?.includes("token")) {
|
|
1252
|
+
console.log(`
|
|
1253
|
+
${colors.muted("Try running:")} ${colors.white("npx ccgather auth")}`);
|
|
1254
|
+
}
|
|
1255
|
+
console.log();
|
|
1187
1256
|
process.exit(1);
|
|
1188
1257
|
}
|
|
1189
1258
|
}
|
|
@@ -1226,7 +1295,7 @@ async function status(options) {
|
|
|
1226
1295
|
return;
|
|
1227
1296
|
}
|
|
1228
1297
|
spinner?.succeed(colors.success("Status retrieved"));
|
|
1229
|
-
printCompactHeader("1.2.
|
|
1298
|
+
printCompactHeader("1.2.1");
|
|
1230
1299
|
console.log(header("Your CCgather Stats", "\u{1F4CA}"));
|
|
1231
1300
|
const levelInfo = getLevelInfo(stats.totalTokens);
|
|
1232
1301
|
const medal = getRankMedal(stats.rank);
|
|
@@ -1234,7 +1303,9 @@ async function status(options) {
|
|
|
1234
1303
|
console.log(` ${medal} ${colors.white.bold(`Rank #${stats.rank}`)}`);
|
|
1235
1304
|
console.log(` ${colors.dim(`Top ${stats.percentile.toFixed(1)}% of all users`)}`);
|
|
1236
1305
|
console.log();
|
|
1237
|
-
console.log(
|
|
1306
|
+
console.log(
|
|
1307
|
+
` ${levelInfo.icon} ${levelInfo.color(`Level ${levelInfo.level} \u2022 ${levelInfo.name}`)}`
|
|
1308
|
+
);
|
|
1238
1309
|
if (stats.tier) {
|
|
1239
1310
|
const badge = getCCplanBadge(stats.tier);
|
|
1240
1311
|
if (badge) {
|
|
@@ -1273,8 +1344,8 @@ function getClaudeSettingsDir2() {
|
|
|
1273
1344
|
return path5.join(os5.homedir(), ".claude");
|
|
1274
1345
|
}
|
|
1275
1346
|
async function openBrowser(url) {
|
|
1276
|
-
const { default:
|
|
1277
|
-
await
|
|
1347
|
+
const { default: open2 } = await import("open");
|
|
1348
|
+
await open2(url);
|
|
1278
1349
|
}
|
|
1279
1350
|
function createCallbackServer() {
|
|
1280
1351
|
return new Promise((resolve, reject) => {
|
|
@@ -1655,8 +1726,12 @@ function displayResults(data) {
|
|
|
1655
1726
|
`${colors.muted("Output Tokens")} ${colors.white(formatNumber(data.usage.outputTokens))}`
|
|
1656
1727
|
];
|
|
1657
1728
|
if (data.usage.cacheReadTokens > 0 || data.usage.cacheWriteTokens > 0) {
|
|
1658
|
-
usageLines.push(
|
|
1659
|
-
|
|
1729
|
+
usageLines.push(
|
|
1730
|
+
`${colors.muted("Cache Read")} ${colors.dim(formatNumber(data.usage.cacheReadTokens))}`
|
|
1731
|
+
);
|
|
1732
|
+
usageLines.push(
|
|
1733
|
+
`${colors.muted("Cache Write")} ${colors.dim(formatNumber(data.usage.cacheWriteTokens))}`
|
|
1734
|
+
);
|
|
1660
1735
|
}
|
|
1661
1736
|
console.log(createBox(usageLines));
|
|
1662
1737
|
console.log();
|
|
@@ -1684,7 +1759,9 @@ function displayResults(data) {
|
|
|
1684
1759
|
const sortedModels = Object.entries(data.models).sort(([, a], [, b]) => b - a).slice(0, 5);
|
|
1685
1760
|
for (const [model, tokens] of sortedModels) {
|
|
1686
1761
|
const shortModel = model.replace("claude-", "").substring(0, 20);
|
|
1687
|
-
console.log(
|
|
1762
|
+
console.log(
|
|
1763
|
+
` ${colors.dim("\u2022")} ${colors.white(shortModel.padEnd(22))} ${colors.primary(formatNumber(tokens))}`
|
|
1764
|
+
);
|
|
1688
1765
|
}
|
|
1689
1766
|
if (Object.keys(data.models).length > 5) {
|
|
1690
1767
|
console.log(` ${colors.dim(`... and ${Object.keys(data.models).length - 5} more models`)}`);
|
|
@@ -1702,7 +1779,9 @@ function displayResults(data) {
|
|
|
1702
1779
|
);
|
|
1703
1780
|
}
|
|
1704
1781
|
if (Object.keys(data.projects).length > 5) {
|
|
1705
|
-
console.log(
|
|
1782
|
+
console.log(
|
|
1783
|
+
` ${colors.dim(`... and ${Object.keys(data.projects).length - 5} more projects`)}`
|
|
1784
|
+
);
|
|
1706
1785
|
}
|
|
1707
1786
|
}
|
|
1708
1787
|
console.log();
|
|
@@ -1710,14 +1789,28 @@ function displayResults(data) {
|
|
|
1710
1789
|
console.log(` ${colors.muted("Saved to:")} ${colors.dim(getCCGatherJsonPath())}`);
|
|
1711
1790
|
console.log();
|
|
1712
1791
|
}
|
|
1713
|
-
async function scan() {
|
|
1714
|
-
printCompactHeader("1.2.
|
|
1792
|
+
async function scan(options = {}) {
|
|
1793
|
+
printCompactHeader("1.2.1");
|
|
1715
1794
|
console.log(header("Scan Claude Code Usage", "\u{1F50D}"));
|
|
1795
|
+
let days;
|
|
1796
|
+
if (options.all) {
|
|
1797
|
+
days = 0;
|
|
1798
|
+
console.log(` ${colors.muted("Mode:")} ${colors.primary("All time")} (no date limit)
|
|
1799
|
+
`);
|
|
1800
|
+
} else if (options.days) {
|
|
1801
|
+
days = options.days;
|
|
1802
|
+
console.log(` ${colors.muted("Mode:")} ${colors.primary(`Last ${days} days`)}
|
|
1803
|
+
`);
|
|
1804
|
+
} else {
|
|
1805
|
+
days = 30;
|
|
1806
|
+
console.log(` ${colors.muted("Mode:")} ${colors.primary("Last 30 days")} (default)
|
|
1807
|
+
`);
|
|
1808
|
+
}
|
|
1716
1809
|
const spinner = (0, import_ora5.default)({
|
|
1717
1810
|
text: "Scanning JSONL files...",
|
|
1718
1811
|
color: "cyan"
|
|
1719
1812
|
}).start();
|
|
1720
|
-
const data = scanAndSave();
|
|
1813
|
+
const data = scanAndSave({ days });
|
|
1721
1814
|
if (!data) {
|
|
1722
1815
|
spinner.fail(colors.error("No usage data found"));
|
|
1723
1816
|
console.log();
|
|
@@ -1732,15 +1825,20 @@ async function scan() {
|
|
|
1732
1825
|
displayResults(data);
|
|
1733
1826
|
console.log(` ${colors.muted("Next:")} Run ${colors.white("npx ccgather")} to submit your data`);
|
|
1734
1827
|
console.log();
|
|
1828
|
+
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1829
|
+
console.log(
|
|
1830
|
+
` ${colors.muted("Tip:")} Use ${colors.white("--all")} for all-time data or ${colors.white("--days <n>")} for custom range`
|
|
1831
|
+
);
|
|
1832
|
+
console.log();
|
|
1735
1833
|
}
|
|
1736
1834
|
|
|
1737
1835
|
// src/index.ts
|
|
1738
1836
|
init_config();
|
|
1739
1837
|
init_api();
|
|
1740
|
-
var VERSION = "1.2.
|
|
1838
|
+
var VERSION = "1.2.1";
|
|
1741
1839
|
var program = new import_commander.Command();
|
|
1742
1840
|
program.name("ccgather").description("Submit your Claude Code usage to the CCgather leaderboard").version(VERSION).option("-y, --yes", "Skip confirmation prompt").option("--auto", "Enable automatic sync on session end").option("--manual", "Disable automatic sync").option("--no-menu", "Skip interactive menu (direct submit)");
|
|
1743
|
-
program.command("scan").description("Scan Claude Code usage and create ccgather.json").action(scan);
|
|
1841
|
+
program.command("scan").description("Scan Claude Code usage and create ccgather.json").option("-a, --all", "Scan all-time usage (no date limit)").option("-d, --days <number>", "Number of days to scan (default: 30)", parseInt).action((opts) => scan(opts));
|
|
1744
1842
|
program.command("rank").description("View your current rank and stats").action(() => status({ json: false }));
|
|
1745
1843
|
program.command("status").description("View your current rank and stats").option("--json", "Output as JSON").action((opts) => status(opts));
|
|
1746
1844
|
program.command("sync").description("Sync usage data to CCgather").action(async () => {
|
|
@@ -1757,6 +1855,25 @@ program.command("reset").description("Remove auto-sync hook and clear config").a
|
|
|
1757
1855
|
});
|
|
1758
1856
|
async function showInteractiveMenu() {
|
|
1759
1857
|
printHeader(VERSION);
|
|
1858
|
+
if (!isAuthenticated()) {
|
|
1859
|
+
console.log(colors.warning("\n \u{1F510} Authentication required\n"));
|
|
1860
|
+
console.log(colors.dim(" To submit your Claude Code usage, you need to log in first.\n"));
|
|
1861
|
+
const { startAuth } = await import_inquirer3.default.prompt([
|
|
1862
|
+
{
|
|
1863
|
+
type: "confirm",
|
|
1864
|
+
name: "startAuth",
|
|
1865
|
+
message: "Would you like to authenticate now?",
|
|
1866
|
+
default: true
|
|
1867
|
+
}
|
|
1868
|
+
]);
|
|
1869
|
+
if (startAuth) {
|
|
1870
|
+
const { auth: auth2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
1871
|
+
await auth2({});
|
|
1872
|
+
console.log();
|
|
1873
|
+
} else {
|
|
1874
|
+
console.log(colors.dim("\n You can authenticate later by running: npx ccgather auth\n"));
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1760
1877
|
if (isAuthenticated()) {
|
|
1761
1878
|
try {
|
|
1762
1879
|
const result = await getStatus();
|
|
@@ -1776,8 +1893,6 @@ async function showInteractiveMenu() {
|
|
|
1776
1893
|
}
|
|
1777
1894
|
} catch {
|
|
1778
1895
|
}
|
|
1779
|
-
} else {
|
|
1780
|
-
console.log(colors.dim(' Not logged in. Run "ccgather auth" to authenticate.\n'));
|
|
1781
1896
|
}
|
|
1782
1897
|
const { action } = await import_inquirer3.default.prompt([
|
|
1783
1898
|
{
|
|
@@ -1891,10 +2006,18 @@ async function showSettingsMenu() {
|
|
|
1891
2006
|
console.log();
|
|
1892
2007
|
console.log(colors.muted(" Current Configuration:"));
|
|
1893
2008
|
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1894
|
-
console.log(
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
console.log(
|
|
2009
|
+
console.log(
|
|
2010
|
+
` ${colors.muted("Username:")} ${config.get("username") || colors.dim("(not set)")}`
|
|
2011
|
+
);
|
|
2012
|
+
console.log(
|
|
2013
|
+
` ${colors.muted("API Token:")} ${config.get("apiToken") ? colors.success("\u2713 Set") : colors.error("\u2717 Not set")}`
|
|
2014
|
+
);
|
|
2015
|
+
console.log(
|
|
2016
|
+
` ${colors.muted("Auto-sync:")} ${config.get("autoSync") ? colors.success("Enabled") : colors.dim("Disabled")}`
|
|
2017
|
+
);
|
|
2018
|
+
console.log(
|
|
2019
|
+
` ${colors.muted("Last Sync:")} ${config.get("lastSync") || colors.dim("Never")}`
|
|
2020
|
+
);
|
|
1898
2021
|
console.log();
|
|
1899
2022
|
await promptContinue();
|
|
1900
2023
|
await showSettingsMenu();
|
|
@@ -1908,22 +2031,30 @@ function showHelp() {
|
|
|
1908
2031
|
console.log(colors.primary.bold(" CCgather CLI Commands"));
|
|
1909
2032
|
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1910
2033
|
console.log();
|
|
1911
|
-
console.log(` ${colors.white("npx ccgather")}
|
|
1912
|
-
console.log(
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
console.log(` ${colors.white("npx ccgather
|
|
1916
|
-
console.log(` ${colors.white("npx ccgather
|
|
2034
|
+
console.log(` ${colors.white("npx ccgather")} Interactive menu (default)`);
|
|
2035
|
+
console.log(
|
|
2036
|
+
` ${colors.white("npx ccgather scan")} Scan local Claude Code usage (last 30 days)`
|
|
2037
|
+
);
|
|
2038
|
+
console.log(` ${colors.white("npx ccgather scan --all")} Scan all-time usage (no date limit)`);
|
|
2039
|
+
console.log(` ${colors.white("npx ccgather scan -d 90")} Scan last 90 days of usage`);
|
|
2040
|
+
console.log(` ${colors.white("npx ccgather rank")} View your current rank`);
|
|
2041
|
+
console.log(` ${colors.white("npx ccgather sync")} Sync usage data`);
|
|
2042
|
+
console.log(` ${colors.white("npx ccgather auth")} Authenticate with CCgather`);
|
|
2043
|
+
console.log(` ${colors.white("npx ccgather reset")} Reset all settings`);
|
|
1917
2044
|
console.log();
|
|
1918
2045
|
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1919
|
-
console.log(` ${colors.muted("Options:")}`);
|
|
2046
|
+
console.log(` ${colors.muted("Scan Options:")}`);
|
|
2047
|
+
console.log(` ${colors.white("-a, --all")} Scan all-time usage (no date limit)`);
|
|
2048
|
+
console.log(` ${colors.white("-d, --days <n>")} Scan last N days (default: 30)`);
|
|
2049
|
+
console.log();
|
|
2050
|
+
console.log(` ${colors.muted("General Options:")}`);
|
|
1920
2051
|
console.log(` ${colors.white("-y, --yes")} Skip confirmation prompts`);
|
|
1921
2052
|
console.log(` ${colors.white("--auto")} Enable auto-sync`);
|
|
1922
2053
|
console.log(` ${colors.white("--manual")} Disable auto-sync`);
|
|
1923
2054
|
console.log(` ${colors.white("--no-menu")} Skip interactive menu`);
|
|
1924
2055
|
console.log();
|
|
1925
|
-
console.log(` ${colors.muted("Documentation:")} ${link("https://ccgather.
|
|
1926
|
-
console.log(` ${colors.muted("Leaderboard:")} ${link("https://ccgather.
|
|
2056
|
+
console.log(` ${colors.muted("Documentation:")} ${link("https://ccgather.com/docs")}`);
|
|
2057
|
+
console.log(` ${colors.muted("Leaderboard:")} ${link("https://ccgather.com/leaderboard")}`);
|
|
1927
2058
|
console.log();
|
|
1928
2059
|
}
|
|
1929
2060
|
async function promptContinue() {
|
package/package.json
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ccgather",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "CLI tool for syncing Claude Code usage data to CCgather leaderboard",
|
|
5
|
-
"bin": {
|
|
6
|
-
"ccgather": "
|
|
7
|
-
"ccg": "
|
|
8
|
-
},
|
|
9
|
-
"main": "./dist/index.js",
|
|
10
|
-
"types": "./dist/index.d.ts",
|
|
11
|
-
"scripts": {
|
|
12
|
-
"build": "tsup src/index.ts --format cjs --dts",
|
|
13
|
-
"dev": "tsup src/index.ts --format cjs --dts --watch",
|
|
14
|
-
"start": "node dist/index.js",
|
|
15
|
-
"typecheck": "tsc --noEmit"
|
|
16
|
-
},
|
|
17
|
-
"keywords": [
|
|
18
|
-
"claude",
|
|
19
|
-
"anthropic",
|
|
20
|
-
"claude-code",
|
|
21
|
-
"ccgather",
|
|
22
|
-
"leaderboard",
|
|
23
|
-
"cli"
|
|
24
|
-
],
|
|
25
|
-
"author": "",
|
|
26
|
-
"license": "MIT",
|
|
27
|
-
"dependencies": {
|
|
28
|
-
"chalk": "^5.3.0",
|
|
29
|
-
"commander": "^12.1.0",
|
|
30
|
-
"conf": "^13.0.1",
|
|
31
|
-
"inquirer": "^10.2.2",
|
|
32
|
-
"open": "^10.1.0",
|
|
33
|
-
"ora": "^8.1.0"
|
|
34
|
-
},
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@types/inquirer": "^9.0.7",
|
|
37
|
-
"@types/node": "^22.10.2",
|
|
38
|
-
"tsup": "^8.3.5",
|
|
39
|
-
"typescript": "^5.7.2"
|
|
40
|
-
},
|
|
41
|
-
"engines": {
|
|
42
|
-
"node": ">=18"
|
|
43
|
-
},
|
|
44
|
-
"files": [
|
|
45
|
-
"dist"
|
|
46
|
-
],
|
|
47
|
-
"repository": {
|
|
48
|
-
"type": "git",
|
|
49
|
-
"url": "https://github.com/DHxYoon/CCgather"
|
|
50
|
-
}
|
|
51
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "ccgather",
|
|
3
|
+
"version": "1.3.0",
|
|
4
|
+
"description": "CLI tool for syncing Claude Code usage data to CCgather leaderboard",
|
|
5
|
+
"bin": {
|
|
6
|
+
"ccgather": "dist/index.js",
|
|
7
|
+
"ccg": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup src/index.ts --format cjs --dts",
|
|
13
|
+
"dev": "tsup src/index.ts --format cjs --dts --watch",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"claude",
|
|
19
|
+
"anthropic",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"ccgather",
|
|
22
|
+
"leaderboard",
|
|
23
|
+
"cli"
|
|
24
|
+
],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"chalk": "^5.3.0",
|
|
29
|
+
"commander": "^12.1.0",
|
|
30
|
+
"conf": "^13.0.1",
|
|
31
|
+
"inquirer": "^10.2.2",
|
|
32
|
+
"open": "^10.1.0",
|
|
33
|
+
"ora": "^8.1.0"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@types/inquirer": "^9.0.7",
|
|
37
|
+
"@types/node": "^22.10.2",
|
|
38
|
+
"tsup": "^8.3.5",
|
|
39
|
+
"typescript": "^5.7.2"
|
|
40
|
+
},
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist"
|
|
46
|
+
],
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/DHxYoon/CCgather.git"
|
|
50
|
+
}
|
|
51
|
+
}
|