ccgather 2.0.32 → 2.0.34
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 +250 -54
- package/package.json +55 -55
package/dist/index.js
CHANGED
|
@@ -253,7 +253,7 @@ var init_ui = __esm({
|
|
|
253
253
|
"use strict";
|
|
254
254
|
import_chalk = __toESM(require("chalk"));
|
|
255
255
|
import_string_width = __toESM(require("string-width"));
|
|
256
|
-
VERSION = true ? "2.0.
|
|
256
|
+
VERSION = true ? "2.0.33" : "0.0.0";
|
|
257
257
|
colors = {
|
|
258
258
|
primary: import_chalk.default.hex("#DA7756"),
|
|
259
259
|
// Claude coral
|
|
@@ -489,19 +489,45 @@ var import_ora2 = __toESM(require("ora"));
|
|
|
489
489
|
var import_inquirer2 = __toESM(require("inquirer"));
|
|
490
490
|
init_config();
|
|
491
491
|
|
|
492
|
-
// src/lib/
|
|
493
|
-
var
|
|
494
|
-
var path2 = __toESM(require("path"));
|
|
495
|
-
var os2 = __toESM(require("os"));
|
|
492
|
+
// src/lib/device.ts
|
|
493
|
+
var os = __toESM(require("os"));
|
|
496
494
|
var crypto = __toESM(require("crypto"));
|
|
495
|
+
init_config();
|
|
496
|
+
var DEVICE_ID_LENGTH = 16;
|
|
497
|
+
function getDeviceId() {
|
|
498
|
+
try {
|
|
499
|
+
const config = getConfig();
|
|
500
|
+
const existing = config.get("deviceId");
|
|
501
|
+
if (existing) {
|
|
502
|
+
return existing;
|
|
503
|
+
}
|
|
504
|
+
const salt = crypto.randomBytes(8).toString("hex");
|
|
505
|
+
const raw = `${os.hostname() || "unknown"}:${os.homedir()}:${os.platform()}:${os.arch()}:${salt}`;
|
|
506
|
+
const deviceId = crypto.createHash("sha256").update(raw).digest("hex").slice(0, DEVICE_ID_LENGTH);
|
|
507
|
+
try {
|
|
508
|
+
config.set("deviceId", deviceId);
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
return deviceId;
|
|
512
|
+
} catch {
|
|
513
|
+
const raw = `${os.hostname() || "unknown"}:${os.homedir()}:${os.platform()}:${os.arch()}`;
|
|
514
|
+
return crypto.createHash("sha256").update(raw).digest("hex").slice(0, DEVICE_ID_LENGTH);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/lib/ccgather-json.ts
|
|
519
|
+
var fs3 = __toESM(require("fs"));
|
|
520
|
+
var path3 = __toESM(require("path"));
|
|
521
|
+
var os4 = __toESM(require("os"));
|
|
522
|
+
var crypto2 = __toESM(require("crypto"));
|
|
497
523
|
|
|
498
524
|
// src/lib/credentials.ts
|
|
499
525
|
var fs = __toESM(require("fs"));
|
|
500
526
|
var path = __toESM(require("path"));
|
|
501
|
-
var
|
|
527
|
+
var os2 = __toESM(require("os"));
|
|
502
528
|
var import_child_process = require("child_process");
|
|
503
529
|
function getCredentialsPath() {
|
|
504
|
-
return path.join(
|
|
530
|
+
return path.join(os2.homedir(), ".claude", ".credentials.json");
|
|
505
531
|
}
|
|
506
532
|
function readFromMacKeychain() {
|
|
507
533
|
try {
|
|
@@ -616,6 +642,154 @@ function readCredentials() {
|
|
|
616
642
|
};
|
|
617
643
|
}
|
|
618
644
|
|
|
645
|
+
// src/lib/pricing.ts
|
|
646
|
+
var fs2 = __toESM(require("fs"));
|
|
647
|
+
var path2 = __toESM(require("path"));
|
|
648
|
+
var os3 = __toESM(require("os"));
|
|
649
|
+
var LITELLM_PRICING_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
650
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
651
|
+
var FETCH_TIMEOUT_MS = 5e3;
|
|
652
|
+
var FALLBACK_PRICING = {
|
|
653
|
+
"opus-4": { input: 5, output: 25, cacheWrite: 6.25, cacheRead: 0.5 },
|
|
654
|
+
"sonnet-4": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
655
|
+
haiku: { input: 1, output: 5, cacheWrite: 1.25, cacheRead: 0.1 },
|
|
656
|
+
default: { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 }
|
|
657
|
+
};
|
|
658
|
+
var pricingData = null;
|
|
659
|
+
function getCacheFilePath() {
|
|
660
|
+
const configDir = process.platform === "win32" ? path2.join(
|
|
661
|
+
process.env.APPDATA || path2.join(os3.homedir(), "AppData", "Roaming"),
|
|
662
|
+
"ccgather-nodejs"
|
|
663
|
+
) : path2.join(os3.homedir(), ".config", "ccgather-nodejs");
|
|
664
|
+
return path2.join(configDir, "pricing-cache.json");
|
|
665
|
+
}
|
|
666
|
+
function loadCache() {
|
|
667
|
+
try {
|
|
668
|
+
const cachePath = getCacheFilePath();
|
|
669
|
+
if (!fs2.existsSync(cachePath)) return null;
|
|
670
|
+
const raw = fs2.readFileSync(cachePath, "utf-8");
|
|
671
|
+
const cache = JSON.parse(raw);
|
|
672
|
+
if (Date.now() - cache.fetchedAt > CACHE_TTL_MS) return null;
|
|
673
|
+
if (!cache.models || Object.keys(cache.models).length === 0) return null;
|
|
674
|
+
return cache;
|
|
675
|
+
} catch {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function saveCache(models) {
|
|
680
|
+
try {
|
|
681
|
+
const cachePath = getCacheFilePath();
|
|
682
|
+
const cacheDir = path2.dirname(cachePath);
|
|
683
|
+
if (!fs2.existsSync(cacheDir)) {
|
|
684
|
+
fs2.mkdirSync(cacheDir, { recursive: true });
|
|
685
|
+
}
|
|
686
|
+
const cache = {
|
|
687
|
+
fetchedAt: Date.now(),
|
|
688
|
+
models
|
|
689
|
+
};
|
|
690
|
+
fs2.writeFileSync(cachePath, JSON.stringify(cache), "utf-8");
|
|
691
|
+
} catch {
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
function extractClaudePricing(rawData) {
|
|
695
|
+
const result = {};
|
|
696
|
+
for (const [key, value] of Object.entries(rawData)) {
|
|
697
|
+
if (!key.startsWith("claude-")) continue;
|
|
698
|
+
const inputCostPerToken = value.input_cost_per_token;
|
|
699
|
+
const outputCostPerToken = value.output_cost_per_token;
|
|
700
|
+
if (inputCostPerToken == null || outputCostPerToken == null) continue;
|
|
701
|
+
const input = inputCostPerToken * 1e6;
|
|
702
|
+
const output = outputCostPerToken * 1e6;
|
|
703
|
+
const cacheWritePerToken = value.cache_creation_input_token_cost;
|
|
704
|
+
const cacheReadPerToken = value.cache_read_input_token_cost;
|
|
705
|
+
const cacheWrite = cacheWritePerToken != null ? cacheWritePerToken * 1e6 : input * 1.25;
|
|
706
|
+
const cacheRead = cacheReadPerToken != null ? cacheReadPerToken * 1e6 : input * 0.1;
|
|
707
|
+
const pricing = {
|
|
708
|
+
input: Math.round(input * 1e3) / 1e3,
|
|
709
|
+
output: Math.round(output * 1e3) / 1e3,
|
|
710
|
+
cacheWrite: Math.round(cacheWrite * 1e3) / 1e3,
|
|
711
|
+
cacheRead: Math.round(cacheRead * 1e3) / 1e3
|
|
712
|
+
};
|
|
713
|
+
result[key] = pricing;
|
|
714
|
+
const withoutDate = key.replace(/-\d{8}$/, "");
|
|
715
|
+
if (withoutDate !== key && !result[withoutDate]) {
|
|
716
|
+
result[withoutDate] = pricing;
|
|
717
|
+
}
|
|
718
|
+
const withoutVersion = key.replace(/-v\d+:\d+$/, "").replace(/-\d{8}$/, "");
|
|
719
|
+
if (withoutVersion !== key && withoutVersion !== withoutDate && !result[withoutVersion]) {
|
|
720
|
+
result[withoutVersion] = pricing;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
725
|
+
async function initPricing() {
|
|
726
|
+
if (pricingData) return;
|
|
727
|
+
const cached = loadCache();
|
|
728
|
+
if (cached) {
|
|
729
|
+
pricingData = cached.models;
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
try {
|
|
733
|
+
const controller = new AbortController();
|
|
734
|
+
const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
735
|
+
const response = await fetch(LITELLM_PRICING_URL, {
|
|
736
|
+
signal: controller.signal
|
|
737
|
+
});
|
|
738
|
+
clearTimeout(timeout);
|
|
739
|
+
if (response.ok) {
|
|
740
|
+
const rawData = await response.json();
|
|
741
|
+
const extracted = extractClaudePricing(rawData);
|
|
742
|
+
if (Object.keys(extracted).length > 0) {
|
|
743
|
+
pricingData = extracted;
|
|
744
|
+
saveCache(extracted);
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
} catch {
|
|
749
|
+
}
|
|
750
|
+
pricingData = null;
|
|
751
|
+
}
|
|
752
|
+
function matchModel(model) {
|
|
753
|
+
if (pricingData) {
|
|
754
|
+
if (pricingData[model]) {
|
|
755
|
+
return pricingData[model];
|
|
756
|
+
}
|
|
757
|
+
const withoutDate = model.replace(/-\d{8}$/, "");
|
|
758
|
+
if (pricingData[withoutDate]) {
|
|
759
|
+
return pricingData[withoutDate];
|
|
760
|
+
}
|
|
761
|
+
const withoutVersion = model.replace(/-v\d+:\d+$/, "").replace(/-\d{8}$/, "");
|
|
762
|
+
if (pricingData[withoutVersion]) {
|
|
763
|
+
return pricingData[withoutVersion];
|
|
764
|
+
}
|
|
765
|
+
const modelLower2 = model.toLowerCase();
|
|
766
|
+
let bestMatch = null;
|
|
767
|
+
for (const [key, pricing] of Object.entries(pricingData)) {
|
|
768
|
+
if (modelLower2.startsWith(key.toLowerCase()) || key.toLowerCase().startsWith(modelLower2.replace(/-\d{8}$/, ""))) {
|
|
769
|
+
if (!bestMatch || key.length > bestMatch.key.length) {
|
|
770
|
+
bestMatch = { key, pricing };
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
if (bestMatch) {
|
|
775
|
+
return bestMatch.pricing;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
const modelLower = model.toLowerCase();
|
|
779
|
+
if (modelLower.includes("opus")) return FALLBACK_PRICING["opus-4"];
|
|
780
|
+
if (modelLower.includes("haiku")) return FALLBACK_PRICING["haiku"];
|
|
781
|
+
if (modelLower.includes("sonnet")) return FALLBACK_PRICING["sonnet-4"];
|
|
782
|
+
return FALLBACK_PRICING["default"];
|
|
783
|
+
}
|
|
784
|
+
function estimateCost(model, inputTokens, outputTokens, cacheWriteTokens = 0, cacheReadTokens = 0) {
|
|
785
|
+
const price = matchModel(model);
|
|
786
|
+
const inputCost = inputTokens / 1e6 * price.input;
|
|
787
|
+
const outputCost = outputTokens / 1e6 * price.output;
|
|
788
|
+
const cacheWriteCost = cacheWriteTokens / 1e6 * price.cacheWrite;
|
|
789
|
+
const cacheReadCost = cacheReadTokens / 1e6 * price.cacheRead;
|
|
790
|
+
return Math.round((inputCost + outputCost + cacheWriteCost + cacheReadCost) * 100) / 100;
|
|
791
|
+
}
|
|
792
|
+
|
|
619
793
|
// src/lib/ccgather-json.ts
|
|
620
794
|
function hasOpusUsageInProject(dailyUsage) {
|
|
621
795
|
const opusModels = /* @__PURE__ */ new Set();
|
|
@@ -652,26 +826,26 @@ function extractProjectName(filePath) {
|
|
|
652
826
|
}
|
|
653
827
|
function getClaudeProjectsDirs() {
|
|
654
828
|
const dirs = [];
|
|
655
|
-
const home =
|
|
829
|
+
const home = os4.homedir();
|
|
656
830
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
657
831
|
if (configDir) {
|
|
658
|
-
const envPath =
|
|
832
|
+
const envPath = path3.join(configDir, "projects");
|
|
659
833
|
dirs.push(envPath);
|
|
660
834
|
}
|
|
661
835
|
if (process.platform === "win32") {
|
|
662
836
|
const appData = process.env.APPDATA;
|
|
663
837
|
if (appData) {
|
|
664
|
-
const appDataPath =
|
|
838
|
+
const appDataPath = path3.join(appData, "claude", "projects");
|
|
665
839
|
dirs.push(appDataPath);
|
|
666
840
|
}
|
|
667
841
|
}
|
|
668
|
-
const xdgPath =
|
|
842
|
+
const xdgPath = path3.join(home, ".config", "claude", "projects");
|
|
669
843
|
dirs.push(xdgPath);
|
|
670
|
-
const legacyPath =
|
|
844
|
+
const legacyPath = path3.join(home, ".claude", "projects");
|
|
671
845
|
dirs.push(legacyPath);
|
|
672
846
|
const uniqueDirs = [...new Set(dirs)];
|
|
673
847
|
const existingDirs = uniqueDirs.filter((dir) => {
|
|
674
|
-
return
|
|
848
|
+
return fs3.existsSync(dir);
|
|
675
849
|
});
|
|
676
850
|
return existingDirs;
|
|
677
851
|
}
|
|
@@ -690,9 +864,9 @@ function encodePathLikeClaude(inputPath) {
|
|
|
690
864
|
function findJsonlFiles(dir) {
|
|
691
865
|
const files = [];
|
|
692
866
|
try {
|
|
693
|
-
const entries =
|
|
867
|
+
const entries = fs3.readdirSync(dir, { withFileTypes: true });
|
|
694
868
|
for (const entry of entries) {
|
|
695
|
-
const fullPath =
|
|
869
|
+
const fullPath = path3.join(dir, entry.name);
|
|
696
870
|
if (entry.isDirectory()) {
|
|
697
871
|
files.push(...findJsonlFiles(fullPath));
|
|
698
872
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
@@ -703,34 +877,13 @@ function findJsonlFiles(dir) {
|
|
|
703
877
|
}
|
|
704
878
|
return files;
|
|
705
879
|
}
|
|
706
|
-
function estimateCost(model, inputTokens, outputTokens, cacheWriteTokens = 0, cacheReadTokens = 0) {
|
|
707
|
-
const pricing = {
|
|
708
|
-
"claude-opus-4": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
709
|
-
"claude-sonnet-4": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
710
|
-
"claude-haiku": { input: 0.25, output: 1.25, cacheWrite: 0.3125, cacheRead: 0.025 },
|
|
711
|
-
default: { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 }
|
|
712
|
-
};
|
|
713
|
-
let modelKey = "default";
|
|
714
|
-
for (const key of Object.keys(pricing)) {
|
|
715
|
-
if (model.includes(key.replace("claude-", ""))) {
|
|
716
|
-
modelKey = key;
|
|
717
|
-
break;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
const price = pricing[modelKey];
|
|
721
|
-
const inputCost = inputTokens / 1e6 * price.input;
|
|
722
|
-
const outputCost = outputTokens / 1e6 * price.output;
|
|
723
|
-
const cacheWriteCost = cacheWriteTokens / 1e6 * price.cacheWrite;
|
|
724
|
-
const cacheReadCost = cacheReadTokens / 1e6 * price.cacheRead;
|
|
725
|
-
return Math.round((inputCost + outputCost + cacheWriteCost + cacheReadCost) * 100) / 100;
|
|
726
|
-
}
|
|
727
880
|
function generateSessionHash(filePath, maxLines = 50) {
|
|
728
881
|
try {
|
|
729
|
-
const content =
|
|
882
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
730
883
|
const lines = content.split("\n").slice(0, maxLines).join("\n");
|
|
731
|
-
const fileName =
|
|
884
|
+
const fileName = path3.basename(filePath);
|
|
732
885
|
const hashInput = `${fileName}:${lines}`;
|
|
733
|
-
return
|
|
886
|
+
return crypto2.createHash("sha256").update(hashInput).digest("hex");
|
|
734
887
|
} catch {
|
|
735
888
|
return null;
|
|
736
889
|
}
|
|
@@ -744,7 +897,7 @@ function generateSessionFingerprint(sessionFiles) {
|
|
|
744
897
|
}
|
|
745
898
|
}
|
|
746
899
|
sessionHashes.sort();
|
|
747
|
-
const combinedHash =
|
|
900
|
+
const combinedHash = crypto2.createHash("sha256").update(sessionHashes.join(":")).digest("hex");
|
|
748
901
|
return {
|
|
749
902
|
sessionHashes,
|
|
750
903
|
combinedHash,
|
|
@@ -752,30 +905,30 @@ function generateSessionFingerprint(sessionFiles) {
|
|
|
752
905
|
};
|
|
753
906
|
}
|
|
754
907
|
function getSessionPathDebugInfo() {
|
|
755
|
-
const home =
|
|
908
|
+
const home = os4.homedir();
|
|
756
909
|
const cwd = process.cwd();
|
|
757
910
|
const encodedCwd = encodePathLikeClaude(cwd);
|
|
758
911
|
const pathsToCheck = [
|
|
759
|
-
|
|
760
|
-
|
|
912
|
+
path3.join(home, ".config", "claude", "projects"),
|
|
913
|
+
path3.join(home, ".claude", "projects")
|
|
761
914
|
];
|
|
762
915
|
const configDir = process.env.CLAUDE_CONFIG_DIR;
|
|
763
916
|
if (configDir) {
|
|
764
|
-
pathsToCheck.unshift(
|
|
917
|
+
pathsToCheck.unshift(path3.join(configDir, "projects"));
|
|
765
918
|
}
|
|
766
919
|
if (process.platform === "win32" && process.env.APPDATA) {
|
|
767
|
-
pathsToCheck.unshift(
|
|
920
|
+
pathsToCheck.unshift(path3.join(process.env.APPDATA, "claude", "projects"));
|
|
768
921
|
}
|
|
769
922
|
const searchedPaths = pathsToCheck.map((p) => {
|
|
770
|
-
const exists =
|
|
923
|
+
const exists = fs3.existsSync(p);
|
|
771
924
|
let matchingDirs;
|
|
772
925
|
if (exists) {
|
|
773
926
|
try {
|
|
774
|
-
const entries =
|
|
927
|
+
const entries = fs3.readdirSync(p, { withFileTypes: true });
|
|
775
928
|
matchingDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name).filter((name) => {
|
|
776
929
|
const lowerName = name.toLowerCase();
|
|
777
930
|
const lowerEncoded = encodedCwd.toLowerCase();
|
|
778
|
-
return lowerName.includes(
|
|
931
|
+
return lowerName.includes(path3.basename(cwd).toLowerCase()) || lowerEncoded.includes(lowerName) || lowerName.includes(lowerEncoded.slice(-20));
|
|
779
932
|
}).slice(0, 5);
|
|
780
933
|
} catch {
|
|
781
934
|
matchingDirs = void 0;
|
|
@@ -819,11 +972,11 @@ function scanAllProjects(options = {}) {
|
|
|
819
972
|
const allJsonlFiles = [];
|
|
820
973
|
for (const projectsDir of projectsDirs) {
|
|
821
974
|
try {
|
|
822
|
-
const entries =
|
|
975
|
+
const entries = fs3.readdirSync(projectsDir, { withFileTypes: true });
|
|
823
976
|
for (const entry of entries) {
|
|
824
977
|
if (!entry.isDirectory()) continue;
|
|
825
978
|
if (entry.name.startsWith(".")) continue;
|
|
826
|
-
const projectPath =
|
|
979
|
+
const projectPath = path3.join(projectsDir, entry.name);
|
|
827
980
|
const jsonlFiles = findJsonlFiles(projectPath);
|
|
828
981
|
allJsonlFiles.push(...jsonlFiles);
|
|
829
982
|
}
|
|
@@ -849,7 +1002,7 @@ function scanAllProjects(options = {}) {
|
|
|
849
1002
|
}
|
|
850
1003
|
projects[projectName].sessions++;
|
|
851
1004
|
try {
|
|
852
|
-
const content =
|
|
1005
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
853
1006
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
854
1007
|
for (const line of lines) {
|
|
855
1008
|
try {
|
|
@@ -976,11 +1129,11 @@ function getAllSessionsCount() {
|
|
|
976
1129
|
let count = 0;
|
|
977
1130
|
for (const projectsDir of projectsDirs) {
|
|
978
1131
|
try {
|
|
979
|
-
const entries =
|
|
1132
|
+
const entries = fs3.readdirSync(projectsDir, { withFileTypes: true });
|
|
980
1133
|
for (const entry of entries) {
|
|
981
1134
|
if (!entry.isDirectory()) continue;
|
|
982
1135
|
if (entry.name.startsWith(".")) continue;
|
|
983
|
-
const projectPath =
|
|
1136
|
+
const projectPath = path3.join(projectsDir, entry.name);
|
|
984
1137
|
count += findJsonlFiles(projectPath).length;
|
|
985
1138
|
}
|
|
986
1139
|
} catch {
|
|
@@ -995,6 +1148,27 @@ function hasAnySessions() {
|
|
|
995
1148
|
|
|
996
1149
|
// src/commands/submit.ts
|
|
997
1150
|
init_ui();
|
|
1151
|
+
async function reportSubmitAttempt(reason, debugInfo) {
|
|
1152
|
+
try {
|
|
1153
|
+
const config = getConfig();
|
|
1154
|
+
const apiUrl = getApiUrl();
|
|
1155
|
+
const token = config.get("apiToken");
|
|
1156
|
+
await fetch(`${apiUrl}/cli/submit-attempt`, {
|
|
1157
|
+
method: "POST",
|
|
1158
|
+
headers: {
|
|
1159
|
+
"Content-Type": "application/json",
|
|
1160
|
+
...token && { Authorization: `Bearer ${token}` }
|
|
1161
|
+
},
|
|
1162
|
+
body: JSON.stringify({
|
|
1163
|
+
reason,
|
|
1164
|
+
debugInfo,
|
|
1165
|
+
cliVersion: process.env.npm_package_version || "unknown",
|
|
1166
|
+
platform: process.platform
|
|
1167
|
+
})
|
|
1168
|
+
});
|
|
1169
|
+
} catch {
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
998
1172
|
function ccgatherToUsageData(data) {
|
|
999
1173
|
const opusCheck = hasOpusUsageInProject(data.dailyUsage);
|
|
1000
1174
|
return {
|
|
@@ -1050,7 +1224,9 @@ async function submitToServer(data) {
|
|
|
1050
1224
|
sessionFingerprint: data.sessionFingerprint,
|
|
1051
1225
|
// Opus info for badge display
|
|
1052
1226
|
hasOpusUsage: data.hasOpusUsage,
|
|
1053
|
-
opusModels: data.opusModels
|
|
1227
|
+
opusModels: data.opusModels,
|
|
1228
|
+
// Multi-device support
|
|
1229
|
+
deviceId: getDeviceId()
|
|
1054
1230
|
})
|
|
1055
1231
|
});
|
|
1056
1232
|
if (!response.ok) {
|
|
@@ -1179,6 +1355,7 @@ async function submit(options) {
|
|
|
1179
1355
|
}
|
|
1180
1356
|
const username = tokenCheck.username || config.get("username");
|
|
1181
1357
|
verifySpinner.succeed(colors.success(`Authenticated as ${colors.white(username || "unknown")}`));
|
|
1358
|
+
await initPricing();
|
|
1182
1359
|
if (!hasAnySessions()) {
|
|
1183
1360
|
console.log(`
|
|
1184
1361
|
${error("No Claude Code sessions found.")}`);
|
|
@@ -1200,6 +1377,7 @@ async function submit(options) {
|
|
|
1200
1377
|
console.log(` ${status} ${pathInfo.path}`);
|
|
1201
1378
|
}
|
|
1202
1379
|
console.log();
|
|
1380
|
+
await reportSubmitAttempt("no_sessions", debugInfo);
|
|
1203
1381
|
process.exit(1);
|
|
1204
1382
|
}
|
|
1205
1383
|
console.log(`
|
|
@@ -1222,6 +1400,7 @@ async function submit(options) {
|
|
|
1222
1400
|
console.log(` ${colors.error("\u2717")} ${colors.error("No usage data found.")}`);
|
|
1223
1401
|
console.log(` ${colors.muted("Make sure you have used Claude Code at least once.")}
|
|
1224
1402
|
`);
|
|
1403
|
+
await reportSubmitAttempt("no_data");
|
|
1225
1404
|
process.exit(1);
|
|
1226
1405
|
}
|
|
1227
1406
|
const usageData = ccgatherToUsageData(scannedData);
|
|
@@ -1286,6 +1465,23 @@ async function submit(options) {
|
|
|
1286
1465
|
` ${colors.muted("Accumulated")} \u26A1 ${colors.primary(formatNumber(prevTokens))} ${colors.dim("\u2502")} \u{1F4B0} ${colors.warning(formatCost(prevCost))}`
|
|
1287
1466
|
);
|
|
1288
1467
|
}
|
|
1468
|
+
if (result.deviceInfo && result.deviceInfo.totalDevices > 1) {
|
|
1469
|
+
const di = result.deviceInfo;
|
|
1470
|
+
const otherCount = di.totalDevices - 1;
|
|
1471
|
+
console.log();
|
|
1472
|
+
console.log(sectionHeader("\u{1F5A5}\uFE0F", "Multi-Device"));
|
|
1473
|
+
console.log();
|
|
1474
|
+
console.log(
|
|
1475
|
+
` ${colors.muted("This PC")} \u26A1 ${colors.primary(formatNumber(di.thisDeviceTokens))} ${colors.dim("\u2502")} \u{1F4B0} ${colors.warning(formatCost(di.thisDeviceCost))}`
|
|
1476
|
+
);
|
|
1477
|
+
console.log(
|
|
1478
|
+
` ${colors.muted(`+ ${otherCount} other`)} \u26A1 ${colors.primary(formatNumber(di.otherDevicesTokens))} ${colors.dim("\u2502")} \u{1F4B0} ${colors.warning(formatCost(di.otherDevicesCost))}`
|
|
1479
|
+
);
|
|
1480
|
+
console.log(` ${colors.dim("\u2500".repeat(33))}`);
|
|
1481
|
+
console.log(
|
|
1482
|
+
` ${colors.white.bold("Combined")} \u26A1 ${colors.primary(formatNumber(di.combinedTokens))} ${colors.dim("\u2502")} \u{1F4B0} ${colors.warning(formatCost(di.combinedCost))} ${colors.success("\u2713")}`
|
|
1483
|
+
);
|
|
1484
|
+
}
|
|
1289
1485
|
if (result.rank || result.countryRank) {
|
|
1290
1486
|
console.log();
|
|
1291
1487
|
console.log(sectionHeader("\u{1F4CA}", "Your Ranking"));
|
package/package.json
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "ccgather",
|
|
3
|
-
"version": "2.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",
|
|
13
|
-
"dev": "tsup --watch",
|
|
14
|
-
"start": "node dist/index.js",
|
|
15
|
-
"typecheck": "tsc --noEmit",
|
|
16
|
-
"test": "vitest run",
|
|
17
|
-
"test:watch": "vitest"
|
|
18
|
-
},
|
|
19
|
-
"keywords": [
|
|
20
|
-
"claude",
|
|
21
|
-
"anthropic",
|
|
22
|
-
"claude-code",
|
|
23
|
-
"ccgather",
|
|
24
|
-
"leaderboard",
|
|
25
|
-
"cli"
|
|
26
|
-
],
|
|
27
|
-
"author": "",
|
|
28
|
-
"license": "Apache-2.0",
|
|
29
|
-
"dependencies": {
|
|
30
|
-
"chalk": "^5.3.0",
|
|
31
|
-
"commander": "^12.1.0",
|
|
32
|
-
"conf": "^13.0.1",
|
|
33
|
-
"inquirer": "^9.2.23",
|
|
34
|
-
"open": "^10.1.0",
|
|
35
|
-
"ora": "^8.1.0",
|
|
36
|
-
"string-width": "^7.2.0"
|
|
37
|
-
},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@types/inquirer": "^9.0.7",
|
|
40
|
-
"@types/node": "^22.10.2",
|
|
41
|
-
"tsup": "^8.3.5",
|
|
42
|
-
"typescript": "^5.7.2",
|
|
43
|
-
"vitest": "^3.0.0"
|
|
44
|
-
},
|
|
45
|
-
"engines": {
|
|
46
|
-
"node": ">=18"
|
|
47
|
-
},
|
|
48
|
-
"files": [
|
|
49
|
-
"dist"
|
|
50
|
-
],
|
|
51
|
-
"repository": {
|
|
52
|
-
"type": "git",
|
|
53
|
-
"url": "git+https://github.com/DHxWhy/CCgather.git"
|
|
54
|
-
}
|
|
55
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "ccgather",
|
|
3
|
+
"version": "2.0.34",
|
|
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",
|
|
13
|
+
"dev": "tsup --watch",
|
|
14
|
+
"start": "node dist/index.js",
|
|
15
|
+
"typecheck": "tsc --noEmit",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"claude",
|
|
21
|
+
"anthropic",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"ccgather",
|
|
24
|
+
"leaderboard",
|
|
25
|
+
"cli"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "Apache-2.0",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"chalk": "^5.3.0",
|
|
31
|
+
"commander": "^12.1.0",
|
|
32
|
+
"conf": "^13.0.1",
|
|
33
|
+
"inquirer": "^9.2.23",
|
|
34
|
+
"open": "^10.1.0",
|
|
35
|
+
"ora": "^8.1.0",
|
|
36
|
+
"string-width": "^7.2.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/inquirer": "^9.0.7",
|
|
40
|
+
"@types/node": "^22.10.2",
|
|
41
|
+
"tsup": "^8.3.5",
|
|
42
|
+
"typescript": "^5.7.2",
|
|
43
|
+
"vitest": "^3.0.0"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=18"
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"dist"
|
|
50
|
+
],
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/DHxWhy/CCgather.git"
|
|
54
|
+
}
|
|
55
|
+
}
|