ccgather 2.0.31 → 2.0.33
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 +262 -163
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -107,19 +107,15 @@ function createProfessionalHeader() {
|
|
|
107
107
|
);
|
|
108
108
|
return lines;
|
|
109
109
|
}
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
const bottom = colors.dim(` ${box.bottomLeft}${box.horizontal.repeat(width)}${box.bottomRight}`);
|
|
113
|
-
console.log(top);
|
|
114
|
-
await sleep(lineDelay / 2);
|
|
115
|
-
for (const line of lines) {
|
|
110
|
+
function createBox(lines, width = 47) {
|
|
111
|
+
const paddedLines = lines.map((line) => {
|
|
116
112
|
const visibleLength = getDisplayWidth(line);
|
|
117
113
|
const padding = width - 2 - visibleLength;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
|
|
114
|
+
return `${box.vertical} ${line}${" ".repeat(Math.max(0, padding))} ${box.vertical}`;
|
|
115
|
+
});
|
|
116
|
+
const top = colors.dim(` ${box.topLeft}${box.horizontal.repeat(width)}${box.topRight}`);
|
|
117
|
+
const bottom = colors.dim(` ${box.bottomLeft}${box.horizontal.repeat(width)}${box.bottomRight}`);
|
|
118
|
+
return [top, ...paddedLines.map((l) => colors.dim(" ") + l), bottom].join("\n");
|
|
123
119
|
}
|
|
124
120
|
function _stripAnsi(str) {
|
|
125
121
|
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
@@ -247,57 +243,6 @@ async function printAnimatedHeader() {
|
|
|
247
243
|
}
|
|
248
244
|
console.log();
|
|
249
245
|
}
|
|
250
|
-
async function slotMachineRank(finalRank, label, medal, iterations = 12, previousRank) {
|
|
251
|
-
const maxRank = Math.max(finalRank * 3, 100);
|
|
252
|
-
for (let i = 0; i < iterations; i++) {
|
|
253
|
-
const fakeRank = Math.floor(Math.random() * maxRank) + 1;
|
|
254
|
-
const speed = Math.min(30 + i * 15, 150);
|
|
255
|
-
process.stdout.write(
|
|
256
|
-
`\r ${medal} ${colors.muted(label)} ${colors.dim(`#${fakeRank}`)} `
|
|
257
|
-
);
|
|
258
|
-
await sleep(speed);
|
|
259
|
-
}
|
|
260
|
-
let changeText = "";
|
|
261
|
-
if (previousRank && previousRank !== finalRank) {
|
|
262
|
-
const change = previousRank - finalRank;
|
|
263
|
-
if (change > 0) {
|
|
264
|
-
changeText = ` ${colors.success(`\u2191${change}`)}`;
|
|
265
|
-
} else if (change < 0) {
|
|
266
|
-
changeText = ` ${colors.error(`\u2193${Math.abs(change)}`)}`;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
process.stdout.write(
|
|
270
|
-
`\r ${medal} ${colors.muted(label)} ${colors.primary.bold(`#${finalRank}`)}${changeText}
|
|
271
|
-
`
|
|
272
|
-
);
|
|
273
|
-
}
|
|
274
|
-
async function animatedProgressBar(targetPercent, barWidth = 20, stepDelay = 25) {
|
|
275
|
-
const steps = Math.min(targetPercent, 20);
|
|
276
|
-
const stepSize = targetPercent / steps;
|
|
277
|
-
for (let i = 0; i <= steps; i++) {
|
|
278
|
-
const currentPercent = Math.round(i * stepSize);
|
|
279
|
-
const filled = Math.round(currentPercent / 100 * barWidth);
|
|
280
|
-
const empty = barWidth - filled;
|
|
281
|
-
const bar = colors.primary("\u2588".repeat(filled)) + colors.dim("\u2591".repeat(empty));
|
|
282
|
-
process.stdout.write(`\r [${bar}] ${colors.white(`${currentPercent}%`)} `);
|
|
283
|
-
await sleep(stepDelay);
|
|
284
|
-
}
|
|
285
|
-
const finalFilled = Math.round(targetPercent / 100 * barWidth);
|
|
286
|
-
const finalEmpty = barWidth - finalFilled;
|
|
287
|
-
return colors.primary("\u2588".repeat(finalFilled)) + colors.dim("\u2591".repeat(finalEmpty));
|
|
288
|
-
}
|
|
289
|
-
async function suspenseDots(message, durationMs = 600) {
|
|
290
|
-
const frames = ["", ".", "..", "..."];
|
|
291
|
-
const frameDelay = 100;
|
|
292
|
-
const iterations = Math.ceil(durationMs / (frames.length * frameDelay));
|
|
293
|
-
for (let i = 0; i < iterations; i++) {
|
|
294
|
-
for (const frame of frames) {
|
|
295
|
-
process.stdout.write(`\r ${colors.muted(message)}${colors.primary(frame)} `);
|
|
296
|
-
await sleep(frameDelay);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
process.stdout.write("\r" + " ".repeat(50) + "\r");
|
|
300
|
-
}
|
|
301
246
|
async function printAnimatedWelcomeBox(user) {
|
|
302
247
|
console.log(` \u{1F44B} ${colors.white.bold(`Welcome back, ${user.username}!`)}`);
|
|
303
248
|
await sleep(50);
|
|
@@ -308,7 +253,7 @@ var init_ui = __esm({
|
|
|
308
253
|
"use strict";
|
|
309
254
|
import_chalk = __toESM(require("chalk"));
|
|
310
255
|
import_string_width = __toESM(require("string-width"));
|
|
311
|
-
VERSION = true ? "2.0.
|
|
256
|
+
VERSION = true ? "2.0.33" : "0.0.0";
|
|
312
257
|
colors = {
|
|
313
258
|
primary: import_chalk.default.hex("#DA7756"),
|
|
314
259
|
// Claude coral
|
|
@@ -545,9 +490,9 @@ var import_inquirer2 = __toESM(require("inquirer"));
|
|
|
545
490
|
init_config();
|
|
546
491
|
|
|
547
492
|
// src/lib/ccgather-json.ts
|
|
548
|
-
var
|
|
549
|
-
var
|
|
550
|
-
var
|
|
493
|
+
var fs3 = __toESM(require("fs"));
|
|
494
|
+
var path3 = __toESM(require("path"));
|
|
495
|
+
var os3 = __toESM(require("os"));
|
|
551
496
|
var crypto = __toESM(require("crypto"));
|
|
552
497
|
|
|
553
498
|
// src/lib/credentials.ts
|
|
@@ -671,6 +616,154 @@ function readCredentials() {
|
|
|
671
616
|
};
|
|
672
617
|
}
|
|
673
618
|
|
|
619
|
+
// src/lib/pricing.ts
|
|
620
|
+
var fs2 = __toESM(require("fs"));
|
|
621
|
+
var path2 = __toESM(require("path"));
|
|
622
|
+
var os2 = __toESM(require("os"));
|
|
623
|
+
var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
624
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
625
|
+
var FETCH_TIMEOUT_MS = 5e3;
|
|
626
|
+
var FALLBACK_PRICING = {
|
|
627
|
+
"opus-4": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },
|
|
628
|
+
"sonnet-4": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
629
|
+
haiku: { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
|
|
630
|
+
default: { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 }
|
|
631
|
+
};
|
|
632
|
+
var pricingData = null;
|
|
633
|
+
function getCacheFilePath() {
|
|
634
|
+
const configDir = process.platform === "win32" ? path2.join(
|
|
635
|
+
process.env.APPDATA || path2.join(os2.homedir(), "AppData", "Roaming"),
|
|
636
|
+
"ccgather-nodejs"
|
|
637
|
+
) : path2.join(os2.homedir(), ".config", "ccgather-nodejs");
|
|
638
|
+
return path2.join(configDir, "pricing-cache.json");
|
|
639
|
+
}
|
|
640
|
+
function loadCache() {
|
|
641
|
+
try {
|
|
642
|
+
const cachePath = getCacheFilePath();
|
|
643
|
+
if (!fs2.existsSync(cachePath)) return null;
|
|
644
|
+
const raw = fs2.readFileSync(cachePath, "utf-8");
|
|
645
|
+
const cache = JSON.parse(raw);
|
|
646
|
+
if (Date.now() - cache.fetchedAt > CACHE_TTL_MS) return null;
|
|
647
|
+
if (!cache.models || Object.keys(cache.models).length === 0) return null;
|
|
648
|
+
return cache;
|
|
649
|
+
} catch {
|
|
650
|
+
return null;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
function saveCache(models) {
|
|
654
|
+
try {
|
|
655
|
+
const cachePath = getCacheFilePath();
|
|
656
|
+
const cacheDir = path2.dirname(cachePath);
|
|
657
|
+
if (!fs2.existsSync(cacheDir)) {
|
|
658
|
+
fs2.mkdirSync(cacheDir, { recursive: true });
|
|
659
|
+
}
|
|
660
|
+
const cache = {
|
|
661
|
+
fetchedAt: Date.now(),
|
|
662
|
+
models
|
|
663
|
+
};
|
|
664
|
+
fs2.writeFileSync(cachePath, JSON.stringify(cache), "utf-8");
|
|
665
|
+
} catch {
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function extractClaudePricing(rawData) {
|
|
669
|
+
const result = {};
|
|
670
|
+
for (const [key, value] of Object.entries(rawData)) {
|
|
671
|
+
if (!key.startsWith("claude-")) continue;
|
|
672
|
+
const inputCostPerToken = value.input_cost_per_token;
|
|
673
|
+
const outputCostPerToken = value.output_cost_per_token;
|
|
674
|
+
if (inputCostPerToken == null || outputCostPerToken == null) continue;
|
|
675
|
+
const input = inputCostPerToken * 1e6;
|
|
676
|
+
const output = outputCostPerToken * 1e6;
|
|
677
|
+
const cacheWritePerToken = value.cache_creation_input_token_cost;
|
|
678
|
+
const cacheReadPerToken = value.cache_read_input_token_cost;
|
|
679
|
+
const cacheWrite = cacheWritePerToken != null ? cacheWritePerToken * 1e6 : input * 1.25;
|
|
680
|
+
const cacheRead = cacheReadPerToken != null ? cacheReadPerToken * 1e6 : input * 0.1;
|
|
681
|
+
const pricing = {
|
|
682
|
+
input: Math.round(input * 1e3) / 1e3,
|
|
683
|
+
output: Math.round(output * 1e3) / 1e3,
|
|
684
|
+
cacheWrite: Math.round(cacheWrite * 1e3) / 1e3,
|
|
685
|
+
cacheRead: Math.round(cacheRead * 1e3) / 1e3
|
|
686
|
+
};
|
|
687
|
+
result[key] = pricing;
|
|
688
|
+
const withoutDate = key.replace(/-\d{8}$/, "");
|
|
689
|
+
if (withoutDate !== key && !result[withoutDate]) {
|
|
690
|
+
result[withoutDate] = pricing;
|
|
691
|
+
}
|
|
692
|
+
const withoutVersion = key.replace(/-v\d+:\d+$/, "").replace(/-\d{8}$/, "");
|
|
693
|
+
if (withoutVersion !== key && withoutVersion !== withoutDate && !result[withoutVersion]) {
|
|
694
|
+
result[withoutVersion] = pricing;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return result;
|
|
698
|
+
}
|
|
699
|
+
async function initPricing() {
|
|
700
|
+
if (pricingData) return;
|
|
701
|
+
const cached = loadCache();
|
|
702
|
+
if (cached) {
|
|
703
|
+
pricingData = cached.models;
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
try {
|
|
707
|
+
const controller = new AbortController();
|
|
708
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
709
|
+
const response = await fetch(LITELLM_PRICING_URL, {
|
|
710
|
+
signal: controller.signal
|
|
711
|
+
});
|
|
712
|
+
clearTimeout(timeout);
|
|
713
|
+
if (response.ok) {
|
|
714
|
+
const rawData = await response.json();
|
|
715
|
+
const extracted = extractClaudePricing(rawData);
|
|
716
|
+
if (Object.keys(extracted).length > 0) {
|
|
717
|
+
pricingData = extracted;
|
|
718
|
+
saveCache(extracted);
|
|
719
|
+
return;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
} catch {
|
|
723
|
+
}
|
|
724
|
+
pricingData = null;
|
|
725
|
+
}
|
|
726
|
+
function matchModel(model) {
|
|
727
|
+
if (pricingData) {
|
|
728
|
+
if (pricingData[model]) {
|
|
729
|
+
return pricingData[model];
|
|
730
|
+
}
|
|
731
|
+
const withoutDate = model.replace(/-\d{8}$/, "");
|
|
732
|
+
if (pricingData[withoutDate]) {
|
|
733
|
+
return pricingData[withoutDate];
|
|
734
|
+
}
|
|
735
|
+
const withoutVersion = model.replace(/-v\d+:\d+$/, "").replace(/-\d{8}$/, "");
|
|
736
|
+
if (pricingData[withoutVersion]) {
|
|
737
|
+
return pricingData[withoutVersion];
|
|
738
|
+
}
|
|
739
|
+
const modelLower2 = model.toLowerCase();
|
|
740
|
+
let bestMatch = null;
|
|
741
|
+
for (const [key, pricing] of Object.entries(pricingData)) {
|
|
742
|
+
if (modelLower2.startsWith(key.toLowerCase()) || key.toLowerCase().startsWith(modelLower2.replace(/-\d{8}$/, ""))) {
|
|
743
|
+
if (!bestMatch || key.length > bestMatch.key.length) {
|
|
744
|
+
bestMatch = { key, pricing };
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (bestMatch) {
|
|
749
|
+
return bestMatch.pricing;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
const modelLower = model.toLowerCase();
|
|
753
|
+
if (modelLower.includes("opus")) return FALLBACK_PRICING["opus-4"];
|
|
754
|
+
if (modelLower.includes("haiku")) return FALLBACK_PRICING["haiku"];
|
|
755
|
+
if (modelLower.includes("sonnet")) return FALLBACK_PRICING["sonnet-4"];
|
|
756
|
+
return FALLBACK_PRICING["default"];
|
|
757
|
+
}
|
|
758
|
+
function estimateCost(model, inputTokens, outputTokens, cacheWriteTokens = 0, cacheReadTokens = 0) {
|
|
759
|
+
const price = matchModel(model);
|
|
760
|
+
const inputCost = inputTokens / 1e6 * price.input;
|
|
761
|
+
const outputCost = outputTokens / 1e6 * price.output;
|
|
762
|
+
const cacheWriteCost = cacheWriteTokens / 1e6 * price.cacheWrite;
|
|
763
|
+
const cacheReadCost = cacheReadTokens / 1e6 * price.cacheRead;
|
|
764
|
+
return Math.round((inputCost + outputCost + cacheWriteCost + cacheReadCost) * 100) / 100;
|
|
765
|
+
}
|
|
766
|
+
|
|
674
767
|
// src/lib/ccgather-json.ts
|
|
675
768
|
function hasOpusUsageInProject(dailyUsage) {
|
|
676
769
|
const opusModels = /* @__PURE__ */ new Set();
|
|
@@ -707,26 +800,26 @@ function extractProjectName(filePath) {
|
|
|
707
800
|
}
|
|
708
801
|
function getClaudeProjectsDirs() {
|
|
709
802
|
const dirs = [];
|
|
710
|
-
const home =
|
|
803
|
+
const home = os3.homedir();
|
|
711
804
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
712
805
|
if (configDir) {
|
|
713
|
-
const envPath =
|
|
806
|
+
const envPath = path3.join(configDir, "projects");
|
|
714
807
|
dirs.push(envPath);
|
|
715
808
|
}
|
|
716
809
|
if (process.platform === "win32") {
|
|
717
810
|
const appData = process.env.APPDATA;
|
|
718
811
|
if (appData) {
|
|
719
|
-
const appDataPath =
|
|
812
|
+
const appDataPath = path3.join(appData, "claude", "projects");
|
|
720
813
|
dirs.push(appDataPath);
|
|
721
814
|
}
|
|
722
815
|
}
|
|
723
|
-
const xdgPath =
|
|
816
|
+
const xdgPath = path3.join(home, ".config", "claude", "projects");
|
|
724
817
|
dirs.push(xdgPath);
|
|
725
|
-
const legacyPath =
|
|
818
|
+
const legacyPath = path3.join(home, ".claude", "projects");
|
|
726
819
|
dirs.push(legacyPath);
|
|
727
820
|
const uniqueDirs = [...new Set(dirs)];
|
|
728
821
|
const existingDirs = uniqueDirs.filter((dir) => {
|
|
729
|
-
return
|
|
822
|
+
return fs3.existsSync(dir);
|
|
730
823
|
});
|
|
731
824
|
return existingDirs;
|
|
732
825
|
}
|
|
@@ -745,9 +838,9 @@ function encodePathLikeClaude(inputPath) {
|
|
|
745
838
|
function findJsonlFiles(dir) {
|
|
746
839
|
const files = [];
|
|
747
840
|
try {
|
|
748
|
-
const entries =
|
|
841
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
749
842
|
for (const entry of entries) {
|
|
750
|
-
const fullPath =
|
|
843
|
+
const fullPath = path3.join(dir, entry.name);
|
|
751
844
|
if (entry.isDirectory()) {
|
|
752
845
|
files.push(...findJsonlFiles(fullPath));
|
|
753
846
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
@@ -758,32 +851,11 @@ function findJsonlFiles(dir) {
|
|
|
758
851
|
}
|
|
759
852
|
return files;
|
|
760
853
|
}
|
|
761
|
-
function estimateCost(model, inputTokens, outputTokens, cacheWriteTokens = 0, cacheReadTokens = 0) {
|
|
762
|
-
const pricing = {
|
|
763
|
-
"claude-opus-4": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
764
|
-
"claude-sonnet-4": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
765
|
-
"claude-haiku": { input: 0.25, output: 1.25, cacheWrite: 0.3125, cacheRead: 0.025 },
|
|
766
|
-
default: { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 }
|
|
767
|
-
};
|
|
768
|
-
let modelKey = "default";
|
|
769
|
-
for (const key of Object.keys(pricing)) {
|
|
770
|
-
if (model.includes(key.replace("claude-", ""))) {
|
|
771
|
-
modelKey = key;
|
|
772
|
-
break;
|
|
773
|
-
}
|
|
774
|
-
}
|
|
775
|
-
const price = pricing[modelKey];
|
|
776
|
-
const inputCost = inputTokens / 1e6 * price.input;
|
|
777
|
-
const outputCost = outputTokens / 1e6 * price.output;
|
|
778
|
-
const cacheWriteCost = cacheWriteTokens / 1e6 * price.cacheWrite;
|
|
779
|
-
const cacheReadCost = cacheReadTokens / 1e6 * price.cacheRead;
|
|
780
|
-
return Math.round((inputCost + outputCost + cacheWriteCost + cacheReadCost) * 100) / 100;
|
|
781
|
-
}
|
|
782
854
|
function generateSessionHash(filePath, maxLines = 50) {
|
|
783
855
|
try {
|
|
784
|
-
const content =
|
|
856
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
785
857
|
const lines = content.split("\n").slice(0, maxLines).join("\n");
|
|
786
|
-
const fileName =
|
|
858
|
+
const fileName = path3.basename(filePath);
|
|
787
859
|
const hashInput = `${fileName}:${lines}`;
|
|
788
860
|
return crypto.createHash("sha256").update(hashInput).digest("hex");
|
|
789
861
|
} catch {
|
|
@@ -807,30 +879,30 @@ function generateSessionFingerprint(sessionFiles) {
|
|
|
807
879
|
};
|
|
808
880
|
}
|
|
809
881
|
function getSessionPathDebugInfo() {
|
|
810
|
-
const home =
|
|
882
|
+
const home = os3.homedir();
|
|
811
883
|
const cwd = process.cwd();
|
|
812
884
|
const encodedCwd = encodePathLikeClaude(cwd);
|
|
813
885
|
const pathsToCheck = [
|
|
814
|
-
|
|
815
|
-
|
|
886
|
+
path3.join(home, ".config", "claude", "projects"),
|
|
887
|
+
path3.join(home, ".claude", "projects")
|
|
816
888
|
];
|
|
817
889
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
818
890
|
if (configDir) {
|
|
819
|
-
pathsToCheck.unshift(
|
|
891
|
+
pathsToCheck.unshift(path3.join(configDir, "projects"));
|
|
820
892
|
}
|
|
821
893
|
if (process.platform === "win32" && process.env.APPDATA) {
|
|
822
|
-
pathsToCheck.unshift(
|
|
894
|
+
pathsToCheck.unshift(path3.join(process.env.APPDATA, "claude", "projects"));
|
|
823
895
|
}
|
|
824
896
|
const searchedPaths = pathsToCheck.map((p) => {
|
|
825
|
-
const exists =
|
|
897
|
+
const exists = fs3.existsSync(p);
|
|
826
898
|
let matchingDirs;
|
|
827
899
|
if (exists) {
|
|
828
900
|
try {
|
|
829
|
-
const entries =
|
|
901
|
+
const entries = fs3.readdirSync(p, { withFileTypes: true });
|
|
830
902
|
matchingDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => {
|
|
831
903
|
const lowerName = name.toLowerCase();
|
|
832
904
|
const lowerEncoded = encodedCwd.toLowerCase();
|
|
833
|
-
return lowerName.includes(
|
|
905
|
+
return lowerName.includes(path3.basename(cwd).toLowerCase()) || lowerEncoded.includes(lowerName) || lowerName.includes(lowerEncoded.slice(-20));
|
|
834
906
|
}).slice(0, 5);
|
|
835
907
|
} catch {
|
|
836
908
|
matchingDirs = void 0;
|
|
@@ -874,11 +946,11 @@ function scanAllProjects(options = {}) {
|
|
|
874
946
|
const allJsonlFiles = [];
|
|
875
947
|
for (const projectsDir of projectsDirs) {
|
|
876
948
|
try {
|
|
877
|
-
const entries =
|
|
949
|
+
const entries = fs3.readdirSync(projectsDir, { withFileTypes: true });
|
|
878
950
|
for (const entry of entries) {
|
|
879
951
|
if (!entry.isDirectory()) continue;
|
|
880
952
|
if (entry.name.startsWith(".")) continue;
|
|
881
|
-
const projectPath =
|
|
953
|
+
const projectPath = path3.join(projectsDir, entry.name);
|
|
882
954
|
const jsonlFiles = findJsonlFiles(projectPath);
|
|
883
955
|
allJsonlFiles.push(...jsonlFiles);
|
|
884
956
|
}
|
|
@@ -893,9 +965,6 @@ function scanAllProjects(options = {}) {
|
|
|
893
965
|
const { onProgress } = options;
|
|
894
966
|
for (let i = 0; i < allJsonlFiles.length; i++) {
|
|
895
967
|
const filePath = allJsonlFiles[i];
|
|
896
|
-
if (onProgress) {
|
|
897
|
-
onProgress(i + 1, allJsonlFiles.length);
|
|
898
|
-
}
|
|
899
968
|
const projectName = extractProjectName(filePath);
|
|
900
969
|
if (!projects[projectName]) {
|
|
901
970
|
projects[projectName] = {
|
|
@@ -907,7 +976,7 @@ function scanAllProjects(options = {}) {
|
|
|
907
976
|
}
|
|
908
977
|
projects[projectName].sessions++;
|
|
909
978
|
try {
|
|
910
|
-
const content =
|
|
979
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
911
980
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
912
981
|
for (const line of lines) {
|
|
913
982
|
try {
|
|
@@ -975,6 +1044,9 @@ function scanAllProjects(options = {}) {
|
|
|
975
1044
|
}
|
|
976
1045
|
} catch {
|
|
977
1046
|
}
|
|
1047
|
+
if (onProgress) {
|
|
1048
|
+
onProgress(i + 1, allJsonlFiles.length);
|
|
1049
|
+
}
|
|
978
1050
|
}
|
|
979
1051
|
const totalTokens = totalInputTokens + totalOutputTokens + totalCacheWrite + totalCacheRead;
|
|
980
1052
|
if (totalTokens === 0) {
|
|
@@ -1031,11 +1103,11 @@ function getAllSessionsCount() {
|
|
|
1031
1103
|
let count = 0;
|
|
1032
1104
|
for (const projectsDir of projectsDirs) {
|
|
1033
1105
|
try {
|
|
1034
|
-
const entries =
|
|
1106
|
+
const entries = fs3.readdirSync(projectsDir, { withFileTypes: true });
|
|
1035
1107
|
for (const entry of entries) {
|
|
1036
1108
|
if (!entry.isDirectory()) continue;
|
|
1037
1109
|
if (entry.name.startsWith(".")) continue;
|
|
1038
|
-
const projectPath =
|
|
1110
|
+
const projectPath = path3.join(projectsDir, entry.name);
|
|
1039
1111
|
count += findJsonlFiles(projectPath).length;
|
|
1040
1112
|
}
|
|
1041
1113
|
} catch {
|
|
@@ -1050,6 +1122,27 @@ function hasAnySessions() {
|
|
|
1050
1122
|
|
|
1051
1123
|
// src/commands/submit.ts
|
|
1052
1124
|
init_ui();
|
|
1125
|
+
async function reportSubmitAttempt(reason, debugInfo) {
|
|
1126
|
+
try {
|
|
1127
|
+
const config = getConfig();
|
|
1128
|
+
const apiUrl = getApiUrl();
|
|
1129
|
+
const token = config.get("apiToken");
|
|
1130
|
+
await fetch(`${apiUrl}/cli/submit-attempt`, {
|
|
1131
|
+
method: "POST",
|
|
1132
|
+
headers: {
|
|
1133
|
+
"Content-Type": "application/json",
|
|
1134
|
+
...token && { Authorization: `Bearer ${token}` }
|
|
1135
|
+
},
|
|
1136
|
+
body: JSON.stringify({
|
|
1137
|
+
reason,
|
|
1138
|
+
debugInfo,
|
|
1139
|
+
cliVersion: process.env.npm_package_version || "unknown",
|
|
1140
|
+
platform: process.platform
|
|
1141
|
+
})
|
|
1142
|
+
});
|
|
1143
|
+
} catch {
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1053
1146
|
function ccgatherToUsageData(data) {
|
|
1054
1147
|
const opusCheck = hasOpusUsageInProject(data.dailyUsage);
|
|
1055
1148
|
return {
|
|
@@ -1144,30 +1237,21 @@ function formatBadgeDate(dateStr) {
|
|
|
1144
1237
|
if (!dateStr) return (/* @__PURE__ */ new Date()).toISOString().split("T")[0].replace(/-/g, ".");
|
|
1145
1238
|
return dateStr.split("T")[0].replace(/-/g, ".");
|
|
1146
1239
|
}
|
|
1147
|
-
|
|
1240
|
+
function displayNewBadges(badges) {
|
|
1148
1241
|
if (badges.length === 0) return;
|
|
1149
1242
|
console.log();
|
|
1150
|
-
|
|
1151
|
-
for (let i = 0; i < badges.length; i++) {
|
|
1152
|
-
const badge = badges[i];
|
|
1243
|
+
for (const badge of badges) {
|
|
1153
1244
|
const rarityColor = getRarityColor(badge.rarity);
|
|
1154
1245
|
const rarityLabel = badge.rarity.toUpperCase();
|
|
1155
|
-
if (i > 0) {
|
|
1156
|
-
await sleep(300);
|
|
1157
|
-
}
|
|
1158
1246
|
console.log(
|
|
1159
1247
|
` \u2728 ${badge.icon} ${colors.white.bold(badge.name)} ${rarityColor(`[${rarityLabel}]`)}`
|
|
1160
1248
|
);
|
|
1161
|
-
await sleep(100);
|
|
1162
1249
|
console.log(` ${colors.muted(badge.description)}`);
|
|
1163
|
-
await sleep(80);
|
|
1164
1250
|
if (badge.praise) {
|
|
1165
1251
|
console.log(` ${colors.cyan(`"${badge.praise}"`)}`);
|
|
1166
|
-
await sleep(80);
|
|
1167
1252
|
}
|
|
1168
1253
|
if (badge.category === "rank") {
|
|
1169
1254
|
console.log(` ${colors.dim(`\u{1F4C5} Achieved: ${formatBadgeDate(badge.earnedAt)}`)}`);
|
|
1170
|
-
await sleep(80);
|
|
1171
1255
|
}
|
|
1172
1256
|
}
|
|
1173
1257
|
}
|
|
@@ -1243,6 +1327,7 @@ async function submit(options) {
|
|
|
1243
1327
|
}
|
|
1244
1328
|
const username = tokenCheck.username || config.get("username");
|
|
1245
1329
|
verifySpinner.succeed(colors.success(`Authenticated as ${colors.white(username || "unknown")}`));
|
|
1330
|
+
await initPricing();
|
|
1246
1331
|
if (!hasAnySessions()) {
|
|
1247
1332
|
console.log(`
|
|
1248
1333
|
${error("No Claude Code sessions found.")}`);
|
|
@@ -1264,6 +1349,7 @@ async function submit(options) {
|
|
|
1264
1349
|
console.log(` ${status} ${pathInfo.path}`);
|
|
1265
1350
|
}
|
|
1266
1351
|
console.log();
|
|
1352
|
+
await reportSubmitAttempt("no_sessions", debugInfo);
|
|
1267
1353
|
process.exit(1);
|
|
1268
1354
|
}
|
|
1269
1355
|
console.log(`
|
|
@@ -1280,15 +1366,22 @@ async function submit(options) {
|
|
|
1280
1366
|
if (lastProgress > 0) {
|
|
1281
1367
|
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
1282
1368
|
}
|
|
1369
|
+
process.stdout.write(` ${colors.muted("Processing...")}`);
|
|
1283
1370
|
if (!scannedData) {
|
|
1371
|
+
process.stdout.write("\r" + " ".repeat(40) + "\r");
|
|
1284
1372
|
console.log(` ${colors.error("\u2717")} ${colors.error("No usage data found.")}`);
|
|
1285
1373
|
console.log(` ${colors.muted("Make sure you have used Claude Code at least once.")}
|
|
1286
1374
|
`);
|
|
1375
|
+
await reportSubmitAttempt("no_data");
|
|
1287
1376
|
process.exit(1);
|
|
1288
1377
|
}
|
|
1289
1378
|
const usageData = ccgatherToUsageData(scannedData);
|
|
1290
|
-
|
|
1291
|
-
|
|
1379
|
+
process.stdout.write("\r" + " ".repeat(40) + "\r");
|
|
1380
|
+
const submitSpinner = (0, import_ora2.default)({
|
|
1381
|
+
text: "Submitting to CCgather...",
|
|
1382
|
+
color: "cyan"
|
|
1383
|
+
}).start();
|
|
1384
|
+
const result = await submitToServer(usageData);
|
|
1292
1385
|
const formatDate = (dateStr) => {
|
|
1293
1386
|
if (!dateStr) return "------";
|
|
1294
1387
|
const d = new Date(dateStr);
|
|
@@ -1299,13 +1392,10 @@ async function submit(options) {
|
|
|
1299
1392
|
};
|
|
1300
1393
|
const dateRange = usageData.firstUsed && usageData.lastUsed ? `${formatDate(usageData.firstUsed)} ~ ${formatDate(usageData.lastUsed)}` : "";
|
|
1301
1394
|
const daysTrackedDisplay = dateRange ? `${usageData.daysTracked} days ${colors.dim(`(${dateRange})`)}` : usageData.daysTracked.toString();
|
|
1302
|
-
const levelProgress = getLevelProgress(usageData.totalTokens);
|
|
1303
|
-
const currentLevel = levelProgress.current;
|
|
1304
1395
|
const summaryLines = [
|
|
1305
1396
|
`${colors.muted("Total Cost")} \u{1F4B0} ${colors.warning(formatCost(usageData.totalCost))}`,
|
|
1306
1397
|
`${colors.muted("Total Tokens")} \u26A1 ${colors.primary(formatNumber(usageData.totalTokens))}`,
|
|
1307
|
-
`${colors.muted("Period")} \u{1F4C5} ${colors.white(daysTrackedDisplay)}
|
|
1308
|
-
`${colors.muted("Level")} ${currentLevel.icon} ${currentLevel.color(`${currentLevel.name}`)}`
|
|
1398
|
+
`${colors.muted("Period")} \u{1F4C5} ${colors.white(daysTrackedDisplay)}`
|
|
1309
1399
|
];
|
|
1310
1400
|
if (usageData.ccplan) {
|
|
1311
1401
|
const planColor = getPlanColor(usageData.ccplan);
|
|
@@ -1317,18 +1407,7 @@ async function submit(options) {
|
|
|
1317
1407
|
if (usageData.hasOpusUsage) {
|
|
1318
1408
|
summaryLines.push(`${colors.muted("Models")} ${colors.max("\u2726 Opus User")}`);
|
|
1319
1409
|
}
|
|
1320
|
-
await printAnimatedBox(summaryLines, 52, 60);
|
|
1321
|
-
console.log();
|
|
1322
1410
|
const projectCount = Object.keys(scannedData.projects).length;
|
|
1323
|
-
console.log(
|
|
1324
|
-
` ${colors.dim(`Scanned ${projectCount} project(s), ${usageData.dailyUsage.length} day(s) of data`)}`
|
|
1325
|
-
);
|
|
1326
|
-
console.log();
|
|
1327
|
-
const submitSpinner = (0, import_ora2.default)({
|
|
1328
|
-
text: "Submitting to CCgather...",
|
|
1329
|
-
color: "cyan"
|
|
1330
|
-
}).start();
|
|
1331
|
-
const result = await submitToServer(usageData);
|
|
1332
1411
|
if (result.success) {
|
|
1333
1412
|
submitSpinner.succeed(colors.success("Successfully submitted!"));
|
|
1334
1413
|
console.log();
|
|
@@ -1337,25 +1416,26 @@ async function submit(options) {
|
|
|
1337
1416
|
const lineLength = 40 - text.length;
|
|
1338
1417
|
return ` ${colors.white.bold(text)}${colors.dim("\u2500".repeat(Math.max(0, lineLength)))}`;
|
|
1339
1418
|
};
|
|
1419
|
+
const accumulatedTokens = result.previous?.totalTokens || usageData.totalTokens;
|
|
1420
|
+
const levelProgress = getLevelProgress(accumulatedTokens);
|
|
1421
|
+
const currentLevel = levelProgress.current;
|
|
1422
|
+
summaryLines.push(
|
|
1423
|
+
`${colors.muted("Level")} ${currentLevel.icon} ${currentLevel.color(`${currentLevel.name}`)}`
|
|
1424
|
+
);
|
|
1425
|
+
console.log(createBox(summaryLines, 52));
|
|
1426
|
+
console.log(
|
|
1427
|
+
` ${colors.dim(`Scanned ${projectCount} project(s), ${usageData.dailyUsage.length} day(s) of data`)}`
|
|
1428
|
+
);
|
|
1340
1429
|
if (result.previous) {
|
|
1341
1430
|
const prev = result.previous;
|
|
1431
|
+
console.log();
|
|
1342
1432
|
console.log(sectionHeader("\u{1F4E6}", "Server Records"));
|
|
1343
|
-
await sleep(40);
|
|
1344
1433
|
console.log();
|
|
1345
1434
|
const prevTokens = prev.totalTokens || 0;
|
|
1346
1435
|
const prevCost = prev.totalCost || 0;
|
|
1347
1436
|
console.log(
|
|
1348
1437
|
` ${colors.muted("Accumulated")} \u26A1 ${colors.primary(formatNumber(prevTokens))} ${colors.dim("\u2502")} \u{1F4B0} ${colors.warning(formatCost(prevCost))}`
|
|
1349
1438
|
);
|
|
1350
|
-
await sleep(50);
|
|
1351
|
-
console.log();
|
|
1352
|
-
await sleep(60);
|
|
1353
|
-
const rankSpinner = (0, import_ora2.default)({
|
|
1354
|
-
text: colors.dim("Calculating ranking..."),
|
|
1355
|
-
color: "cyan"
|
|
1356
|
-
}).start();
|
|
1357
|
-
await sleep(400);
|
|
1358
|
-
rankSpinner.stop();
|
|
1359
1439
|
}
|
|
1360
1440
|
if (result.rank || result.countryRank) {
|
|
1361
1441
|
console.log();
|
|
@@ -1363,37 +1443,47 @@ async function submit(options) {
|
|
|
1363
1443
|
console.log();
|
|
1364
1444
|
const prevGlobalRank = result.previous?.previousGlobalRank;
|
|
1365
1445
|
const prevCountryRank = result.previous?.previousCountryRank;
|
|
1446
|
+
const formatRankChange = (current, previous) => {
|
|
1447
|
+
if (!previous || previous === current) return "";
|
|
1448
|
+
const change = previous - current;
|
|
1449
|
+
if (change > 0) return ` ${colors.success(`\u2191${change}`)}`;
|
|
1450
|
+
return ` ${colors.error(`\u2193${Math.abs(change)}`)}`;
|
|
1451
|
+
};
|
|
1366
1452
|
if (result.rank) {
|
|
1367
1453
|
const medal = result.rank === 1 ? "\u{1F947}" : result.rank === 2 ? "\u{1F948}" : result.rank === 3 ? "\u{1F949}" : result.rank <= 10 ? "\u{1F3C5}" : "\u{1F30D}";
|
|
1368
|
-
|
|
1454
|
+
console.log(
|
|
1455
|
+
` ${medal} ${colors.muted("Global:")} ${colors.primary.bold(`#${result.rank}`)}${formatRankChange(result.rank, prevGlobalRank)}`
|
|
1456
|
+
);
|
|
1369
1457
|
}
|
|
1370
1458
|
if (result.countryRank) {
|
|
1371
1459
|
const countryMedal = result.countryRank === 1 ? "\u{1F947}" : result.countryRank <= 3 ? "\u{1F3C6}" : "\u{1F3E0}";
|
|
1372
|
-
|
|
1460
|
+
console.log(
|
|
1461
|
+
` ${countryMedal} ${colors.muted("Country:")} ${colors.primary.bold(`#${result.countryRank}`)}${formatRankChange(result.countryRank, prevCountryRank)}`
|
|
1462
|
+
);
|
|
1373
1463
|
}
|
|
1374
1464
|
}
|
|
1375
1465
|
console.log();
|
|
1376
1466
|
console.log(sectionHeader("\u2B06\uFE0F", "Level Progress"));
|
|
1377
1467
|
console.log();
|
|
1378
|
-
await sleep(200);
|
|
1379
1468
|
console.log(
|
|
1380
1469
|
` ${colors.muted("Lv.")}${colors.white(String(currentLevel.level))} ${currentLevel.icon} ${currentLevel.color(currentLevel.name)}`
|
|
1381
1470
|
);
|
|
1382
1471
|
if (!levelProgress.isMaxLevel && levelProgress.next) {
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1472
|
+
const barWidth = 20;
|
|
1473
|
+
const filled = Math.round(levelProgress.progress / 100 * barWidth);
|
|
1474
|
+
const empty = barWidth - filled;
|
|
1475
|
+
const bar = colors.primary("\u2588".repeat(filled)) + colors.dim("\u2591".repeat(empty));
|
|
1476
|
+
console.log(` [${bar}] ${colors.white(`${levelProgress.progress}%`)}`);
|
|
1386
1477
|
console.log(
|
|
1387
1478
|
` ${colors.dim("\u2192")} ${levelProgress.next.icon} ${colors.white(levelProgress.next.name)} ${colors.muted("in")} ${colors.primary(formatNumber(levelProgress.tokensToNext))}`
|
|
1388
1479
|
);
|
|
1389
1480
|
} else {
|
|
1390
|
-
await sleep(300);
|
|
1391
1481
|
console.log(` ${colors.max("\u2605")} ${colors.max("MAX LEVEL ACHIEVED!")}`);
|
|
1392
1482
|
}
|
|
1393
1483
|
if (result.newBadges && result.newBadges.length > 0) {
|
|
1394
1484
|
console.log();
|
|
1395
1485
|
console.log(sectionHeader("\u{1F389}", "New Badge Unlocked"));
|
|
1396
|
-
|
|
1486
|
+
displayNewBadges(result.newBadges);
|
|
1397
1487
|
}
|
|
1398
1488
|
console.log();
|
|
1399
1489
|
const leaderboardUrl = `https://ccgather.com/leaderboard?u=${username}`;
|
|
@@ -1406,14 +1496,23 @@ async function submit(options) {
|
|
|
1406
1496
|
console.log(` ${colors.muted("Submit regularly to preserve your full history!")}`);
|
|
1407
1497
|
console.log();
|
|
1408
1498
|
} else {
|
|
1409
|
-
submitSpinner.fail(colors.error("Failed to submit"));
|
|
1410
|
-
console.log(`
|
|
1411
|
-
${error(result.error || "Unknown error")}`);
|
|
1412
1499
|
if (result.retryAfterMinutes) {
|
|
1500
|
+
submitSpinner.fail(colors.warning("Submission limit reached"));
|
|
1501
|
+
console.log();
|
|
1502
|
+
console.log(
|
|
1503
|
+
` ${colors.muted("To keep our service stable, you can submit up to 2 times per hour.")}`
|
|
1504
|
+
);
|
|
1505
|
+
console.log(
|
|
1506
|
+
` ${colors.muted("Your data is safely stored locally - submit anytime later!")}`
|
|
1507
|
+
);
|
|
1413
1508
|
console.log();
|
|
1414
1509
|
console.log(
|
|
1415
|
-
` ${colors.warning("\u23F3")} ${colors.
|
|
1510
|
+
` ${colors.warning("\u23F3")} ${colors.white("Ready to submit again in")} ${colors.primary(`${result.retryAfterMinutes} minute${result.retryAfterMinutes !== 1 ? "s" : ""}`)}`
|
|
1416
1511
|
);
|
|
1512
|
+
} else {
|
|
1513
|
+
submitSpinner.fail(colors.error("Failed to submit"));
|
|
1514
|
+
console.log(`
|
|
1515
|
+
${error(result.error || "Unknown error")}`);
|
|
1417
1516
|
}
|
|
1418
1517
|
console.log();
|
|
1419
1518
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccgather",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.33",
|
|
4
4
|
"description": "CLI tool for syncing Claude Code usage data to CCgather leaderboard",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ccgather": "dist/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"cli"
|
|
26
26
|
],
|
|
27
27
|
"author": "",
|
|
28
|
-
"license": "
|
|
28
|
+
"license": "Apache-2.0",
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"chalk": "^5.3.0",
|
|
31
31
|
"commander": "^12.1.0",
|
|
@@ -50,6 +50,6 @@
|
|
|
50
50
|
],
|
|
51
51
|
"repository": {
|
|
52
52
|
"type": "git",
|
|
53
|
-
"url": "git+https://github.com/
|
|
53
|
+
"url": "git+https://github.com/DHxWhy/CCgather.git"
|
|
54
54
|
}
|
|
55
55
|
}
|