ccgather 1.5.0 → 2.0.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 +143 -419
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -208,60 +208,9 @@ function success(message) {
|
|
|
208
208
|
function error(message) {
|
|
209
209
|
return `${colors.error("\u2717")} ${message}`;
|
|
210
210
|
}
|
|
211
|
-
function warning(message) {
|
|
212
|
-
return `${colors.warning("\u26A0")} ${message}`;
|
|
213
|
-
}
|
|
214
211
|
function link(url) {
|
|
215
212
|
return hyperlink(colors.cyan.underline(url), url);
|
|
216
213
|
}
|
|
217
|
-
function planDetectionSection(title, icon = "\u{1F50D}") {
|
|
218
|
-
return `
|
|
219
|
-
${colors.dim("\u2500".repeat(50))}
|
|
220
|
-
${icon} ${colors.white.bold(title)}
|
|
221
|
-
${colors.dim("\u2500".repeat(50))}`;
|
|
222
|
-
}
|
|
223
|
-
function maxVerifiedMessage(opusModels) {
|
|
224
|
-
const modelList = opusModels.length > 2 ? `${opusModels.slice(0, 2).join(", ")} +${opusModels.length - 2} more` : opusModels.join(", ");
|
|
225
|
-
return [
|
|
226
|
-
``,
|
|
227
|
-
` ${success("Max plan verified")}`,
|
|
228
|
-
``,
|
|
229
|
-
` ${colors.muted("\u{1F4A1} Reason:")} Opus model usage detected`,
|
|
230
|
-
` ${colors.dim("Models:")} ${colors.cyan(modelList)}`,
|
|
231
|
-
``,
|
|
232
|
-
` ${colors.dim("All data will be recorded under Max league.")}`,
|
|
233
|
-
``
|
|
234
|
-
];
|
|
235
|
-
}
|
|
236
|
-
function pastDataWarningMessage(oldestDate, daysSince) {
|
|
237
|
-
return [
|
|
238
|
-
``,
|
|
239
|
-
` ${warning(`Data older than 30 days detected`)}`,
|
|
240
|
-
``,
|
|
241
|
-
` ${colors.muted("Oldest record:")} ${colors.white(oldestDate)} ${colors.dim(`(${daysSince} days ago)`)}`,
|
|
242
|
-
` ${colors.muted("Opus usage:")} ${colors.dim("Not found")}`,
|
|
243
|
-
``,
|
|
244
|
-
` ${colors.dim("\u2192 Max plan cannot be verified automatically.")}`,
|
|
245
|
-
``
|
|
246
|
-
];
|
|
247
|
-
}
|
|
248
|
-
function trustMessage() {
|
|
249
|
-
return [
|
|
250
|
-
` ${colors.muted("\u{1F4A1} For fair league placement, we trust your choice.")}`,
|
|
251
|
-
` ${colors.dim("Tip: Submit regularly for accurate tracking!")}`,
|
|
252
|
-
``
|
|
253
|
-
];
|
|
254
|
-
}
|
|
255
|
-
function currentPlanMessage(plan) {
|
|
256
|
-
const planColor = plan === "max" ? colors.max : plan === "pro" ? colors.pro : colors.free;
|
|
257
|
-
const planBadge = plan === "max" ? "\u{1F680} MAX" : plan === "pro" ? "\u26A1 PRO" : "\u26AA FREE";
|
|
258
|
-
return [
|
|
259
|
-
``,
|
|
260
|
-
` ${colors.muted("\u{1F4CB} Plan:")} ${planColor(planBadge)}`,
|
|
261
|
-
` ${colors.dim("Current plan from credentials will be used.")}`,
|
|
262
|
-
``
|
|
263
|
-
];
|
|
264
|
-
}
|
|
265
214
|
function sleep(ms) {
|
|
266
215
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
267
216
|
}
|
|
@@ -380,7 +329,7 @@ var init_ui = __esm({
|
|
|
380
329
|
"use strict";
|
|
381
330
|
import_chalk = __toESM(require("chalk"));
|
|
382
331
|
import_string_width = __toESM(require("string-width"));
|
|
383
|
-
VERSION = true ? "
|
|
332
|
+
VERSION = true ? "2.0.0" : "0.0.0";
|
|
384
333
|
colors = {
|
|
385
334
|
primary: import_chalk.default.hex("#DA7756"),
|
|
386
335
|
// Claude coral
|
|
@@ -772,22 +721,6 @@ function hasOpusUsageInProject(dailyUsage) {
|
|
|
772
721
|
opusDates
|
|
773
722
|
};
|
|
774
723
|
}
|
|
775
|
-
function hasOldData(dailyUsage, days = 30) {
|
|
776
|
-
if (dailyUsage.length === 0) {
|
|
777
|
-
return { hasOldData: false, oldestDate: null, daysSinceOldest: 0 };
|
|
778
|
-
}
|
|
779
|
-
const sortedDates = dailyUsage.map((d) => d.date).sort((a, b) => a.localeCompare(b));
|
|
780
|
-
const oldestDate = sortedDates[0];
|
|
781
|
-
const oldestDateObj = new Date(oldestDate);
|
|
782
|
-
const now = /* @__PURE__ */ new Date();
|
|
783
|
-
const diffTime = now.getTime() - oldestDateObj.getTime();
|
|
784
|
-
const daysSinceOldest = Math.floor(diffTime / (1e3 * 60 * 60 * 24));
|
|
785
|
-
return {
|
|
786
|
-
hasOldData: daysSinceOldest > days,
|
|
787
|
-
oldestDate,
|
|
788
|
-
daysSinceOldest
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
724
|
var CCGATHER_JSON_VERSION = "1.2.0";
|
|
792
725
|
function extractProjectName(filePath) {
|
|
793
726
|
const parts = filePath.split(/[/\\]/);
|
|
@@ -856,55 +789,6 @@ function encodePathLikeClaude(inputPath) {
|
|
|
856
789
|
return "-";
|
|
857
790
|
}).join("");
|
|
858
791
|
}
|
|
859
|
-
function getCurrentProjectDir() {
|
|
860
|
-
const projectsDirs = getClaudeProjectsDirs();
|
|
861
|
-
if (projectsDirs.length === 0) {
|
|
862
|
-
debugLog("No project directories found");
|
|
863
|
-
return null;
|
|
864
|
-
}
|
|
865
|
-
const cwd = process.cwd();
|
|
866
|
-
const projectName = path2.basename(cwd);
|
|
867
|
-
const encodedCwd = encodePathLikeClaude(cwd);
|
|
868
|
-
debugLog("Looking for project:", projectName);
|
|
869
|
-
debugLog("Encoded CWD:", encodedCwd);
|
|
870
|
-
for (const projectsDir of projectsDirs) {
|
|
871
|
-
try {
|
|
872
|
-
const entries = fs2.readdirSync(projectsDir, { withFileTypes: true });
|
|
873
|
-
debugLog(`Scanning ${projectsDir}: ${entries.length} entries`);
|
|
874
|
-
for (const entry of entries) {
|
|
875
|
-
if (!entry.isDirectory()) continue;
|
|
876
|
-
const folderName = entry.name;
|
|
877
|
-
const folderNameLower = folderName.toLowerCase();
|
|
878
|
-
const projectNameLower = projectName.toLowerCase();
|
|
879
|
-
const encodedCwdLower = encodedCwd.toLowerCase();
|
|
880
|
-
if (folderName === encodedCwd || folderNameLower === encodedCwdLower) {
|
|
881
|
-
debugLog("Match found (exact):", folderName);
|
|
882
|
-
return path2.join(projectsDir, entry.name);
|
|
883
|
-
}
|
|
884
|
-
if (folderNameLower.includes(projectNameLower)) {
|
|
885
|
-
debugLog("Match found (contains project name):", folderName);
|
|
886
|
-
return path2.join(projectsDir, entry.name);
|
|
887
|
-
}
|
|
888
|
-
const projectNameHyphenated = projectNameLower.replace(/_/g, "-");
|
|
889
|
-
if (folderNameLower.includes(projectNameHyphenated)) {
|
|
890
|
-
debugLog("Match found (hyphenated project name):", folderName);
|
|
891
|
-
return path2.join(projectsDir, entry.name);
|
|
892
|
-
}
|
|
893
|
-
const projectNamePattern = projectNameLower.replace(/[^a-z0-9]/g, "");
|
|
894
|
-
const folderNamePattern = folderNameLower.replace(/[^a-z0-9]/g, "");
|
|
895
|
-
if (folderNamePattern.endsWith(projectNamePattern) && projectNamePattern.length > 3) {
|
|
896
|
-
debugLog("Match found (ends with pattern):", folderName);
|
|
897
|
-
return path2.join(projectsDir, entry.name);
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
} catch (err) {
|
|
901
|
-
debugLog("Error scanning directory:", err);
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
debugLog("No matching project directory found");
|
|
906
|
-
return null;
|
|
907
|
-
}
|
|
908
792
|
function findJsonlFiles(dir) {
|
|
909
793
|
const files = [];
|
|
910
794
|
try {
|
|
@@ -969,44 +853,53 @@ function generateSessionFingerprint(sessionFiles) {
|
|
|
969
853
|
sessionCount: sessionHashes.length
|
|
970
854
|
};
|
|
971
855
|
}
|
|
972
|
-
function
|
|
973
|
-
|
|
974
|
-
}
|
|
975
|
-
function getProjectPath() {
|
|
976
|
-
return getCurrentProjectDir();
|
|
977
|
-
}
|
|
978
|
-
function getCurrentProjectName() {
|
|
856
|
+
function getSessionPathDebugInfo() {
|
|
857
|
+
const home = os2.homedir();
|
|
979
858
|
const cwd = process.cwd();
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
859
|
+
const encodedCwd = encodePathLikeClaude(cwd);
|
|
860
|
+
const pathsToCheck = [
|
|
861
|
+
path2.join(home, ".config", "claude", "projects"),
|
|
862
|
+
path2.join(home, ".claude", "projects")
|
|
863
|
+
];
|
|
864
|
+
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
865
|
+
if (configDir) {
|
|
866
|
+
pathsToCheck.unshift(path2.join(configDir, "projects"));
|
|
867
|
+
}
|
|
868
|
+
if (process.platform === "win32" && process.env.APPDATA) {
|
|
869
|
+
pathsToCheck.unshift(path2.join(process.env.APPDATA, "claude", "projects"));
|
|
870
|
+
}
|
|
871
|
+
const searchedPaths = pathsToCheck.map((p) => {
|
|
872
|
+
const exists = fs2.existsSync(p);
|
|
873
|
+
let matchingDirs;
|
|
874
|
+
if (exists) {
|
|
875
|
+
try {
|
|
876
|
+
const entries = fs2.readdirSync(p, { withFileTypes: true });
|
|
877
|
+
matchingDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => {
|
|
878
|
+
const lowerName = name.toLowerCase();
|
|
879
|
+
const lowerEncoded = encodedCwd.toLowerCase();
|
|
880
|
+
return lowerName.includes(path2.basename(cwd).toLowerCase()) || lowerEncoded.includes(lowerName) || lowerName.includes(lowerEncoded.slice(-20));
|
|
881
|
+
}).slice(0, 5);
|
|
882
|
+
} catch {
|
|
883
|
+
matchingDirs = void 0;
|
|
1001
884
|
}
|
|
1002
|
-
} catch {
|
|
1003
|
-
continue;
|
|
1004
885
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
886
|
+
return { path: p, exists, matchingDirs };
|
|
887
|
+
});
|
|
888
|
+
return {
|
|
889
|
+
cwd,
|
|
890
|
+
encodedCwd,
|
|
891
|
+
searchedPaths,
|
|
892
|
+
platform: process.platform,
|
|
893
|
+
home
|
|
894
|
+
};
|
|
1007
895
|
}
|
|
1008
|
-
function
|
|
1009
|
-
const
|
|
896
|
+
function scanAllProjects(options = {}) {
|
|
897
|
+
const projectsDirs = getClaudeProjectsDirs();
|
|
898
|
+
if (projectsDirs.length === 0) {
|
|
899
|
+
debugLog("No project directories found");
|
|
900
|
+
return null;
|
|
901
|
+
}
|
|
902
|
+
const days = options.days ?? 0;
|
|
1010
903
|
let cutoffDate = null;
|
|
1011
904
|
if (days > 0) {
|
|
1012
905
|
const cutoff = /* @__PURE__ */ new Date();
|
|
@@ -1026,13 +919,31 @@ function scanUsageDataFromPath(projectPath, options = {}) {
|
|
|
1026
919
|
const dailyData = {};
|
|
1027
920
|
let firstTimestamp = null;
|
|
1028
921
|
let lastTimestamp = null;
|
|
1029
|
-
const
|
|
1030
|
-
|
|
922
|
+
const allJsonlFiles = [];
|
|
923
|
+
for (const projectsDir of projectsDirs) {
|
|
924
|
+
try {
|
|
925
|
+
const entries = fs2.readdirSync(projectsDir, { withFileTypes: true });
|
|
926
|
+
for (const entry of entries) {
|
|
927
|
+
if (!entry.isDirectory()) continue;
|
|
928
|
+
if (entry.name.startsWith(".")) continue;
|
|
929
|
+
const projectPath = path2.join(projectsDir, entry.name);
|
|
930
|
+
const jsonlFiles = findJsonlFiles(projectPath);
|
|
931
|
+
allJsonlFiles.push(...jsonlFiles);
|
|
932
|
+
}
|
|
933
|
+
} catch {
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
if (allJsonlFiles.length === 0) {
|
|
938
|
+
debugLog("No JSONL files found in any project");
|
|
939
|
+
return null;
|
|
940
|
+
}
|
|
941
|
+
sessionsCount = allJsonlFiles.length;
|
|
1031
942
|
const { onProgress } = options;
|
|
1032
|
-
for (let i = 0; i <
|
|
1033
|
-
const filePath =
|
|
943
|
+
for (let i = 0; i < allJsonlFiles.length; i++) {
|
|
944
|
+
const filePath = allJsonlFiles[i];
|
|
1034
945
|
if (onProgress) {
|
|
1035
|
-
onProgress(i + 1,
|
|
946
|
+
onProgress(i + 1, allJsonlFiles.length);
|
|
1036
947
|
}
|
|
1037
948
|
const projectName = extractProjectName(filePath);
|
|
1038
949
|
if (!projects[projectName]) {
|
|
@@ -1133,7 +1044,7 @@ function scanUsageDataFromPath(projectPath, options = {}) {
|
|
|
1133
1044
|
models: data.models
|
|
1134
1045
|
})).sort((a, b) => a.date.localeCompare(b.date));
|
|
1135
1046
|
const credentials = readCredentials();
|
|
1136
|
-
const sessionFingerprint = generateSessionFingerprint(
|
|
1047
|
+
const sessionFingerprint = generateSessionFingerprint(allJsonlFiles);
|
|
1137
1048
|
return {
|
|
1138
1049
|
version: CCGATHER_JSON_VERSION,
|
|
1139
1050
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -1164,87 +1075,32 @@ function scanUsageDataFromPath(projectPath, options = {}) {
|
|
|
1164
1075
|
sessionFingerprint
|
|
1165
1076
|
};
|
|
1166
1077
|
}
|
|
1167
|
-
function
|
|
1168
|
-
const
|
|
1169
|
-
|
|
1170
|
-
const
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
}
|
|
1179
|
-
if (process.platform === "win32" && process.env.APPDATA) {
|
|
1180
|
-
pathsToCheck.unshift(path2.join(process.env.APPDATA, "claude", "projects"));
|
|
1181
|
-
}
|
|
1182
|
-
const searchedPaths = pathsToCheck.map((p) => {
|
|
1183
|
-
const exists = fs2.existsSync(p);
|
|
1184
|
-
let matchingDirs;
|
|
1185
|
-
if (exists) {
|
|
1186
|
-
try {
|
|
1187
|
-
const entries = fs2.readdirSync(p, { withFileTypes: true });
|
|
1188
|
-
matchingDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => {
|
|
1189
|
-
const lowerName = name.toLowerCase();
|
|
1190
|
-
const lowerEncoded = encodedCwd.toLowerCase();
|
|
1191
|
-
return lowerName.includes(path2.basename(cwd).toLowerCase()) || lowerEncoded.includes(lowerName) || lowerName.includes(lowerEncoded.slice(-20));
|
|
1192
|
-
}).slice(0, 5);
|
|
1193
|
-
} catch {
|
|
1194
|
-
matchingDirs = void 0;
|
|
1078
|
+
function getAllSessionsCount() {
|
|
1079
|
+
const projectsDirs = getClaudeProjectsDirs();
|
|
1080
|
+
let count = 0;
|
|
1081
|
+
for (const projectsDir of projectsDirs) {
|
|
1082
|
+
try {
|
|
1083
|
+
const entries = fs2.readdirSync(projectsDir, { withFileTypes: true });
|
|
1084
|
+
for (const entry of entries) {
|
|
1085
|
+
if (!entry.isDirectory()) continue;
|
|
1086
|
+
if (entry.name.startsWith(".")) continue;
|
|
1087
|
+
const projectPath = path2.join(projectsDir, entry.name);
|
|
1088
|
+
count += findJsonlFiles(projectPath).length;
|
|
1195
1089
|
}
|
|
1090
|
+
} catch {
|
|
1091
|
+
continue;
|
|
1196
1092
|
}
|
|
1197
|
-
return { path: p, exists, matchingDirs };
|
|
1198
|
-
});
|
|
1199
|
-
return {
|
|
1200
|
-
cwd,
|
|
1201
|
-
encodedCwd,
|
|
1202
|
-
searchedPaths,
|
|
1203
|
-
platform: process.platform,
|
|
1204
|
-
home
|
|
1205
|
-
};
|
|
1206
|
-
}
|
|
1207
|
-
var CCGATHER_FILE = ".ccgather";
|
|
1208
|
-
function getCcgatherFilePath() {
|
|
1209
|
-
return path2.join(process.cwd(), CCGATHER_FILE);
|
|
1210
|
-
}
|
|
1211
|
-
function loadProjectLink() {
|
|
1212
|
-
try {
|
|
1213
|
-
const filePath = getCcgatherFilePath();
|
|
1214
|
-
if (!fs2.existsSync(filePath)) {
|
|
1215
|
-
return null;
|
|
1216
|
-
}
|
|
1217
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1218
|
-
const data = JSON.parse(content);
|
|
1219
|
-
if (!data.claudeProjectPath || !data.folderName) {
|
|
1220
|
-
return null;
|
|
1221
|
-
}
|
|
1222
|
-
if (!fs2.existsSync(data.claudeProjectPath)) {
|
|
1223
|
-
return null;
|
|
1224
|
-
}
|
|
1225
|
-
return data;
|
|
1226
|
-
} catch {
|
|
1227
|
-
return null;
|
|
1228
1093
|
}
|
|
1094
|
+
return count;
|
|
1229
1095
|
}
|
|
1230
|
-
function
|
|
1231
|
-
|
|
1232
|
-
const filePath = getCcgatherFilePath();
|
|
1233
|
-
const data = {
|
|
1234
|
-
claudeProjectPath,
|
|
1235
|
-
folderName,
|
|
1236
|
-
linkedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1237
|
-
};
|
|
1238
|
-
fs2.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
1239
|
-
return true;
|
|
1240
|
-
} catch {
|
|
1241
|
-
return false;
|
|
1242
|
-
}
|
|
1096
|
+
function hasAnySessions() {
|
|
1097
|
+
return getAllSessionsCount() > 0;
|
|
1243
1098
|
}
|
|
1244
1099
|
|
|
1245
1100
|
// src/commands/submit.ts
|
|
1246
1101
|
init_ui();
|
|
1247
1102
|
function ccgatherToUsageData(data) {
|
|
1103
|
+
const opusCheck = hasOpusUsageInProject(data.dailyUsage);
|
|
1248
1104
|
return {
|
|
1249
1105
|
totalTokens: data.usage.totalTokens,
|
|
1250
1106
|
totalCost: data.usage.totalCost,
|
|
@@ -1260,7 +1116,9 @@ function ccgatherToUsageData(data) {
|
|
|
1260
1116
|
authMethod: data.account?.authMethod || "unknown",
|
|
1261
1117
|
rawSubscriptionType: data.account?.rawSubscriptionType || null,
|
|
1262
1118
|
dailyUsage: data.dailyUsage || [],
|
|
1263
|
-
sessionFingerprint: data.sessionFingerprint
|
|
1119
|
+
sessionFingerprint: data.sessionFingerprint,
|
|
1120
|
+
hasOpusUsage: opusCheck.detected,
|
|
1121
|
+
opusModels: opusCheck.opusModels
|
|
1264
1122
|
};
|
|
1265
1123
|
}
|
|
1266
1124
|
async function submitToServer(data) {
|
|
@@ -1285,6 +1143,7 @@ async function submitToServer(data) {
|
|
|
1285
1143
|
cacheReadTokens: data.cacheReadTokens,
|
|
1286
1144
|
cacheWriteTokens: data.cacheWriteTokens,
|
|
1287
1145
|
daysTracked: data.daysTracked,
|
|
1146
|
+
// Plan info for badge display (not league placement)
|
|
1288
1147
|
ccplan: data.ccplan,
|
|
1289
1148
|
rateLimitTier: data.rateLimitTier,
|
|
1290
1149
|
authMethod: data.authMethod,
|
|
@@ -1293,9 +1152,9 @@ async function submitToServer(data) {
|
|
|
1293
1152
|
dailyUsage: data.dailyUsage,
|
|
1294
1153
|
// Session fingerprint for duplicate prevention
|
|
1295
1154
|
sessionFingerprint: data.sessionFingerprint,
|
|
1296
|
-
//
|
|
1297
|
-
|
|
1298
|
-
|
|
1155
|
+
// Opus info for badge display
|
|
1156
|
+
hasOpusUsage: data.hasOpusUsage,
|
|
1157
|
+
opusModels: data.opusModels
|
|
1299
1158
|
})
|
|
1300
1159
|
});
|
|
1301
1160
|
if (!response.ok) {
|
|
@@ -1385,6 +1244,20 @@ async function verifyToken() {
|
|
|
1385
1244
|
return { valid: false };
|
|
1386
1245
|
}
|
|
1387
1246
|
}
|
|
1247
|
+
function getPlanColor(plan) {
|
|
1248
|
+
switch (plan.toLowerCase()) {
|
|
1249
|
+
case "max":
|
|
1250
|
+
return colors.max;
|
|
1251
|
+
case "pro":
|
|
1252
|
+
return colors.pro;
|
|
1253
|
+
case "team":
|
|
1254
|
+
return colors.team;
|
|
1255
|
+
case "enterprise":
|
|
1256
|
+
return colors.team;
|
|
1257
|
+
default:
|
|
1258
|
+
return colors.free;
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1388
1261
|
async function submit(options) {
|
|
1389
1262
|
console.log(header("Submit Usage Data", "\u{1F4E4}"));
|
|
1390
1263
|
const config = getConfig();
|
|
@@ -1419,205 +1292,48 @@ async function submit(options) {
|
|
|
1419
1292
|
}
|
|
1420
1293
|
const username = tokenCheck.username || config.get("username");
|
|
1421
1294
|
verifySpinner.succeed(colors.success(`Authenticated as ${colors.white(username || "unknown")}`));
|
|
1422
|
-
|
|
1423
|
-
let selectedProjectPath = null;
|
|
1424
|
-
let selectedFolderName = null;
|
|
1425
|
-
let linkSource = null;
|
|
1426
|
-
const savedLink = loadProjectLink();
|
|
1427
|
-
if (savedLink) {
|
|
1428
|
-
selectedProjectPath = savedLink.claudeProjectPath;
|
|
1429
|
-
selectedFolderName = savedLink.folderName;
|
|
1430
|
-
linkSource = "saved";
|
|
1295
|
+
if (!hasAnySessions()) {
|
|
1431
1296
|
console.log(`
|
|
1432
|
-
${colors.success("\u2713")} ${colors.muted("Using saved project link:")}`);
|
|
1433
|
-
console.log(` ${colors.dim(savedLink.folderName)}`);
|
|
1434
|
-
}
|
|
1435
|
-
if (!selectedProjectPath && hasProjectSessions()) {
|
|
1436
|
-
const autoMatchedPath = getProjectPath();
|
|
1437
|
-
if (autoMatchedPath) {
|
|
1438
|
-
selectedProjectPath = autoMatchedPath;
|
|
1439
|
-
selectedFolderName = autoMatchedPath.split(/[/\\]/).pop() || null;
|
|
1440
|
-
linkSource = "auto";
|
|
1441
|
-
console.log(`
|
|
1442
|
-
${colors.success("\u2713")} ${colors.muted("Auto-matched project:")}`);
|
|
1443
|
-
console.log(` ${colors.dim(selectedFolderName || projectName)}`);
|
|
1444
|
-
if (selectedFolderName) {
|
|
1445
|
-
saveProjectLink(selectedProjectPath, selectedFolderName);
|
|
1446
|
-
console.log(` ${colors.dim("(saved to .ccgather)")}`);
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
if (!selectedProjectPath) {
|
|
1451
|
-
const availableProjects = getAllProjectFolders();
|
|
1452
|
-
if (availableProjects.length === 0) {
|
|
1453
|
-
console.log(`
|
|
1454
1297
|
${error("No Claude Code sessions found.")}`);
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
console.log();
|
|
1460
|
-
console.log(
|
|
1461
|
-
` ${colors.warning("\u{1F4A1} Tip:")} ${colors.muted("Sessions are only created after you")}`
|
|
1462
|
-
);
|
|
1463
|
-
console.log(` ${colors.muted(" send at least one message in Claude Code.")}`);
|
|
1464
|
-
console.log();
|
|
1465
|
-
const debugInfo = getSessionPathDebugInfo();
|
|
1466
|
-
console.log(` ${colors.dim("\u2500".repeat(40))}`);
|
|
1467
|
-
console.log(` ${colors.muted("Searched paths:")}`);
|
|
1468
|
-
for (const pathInfo of debugInfo.searchedPaths) {
|
|
1469
|
-
const status = pathInfo.exists ? colors.success("\u2713") : colors.error("\u2717");
|
|
1470
|
-
console.log(` ${status} ${pathInfo.path}`);
|
|
1471
|
-
}
|
|
1472
|
-
console.log();
|
|
1473
|
-
process.exit(1);
|
|
1474
|
-
}
|
|
1475
|
-
console.log(
|
|
1476
|
-
`
|
|
1477
|
-
${colors.warning("\u26A0")} ${colors.muted("No session found for current directory:")}`
|
|
1478
|
-
);
|
|
1479
|
-
console.log(` ${colors.dim(projectName)}`);
|
|
1298
|
+
console.log();
|
|
1299
|
+
console.log(` ${colors.muted("This usually means:")}`);
|
|
1300
|
+
console.log(` ${colors.muted(" \u2022 You haven't used Claude Code yet, or")}`);
|
|
1301
|
+
console.log(` ${colors.muted(" \u2022 You ran Claude Code but didn't send any messages")}`);
|
|
1480
1302
|
console.log();
|
|
1481
1303
|
console.log(
|
|
1482
|
-
` ${colors.
|
|
1304
|
+
` ${colors.warning("\u{1F4A1} Tip:")} ${colors.muted("Sessions are only created after you")}`
|
|
1483
1305
|
);
|
|
1306
|
+
console.log(` ${colors.muted(" send at least one message in Claude Code.")}`);
|
|
1484
1307
|
console.log();
|
|
1485
|
-
const
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
name: colors.dim("Cancel"),
|
|
1492
|
-
value: "__cancel__",
|
|
1493
|
-
short: "Cancel"
|
|
1494
|
-
});
|
|
1495
|
-
const { selectedProject } = await import_inquirer2.default.prompt([
|
|
1496
|
-
{
|
|
1497
|
-
type: "list",
|
|
1498
|
-
name: "selectedProject",
|
|
1499
|
-
message: "Select a project to link with this folder:",
|
|
1500
|
-
choices,
|
|
1501
|
-
loop: false
|
|
1502
|
-
}
|
|
1503
|
-
]);
|
|
1504
|
-
if (selectedProject === "__cancel__") {
|
|
1505
|
-
console.log(`
|
|
1506
|
-
${colors.muted("Cancelled.")}
|
|
1507
|
-
`);
|
|
1508
|
-
process.exit(0);
|
|
1509
|
-
}
|
|
1510
|
-
selectedProjectPath = selectedProject;
|
|
1511
|
-
const selected = availableProjects.find((p) => p.fullPath === selectedProject);
|
|
1512
|
-
selectedFolderName = selected?.folderName || null;
|
|
1513
|
-
linkSource = "manual";
|
|
1514
|
-
if (selected && selectedProjectPath) {
|
|
1515
|
-
saveProjectLink(selectedProjectPath, selected.folderName);
|
|
1516
|
-
console.log(`
|
|
1517
|
-
${success(`Linked: ${selected.displayName}`)}`);
|
|
1518
|
-
console.log(` ${colors.dim("(saved to .ccgather - will auto-use next time)")}`);
|
|
1308
|
+
const debugInfo = getSessionPathDebugInfo();
|
|
1309
|
+
console.log(` ${colors.dim("\u2500".repeat(40))}`);
|
|
1310
|
+
console.log(` ${colors.muted("Searched paths:")}`);
|
|
1311
|
+
for (const pathInfo of debugInfo.searchedPaths) {
|
|
1312
|
+
const status = pathInfo.exists ? colors.success("\u2713") : colors.error("\u2717");
|
|
1313
|
+
console.log(` ${status} ${pathInfo.path}`);
|
|
1519
1314
|
}
|
|
1315
|
+
console.log();
|
|
1316
|
+
process.exit(1);
|
|
1520
1317
|
}
|
|
1521
|
-
let usageData = null;
|
|
1522
|
-
const allProjects = getAllProjectFolders();
|
|
1523
|
-
const selectedInfo = allProjects.find((p) => p.fullPath === selectedProjectPath);
|
|
1524
|
-
const displayProjectName = selectedInfo?.displayName || selectedFolderName || projectName;
|
|
1525
1318
|
console.log(`
|
|
1526
|
-
${colors.muted("
|
|
1527
|
-
|
|
1528
|
-
|
|
1319
|
+
${colors.muted("Scanning all Claude Code sessions...")}`);
|
|
1320
|
+
const totalSessions = getAllSessionsCount();
|
|
1321
|
+
console.log(` ${colors.dim(`Found ${totalSessions} session file(s)`)}`);
|
|
1322
|
+
const scannedData = scanAllProjects({
|
|
1529
1323
|
onProgress: (current, total) => {
|
|
1530
1324
|
progressBar(current, total, "Scanning");
|
|
1531
1325
|
}
|
|
1532
1326
|
});
|
|
1533
1327
|
await sleep(200);
|
|
1534
|
-
if (scannedData) {
|
|
1535
|
-
usageData = ccgatherToUsageData(scannedData);
|
|
1536
|
-
console.log(` ${success("Scan complete!")}`);
|
|
1537
|
-
}
|
|
1538
|
-
if (!usageData) {
|
|
1328
|
+
if (!scannedData) {
|
|
1539
1329
|
console.log(`
|
|
1540
1330
|
${error("No usage data found.")}`);
|
|
1541
1331
|
console.log(` ${colors.muted("Make sure you have used Claude Code at least once.")}
|
|
1542
1332
|
`);
|
|
1543
1333
|
process.exit(1);
|
|
1544
1334
|
}
|
|
1545
|
-
const
|
|
1546
|
-
|
|
1547
|
-
let finalPlan = usageData.ccplan || "free";
|
|
1548
|
-
let planDetectionReason = "current";
|
|
1549
|
-
if (opusCheck.detected) {
|
|
1550
|
-
finalPlan = "max";
|
|
1551
|
-
planDetectionReason = "opus";
|
|
1552
|
-
console.log(planDetectionSection("Plan Detection", "\u{1F680}"));
|
|
1553
|
-
for (const line of maxVerifiedMessage(opusCheck.opusModels)) {
|
|
1554
|
-
console.log(line);
|
|
1555
|
-
}
|
|
1556
|
-
} else if (oldDataCheck.hasOldData) {
|
|
1557
|
-
planDetectionReason = "user_choice";
|
|
1558
|
-
console.log(planDetectionSection("Past Data Detected", "\u{1F4C5}"));
|
|
1559
|
-
for (const line of pastDataWarningMessage(
|
|
1560
|
-
oldDataCheck.oldestDate || "",
|
|
1561
|
-
oldDataCheck.daysSinceOldest
|
|
1562
|
-
)) {
|
|
1563
|
-
console.log(line);
|
|
1564
|
-
}
|
|
1565
|
-
for (const line of trustMessage()) {
|
|
1566
|
-
console.log(line);
|
|
1567
|
-
}
|
|
1568
|
-
const planChoices = [
|
|
1569
|
-
{
|
|
1570
|
-
name: `${colors.pro("\u26A1")} Pro plan`,
|
|
1571
|
-
value: "pro",
|
|
1572
|
-
short: "Pro"
|
|
1573
|
-
},
|
|
1574
|
-
{
|
|
1575
|
-
name: `${colors.free("\u26AA")} Free plan`,
|
|
1576
|
-
value: "free",
|
|
1577
|
-
short: "Free"
|
|
1578
|
-
},
|
|
1579
|
-
{
|
|
1580
|
-
name: `${colors.dim("?")} Can't remember ${colors.dim("(submit as Free)")}`,
|
|
1581
|
-
value: "free_default",
|
|
1582
|
-
short: "Free (default)"
|
|
1583
|
-
}
|
|
1584
|
-
];
|
|
1585
|
-
const { selectedPlan } = await import_inquirer2.default.prompt([
|
|
1586
|
-
{
|
|
1587
|
-
type: "list",
|
|
1588
|
-
name: "selectedPlan",
|
|
1589
|
-
message: "Which plan were you using at that time?",
|
|
1590
|
-
choices: planChoices,
|
|
1591
|
-
default: 0
|
|
1592
|
-
}
|
|
1593
|
-
]);
|
|
1594
|
-
finalPlan = selectedPlan === "free_default" ? "free" : selectedPlan;
|
|
1595
|
-
console.log();
|
|
1596
|
-
console.log(` ${success(`Selected: ${finalPlan.toUpperCase()}`)}`);
|
|
1597
|
-
} else {
|
|
1598
|
-
planDetectionReason = "current";
|
|
1599
|
-
console.log(
|
|
1600
|
-
` ${colors.dim("\u{1F4CB} League: reading plan info only (subscriptionType, rateLimitTier)")}`
|
|
1601
|
-
);
|
|
1602
|
-
if (usageData.ccplan) {
|
|
1603
|
-
for (const line of currentPlanMessage(usageData.ccplan)) {
|
|
1604
|
-
console.log(line);
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
usageData.ccplan = finalPlan;
|
|
1609
|
-
usageData.leagueReason = planDetectionReason === "opus" ? "opus" : planDetectionReason === "user_choice" ? "user_choice" : "credential";
|
|
1610
|
-
if (planDetectionReason === "opus") {
|
|
1611
|
-
usageData.leagueReasonDetails = `Opus verified: ${opusCheck.opusModels.join(", ")}`;
|
|
1612
|
-
} else if (planDetectionReason === "user_choice") {
|
|
1613
|
-
usageData.leagueReasonDetails = `User selected: ${finalPlan} (data >${oldDataCheck.daysSinceOldest}d old)`;
|
|
1614
|
-
} else {
|
|
1615
|
-
usageData.leagueReasonDetails = `Credential: ${usageData.ccplan || "free"}`;
|
|
1616
|
-
}
|
|
1617
|
-
usageData.dailyUsage = usageData.dailyUsage.map((daily) => ({
|
|
1618
|
-
...daily,
|
|
1619
|
-
ccplan: finalPlan
|
|
1620
|
-
}));
|
|
1335
|
+
const usageData = ccgatherToUsageData(scannedData);
|
|
1336
|
+
console.log(` ${success("Scan complete!")}`);
|
|
1621
1337
|
console.log();
|
|
1622
1338
|
const formatDate = (dateStr) => {
|
|
1623
1339
|
if (!dateStr) return "------";
|
|
@@ -1629,21 +1345,31 @@ async function submit(options) {
|
|
|
1629
1345
|
};
|
|
1630
1346
|
const dateRange = usageData.firstUsed && usageData.lastUsed ? `${formatDate(usageData.firstUsed)} ~ ${formatDate(usageData.lastUsed)}` : "";
|
|
1631
1347
|
const daysTrackedDisplay = dateRange ? `${usageData.daysTracked} days ${colors.dim(`(${dateRange})`)}` : usageData.daysTracked.toString();
|
|
1348
|
+
const levelProgress = getLevelProgress(usageData.totalTokens);
|
|
1349
|
+
const currentLevel = levelProgress.current;
|
|
1632
1350
|
const summaryLines = [
|
|
1633
1351
|
`${colors.muted("Total Cost")} ${colors.success(formatCost(usageData.totalCost))}`,
|
|
1634
1352
|
`${colors.muted("Total Tokens")} ${colors.primary(formatNumber(usageData.totalTokens))}`,
|
|
1635
|
-
`${colors.muted("Period")} ${colors.warning(daysTrackedDisplay)}
|
|
1353
|
+
`${colors.muted("Period")} ${colors.warning(daysTrackedDisplay)}`,
|
|
1354
|
+
`${colors.muted("Level")} ${currentLevel.icon} ${currentLevel.color(`${currentLevel.name}`)}`
|
|
1636
1355
|
];
|
|
1637
1356
|
if (usageData.ccplan) {
|
|
1357
|
+
const planColor = getPlanColor(usageData.ccplan);
|
|
1358
|
+
summaryLines.push(
|
|
1359
|
+
`${colors.muted("Plan")} ${planColor(usageData.ccplan.toUpperCase())} ${colors.dim("(badge)")}`
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
if (usageData.hasOpusUsage) {
|
|
1638
1363
|
summaryLines.push(
|
|
1639
|
-
`${colors.muted("
|
|
1364
|
+
`${colors.muted("Models")} ${colors.max("\u2726")} ${colors.max("Opus User")}`
|
|
1640
1365
|
);
|
|
1641
1366
|
}
|
|
1642
1367
|
console.log(createBox(summaryLines));
|
|
1643
1368
|
console.log();
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
}
|
|
1369
|
+
const projectCount = Object.keys(scannedData.projects).length;
|
|
1370
|
+
console.log(
|
|
1371
|
+
` ${colors.dim(`Scanned ${projectCount} project(s), ${usageData.dailyUsage.length} day(s) of data`)}`
|
|
1372
|
+
);
|
|
1647
1373
|
const { confirmSubmit } = await import_inquirer2.default.prompt([
|
|
1648
1374
|
{
|
|
1649
1375
|
type: "confirm",
|
|
@@ -1687,8 +1413,6 @@ async function submit(options) {
|
|
|
1687
1413
|
console.log();
|
|
1688
1414
|
console.log(sectionHeader("\u2B06\uFE0F", "Level Progress"));
|
|
1689
1415
|
console.log();
|
|
1690
|
-
const levelProgress = getLevelProgress(usageData.totalTokens);
|
|
1691
|
-
const currentLevel = levelProgress.current;
|
|
1692
1416
|
await sleep(200);
|
|
1693
1417
|
console.log(
|
|
1694
1418
|
` ${currentLevel.icon} ${currentLevel.color(`Level ${currentLevel.level}`)} ${colors.muted("\u2022")} ${colors.white(currentLevel.name)}`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccgather",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "CLI tool for syncing Claude Code usage data to CCgather leaderboard",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ccgather": "dist/index.js",
|
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
"build": "tsup",
|
|
13
13
|
"dev": "tsup --watch",
|
|
14
14
|
"start": "node dist/index.js",
|
|
15
|
-
"typecheck": "tsc --noEmit"
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest"
|
|
16
18
|
},
|
|
17
19
|
"keywords": [
|
|
18
20
|
"claude",
|
|
@@ -39,7 +41,8 @@
|
|
|
39
41
|
"@types/node": "^22.10.2",
|
|
40
42
|
"@types/update-notifier": "^6.0.8",
|
|
41
43
|
"tsup": "^8.3.5",
|
|
42
|
-
"typescript": "^5.7.2"
|
|
44
|
+
"typescript": "^5.7.2",
|
|
45
|
+
"vitest": "^3.0.0"
|
|
43
46
|
},
|
|
44
47
|
"engines": {
|
|
45
48
|
"node": ">=18"
|