ccgather 1.0.1 → 1.1.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 +213 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -76,16 +76,16 @@ __export(reset_exports, {
|
|
|
76
76
|
reset: () => reset
|
|
77
77
|
});
|
|
78
78
|
function getClaudeSettingsDir() {
|
|
79
|
-
return
|
|
79
|
+
return path4.join(os4.homedir(), ".claude");
|
|
80
80
|
}
|
|
81
81
|
function removeStopHook() {
|
|
82
82
|
const claudeDir = getClaudeSettingsDir();
|
|
83
|
-
const settingsPath =
|
|
84
|
-
if (!
|
|
83
|
+
const settingsPath = path4.join(claudeDir, "settings.json");
|
|
84
|
+
if (!fs4.existsSync(settingsPath)) {
|
|
85
85
|
return { success: true, message: "No settings file found" };
|
|
86
86
|
}
|
|
87
87
|
try {
|
|
88
|
-
const content =
|
|
88
|
+
const content = fs4.readFileSync(settingsPath, "utf-8");
|
|
89
89
|
const settings = JSON.parse(content);
|
|
90
90
|
if (settings.hooks && typeof settings.hooks === "object") {
|
|
91
91
|
const hooks = settings.hooks;
|
|
@@ -102,7 +102,7 @@ function removeStopHook() {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
|
-
|
|
105
|
+
fs4.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
106
106
|
return { success: true, message: "Hook removed" };
|
|
107
107
|
} catch (err) {
|
|
108
108
|
return {
|
|
@@ -113,9 +113,9 @@ function removeStopHook() {
|
|
|
113
113
|
}
|
|
114
114
|
function removeSyncScript() {
|
|
115
115
|
const claudeDir = getClaudeSettingsDir();
|
|
116
|
-
const scriptPath =
|
|
117
|
-
if (
|
|
118
|
-
|
|
116
|
+
const scriptPath = path4.join(claudeDir, "ccgather-sync.js");
|
|
117
|
+
if (fs4.existsSync(scriptPath)) {
|
|
118
|
+
fs4.unlinkSync(scriptPath);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
async function reset() {
|
|
@@ -173,15 +173,15 @@ async function reset() {
|
|
|
173
173
|
console.log(import_chalk3.default.gray("Run `npx ccgather` to set up again."));
|
|
174
174
|
console.log();
|
|
175
175
|
}
|
|
176
|
-
var import_chalk3, import_ora3,
|
|
176
|
+
var import_chalk3, import_ora3, fs4, path4, os4, import_inquirer;
|
|
177
177
|
var init_reset = __esm({
|
|
178
178
|
"src/commands/reset.ts"() {
|
|
179
179
|
"use strict";
|
|
180
180
|
import_chalk3 = __toESM(require("chalk"));
|
|
181
181
|
import_ora3 = __toESM(require("ora"));
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
fs4 = __toESM(require("fs"));
|
|
183
|
+
path4 = __toESM(require("path"));
|
|
184
|
+
os4 = __toESM(require("os"));
|
|
185
185
|
import_inquirer = __toESM(require("inquirer"));
|
|
186
186
|
init_config();
|
|
187
187
|
}
|
|
@@ -193,28 +193,95 @@ var import_commander = require("commander");
|
|
|
193
193
|
// src/commands/submit.ts
|
|
194
194
|
var import_chalk = __toESM(require("chalk"));
|
|
195
195
|
var import_ora = __toESM(require("ora"));
|
|
196
|
+
var fs3 = __toESM(require("fs"));
|
|
197
|
+
var path3 = __toESM(require("path"));
|
|
198
|
+
var os3 = __toESM(require("os"));
|
|
199
|
+
init_config();
|
|
200
|
+
|
|
201
|
+
// src/lib/ccgather-json.ts
|
|
196
202
|
var fs2 = __toESM(require("fs"));
|
|
197
203
|
var path2 = __toESM(require("path"));
|
|
198
204
|
var os2 = __toESM(require("os"));
|
|
199
|
-
init_config();
|
|
200
205
|
|
|
201
|
-
// src/lib/
|
|
206
|
+
// src/lib/credentials.ts
|
|
202
207
|
var fs = __toESM(require("fs"));
|
|
203
208
|
var path = __toESM(require("path"));
|
|
204
209
|
var os = __toESM(require("os"));
|
|
205
|
-
|
|
210
|
+
function getCredentialsPath() {
|
|
211
|
+
return path.join(os.homedir(), ".claude", ".credentials.json");
|
|
212
|
+
}
|
|
213
|
+
function mapSubscriptionToCCPlan(subscriptionType) {
|
|
214
|
+
if (!subscriptionType) {
|
|
215
|
+
return "free";
|
|
216
|
+
}
|
|
217
|
+
const type = subscriptionType.toLowerCase();
|
|
218
|
+
if (type === "max" || type.includes("max")) {
|
|
219
|
+
return "max";
|
|
220
|
+
}
|
|
221
|
+
if (type === "team" || type === "enterprise") {
|
|
222
|
+
return "team";
|
|
223
|
+
}
|
|
224
|
+
if (type === "pro") {
|
|
225
|
+
return "pro";
|
|
226
|
+
}
|
|
227
|
+
return "free";
|
|
228
|
+
}
|
|
229
|
+
function readCredentials() {
|
|
230
|
+
const credentialsPath = getCredentialsPath();
|
|
231
|
+
const defaultData = {
|
|
232
|
+
ccplan: null,
|
|
233
|
+
rateLimitTier: null
|
|
234
|
+
};
|
|
235
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
236
|
+
return defaultData;
|
|
237
|
+
}
|
|
238
|
+
try {
|
|
239
|
+
const content = fs.readFileSync(credentialsPath, "utf-8");
|
|
240
|
+
const credentials = JSON.parse(content);
|
|
241
|
+
const oauthData = credentials.claudeAiOauth;
|
|
242
|
+
if (!oauthData) {
|
|
243
|
+
return defaultData;
|
|
244
|
+
}
|
|
245
|
+
const ccplan = mapSubscriptionToCCPlan(oauthData.subscriptionType);
|
|
246
|
+
const rateLimitTier = oauthData.rateLimitTier || null;
|
|
247
|
+
return {
|
|
248
|
+
ccplan,
|
|
249
|
+
rateLimitTier
|
|
250
|
+
};
|
|
251
|
+
} catch (error) {
|
|
252
|
+
return defaultData;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// src/lib/ccgather-json.ts
|
|
257
|
+
var CCGATHER_JSON_VERSION = "1.2.0";
|
|
258
|
+
function extractProjectName(filePath) {
|
|
259
|
+
const parts = filePath.split(/[/\\]/);
|
|
260
|
+
const projectsIndex = parts.findIndex((p) => p === "projects");
|
|
261
|
+
if (projectsIndex >= 0 && parts[projectsIndex + 1]) {
|
|
262
|
+
try {
|
|
263
|
+
const encoded = parts[projectsIndex + 1];
|
|
264
|
+
const decoded = decodeURIComponent(encoded);
|
|
265
|
+
const pathParts = decoded.split(/[/\\]/);
|
|
266
|
+
return pathParts[pathParts.length - 1] || decoded;
|
|
267
|
+
} catch {
|
|
268
|
+
return parts[projectsIndex + 1];
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return "unknown";
|
|
272
|
+
}
|
|
206
273
|
function getCCGatherJsonPath() {
|
|
207
|
-
return
|
|
274
|
+
return path2.join(os2.homedir(), ".claude", "ccgather.json");
|
|
208
275
|
}
|
|
209
276
|
function getClaudeProjectsDir() {
|
|
210
|
-
return
|
|
277
|
+
return path2.join(os2.homedir(), ".claude", "projects");
|
|
211
278
|
}
|
|
212
279
|
function findJsonlFiles(dir) {
|
|
213
280
|
const files = [];
|
|
214
281
|
try {
|
|
215
|
-
const entries =
|
|
282
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
216
283
|
for (const entry of entries) {
|
|
217
|
-
const fullPath =
|
|
284
|
+
const fullPath = path2.join(dir, entry.name);
|
|
218
285
|
if (entry.isDirectory()) {
|
|
219
286
|
files.push(...findJsonlFiles(fullPath));
|
|
220
287
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
@@ -230,7 +297,7 @@ function estimateCost(model, inputTokens, outputTokens) {
|
|
|
230
297
|
"claude-opus-4": { input: 15, output: 75 },
|
|
231
298
|
"claude-sonnet-4": { input: 3, output: 15 },
|
|
232
299
|
"claude-haiku": { input: 0.25, output: 1.25 },
|
|
233
|
-
|
|
300
|
+
default: { input: 3, output: 15 }
|
|
234
301
|
};
|
|
235
302
|
let modelKey = "default";
|
|
236
303
|
for (const key of Object.keys(pricing)) {
|
|
@@ -246,7 +313,7 @@ function estimateCost(model, inputTokens, outputTokens) {
|
|
|
246
313
|
}
|
|
247
314
|
function scanUsageData() {
|
|
248
315
|
const projectsDir = getClaudeProjectsDir();
|
|
249
|
-
if (!
|
|
316
|
+
if (!fs2.existsSync(projectsDir)) {
|
|
250
317
|
return null;
|
|
251
318
|
}
|
|
252
319
|
let totalInputTokens = 0;
|
|
@@ -257,13 +324,25 @@ function scanUsageData() {
|
|
|
257
324
|
let sessionsCount = 0;
|
|
258
325
|
const dates = /* @__PURE__ */ new Set();
|
|
259
326
|
const models = {};
|
|
327
|
+
const projects = {};
|
|
328
|
+
const dailyData = {};
|
|
260
329
|
let firstTimestamp = null;
|
|
261
330
|
let lastTimestamp = null;
|
|
262
331
|
const jsonlFiles = findJsonlFiles(projectsDir);
|
|
263
332
|
sessionsCount = jsonlFiles.length;
|
|
264
333
|
for (const filePath of jsonlFiles) {
|
|
334
|
+
const projectName = extractProjectName(filePath);
|
|
335
|
+
if (!projects[projectName]) {
|
|
336
|
+
projects[projectName] = {
|
|
337
|
+
tokens: 0,
|
|
338
|
+
cost: 0,
|
|
339
|
+
sessions: 0,
|
|
340
|
+
models: {}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
projects[projectName].sessions++;
|
|
265
344
|
try {
|
|
266
|
-
const content =
|
|
345
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
267
346
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
268
347
|
for (const line of lines) {
|
|
269
348
|
try {
|
|
@@ -277,12 +356,32 @@ function scanUsageData() {
|
|
|
277
356
|
totalOutputTokens += outputTokens;
|
|
278
357
|
totalCacheRead += usage.cache_read_input_tokens || 0;
|
|
279
358
|
totalCacheWrite += usage.cache_creation_input_tokens || 0;
|
|
280
|
-
|
|
359
|
+
const messageCost = estimateCost(model, inputTokens, outputTokens);
|
|
360
|
+
totalCost += messageCost;
|
|
281
361
|
const totalModelTokens = inputTokens + outputTokens;
|
|
282
362
|
models[model] = (models[model] || 0) + totalModelTokens;
|
|
363
|
+
projects[projectName].tokens += totalModelTokens;
|
|
364
|
+
projects[projectName].cost += messageCost;
|
|
365
|
+
projects[projectName].models[model] = (projects[projectName].models[model] || 0) + totalModelTokens;
|
|
283
366
|
if (event.timestamp) {
|
|
284
367
|
const date = new Date(event.timestamp).toISOString().split("T")[0];
|
|
285
368
|
dates.add(date);
|
|
369
|
+
if (!dailyData[date]) {
|
|
370
|
+
dailyData[date] = {
|
|
371
|
+
tokens: 0,
|
|
372
|
+
cost: 0,
|
|
373
|
+
inputTokens: 0,
|
|
374
|
+
outputTokens: 0,
|
|
375
|
+
sessions: /* @__PURE__ */ new Set(),
|
|
376
|
+
models: {}
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
dailyData[date].tokens += totalModelTokens;
|
|
380
|
+
dailyData[date].cost += messageCost;
|
|
381
|
+
dailyData[date].inputTokens += inputTokens;
|
|
382
|
+
dailyData[date].outputTokens += outputTokens;
|
|
383
|
+
dailyData[date].sessions.add(filePath);
|
|
384
|
+
dailyData[date].models[model] = (dailyData[date].models[model] || 0) + totalModelTokens;
|
|
286
385
|
if (!firstTimestamp || event.timestamp < firstTimestamp) {
|
|
287
386
|
firstTimestamp = event.timestamp;
|
|
288
387
|
}
|
|
@@ -301,6 +400,19 @@ function scanUsageData() {
|
|
|
301
400
|
if (totalTokens === 0) {
|
|
302
401
|
return null;
|
|
303
402
|
}
|
|
403
|
+
for (const projectName of Object.keys(projects)) {
|
|
404
|
+
projects[projectName].cost = Math.round(projects[projectName].cost * 100) / 100;
|
|
405
|
+
}
|
|
406
|
+
const dailyUsage = Object.entries(dailyData).map(([date, data]) => ({
|
|
407
|
+
date,
|
|
408
|
+
tokens: data.tokens,
|
|
409
|
+
cost: Math.round(data.cost * 100) / 100,
|
|
410
|
+
inputTokens: data.inputTokens,
|
|
411
|
+
outputTokens: data.outputTokens,
|
|
412
|
+
sessions: data.sessions.size,
|
|
413
|
+
models: data.models
|
|
414
|
+
})).sort((a, b) => a.date.localeCompare(b.date));
|
|
415
|
+
const credentials = readCredentials();
|
|
304
416
|
return {
|
|
305
417
|
version: CCGATHER_JSON_VERSION,
|
|
306
418
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -319,16 +431,22 @@ function scanUsageData() {
|
|
|
319
431
|
firstUsed: firstTimestamp ? new Date(firstTimestamp).toISOString().split("T")[0] : null,
|
|
320
432
|
lastUsed: lastTimestamp ? new Date(lastTimestamp).toISOString().split("T")[0] : null
|
|
321
433
|
},
|
|
322
|
-
models
|
|
434
|
+
models,
|
|
435
|
+
projects,
|
|
436
|
+
dailyUsage,
|
|
437
|
+
account: {
|
|
438
|
+
ccplan: credentials.ccplan,
|
|
439
|
+
rateLimitTier: credentials.rateLimitTier
|
|
440
|
+
}
|
|
323
441
|
};
|
|
324
442
|
}
|
|
325
443
|
function readCCGatherJson() {
|
|
326
444
|
const jsonPath = getCCGatherJsonPath();
|
|
327
|
-
if (!
|
|
445
|
+
if (!fs2.existsSync(jsonPath)) {
|
|
328
446
|
return null;
|
|
329
447
|
}
|
|
330
448
|
try {
|
|
331
|
-
const content =
|
|
449
|
+
const content = fs2.readFileSync(jsonPath, "utf-8");
|
|
332
450
|
return JSON.parse(content);
|
|
333
451
|
} catch {
|
|
334
452
|
return null;
|
|
@@ -336,11 +454,11 @@ function readCCGatherJson() {
|
|
|
336
454
|
}
|
|
337
455
|
function writeCCGatherJson(data) {
|
|
338
456
|
const jsonPath = getCCGatherJsonPath();
|
|
339
|
-
const claudeDir =
|
|
340
|
-
if (!
|
|
341
|
-
|
|
457
|
+
const claudeDir = path2.dirname(jsonPath);
|
|
458
|
+
if (!fs2.existsSync(claudeDir)) {
|
|
459
|
+
fs2.mkdirSync(claudeDir, { recursive: true });
|
|
342
460
|
}
|
|
343
|
-
|
|
461
|
+
fs2.writeFileSync(jsonPath, JSON.stringify(data, null, 2));
|
|
344
462
|
}
|
|
345
463
|
function scanAndSave() {
|
|
346
464
|
const data = scanUsageData();
|
|
@@ -364,12 +482,12 @@ function ccgatherToUsageData(data) {
|
|
|
364
482
|
}
|
|
365
483
|
function findCcJson() {
|
|
366
484
|
const possiblePaths = [
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
485
|
+
path3.join(process.cwd(), "cc.json"),
|
|
486
|
+
path3.join(os3.homedir(), "cc.json"),
|
|
487
|
+
path3.join(os3.homedir(), ".claude", "cc.json")
|
|
370
488
|
];
|
|
371
489
|
for (const p of possiblePaths) {
|
|
372
|
-
if (
|
|
490
|
+
if (fs3.existsSync(p)) {
|
|
373
491
|
return p;
|
|
374
492
|
}
|
|
375
493
|
}
|
|
@@ -377,7 +495,7 @@ function findCcJson() {
|
|
|
377
495
|
}
|
|
378
496
|
function parseCcJson(filePath) {
|
|
379
497
|
try {
|
|
380
|
-
const content =
|
|
498
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
381
499
|
const data = JSON.parse(content);
|
|
382
500
|
return {
|
|
383
501
|
totalTokens: data.totalTokens || data.total_tokens || 0,
|
|
@@ -674,14 +792,14 @@ async function status(options) {
|
|
|
674
792
|
var import_chalk4 = __toESM(require("chalk"));
|
|
675
793
|
var import_ora4 = __toESM(require("ora"));
|
|
676
794
|
var http = __toESM(require("http"));
|
|
677
|
-
var
|
|
678
|
-
var
|
|
679
|
-
var
|
|
795
|
+
var fs5 = __toESM(require("fs"));
|
|
796
|
+
var path5 = __toESM(require("path"));
|
|
797
|
+
var os5 = __toESM(require("os"));
|
|
680
798
|
init_config();
|
|
681
799
|
init_config();
|
|
682
800
|
var CALLBACK_PORT = 9876;
|
|
683
801
|
function getClaudeSettingsDir2() {
|
|
684
|
-
return
|
|
802
|
+
return path5.join(os5.homedir(), ".claude");
|
|
685
803
|
}
|
|
686
804
|
async function openBrowser(url) {
|
|
687
805
|
const { default: open } = await import("open");
|
|
@@ -911,14 +1029,14 @@ if (usageData) {
|
|
|
911
1029
|
}
|
|
912
1030
|
function installStopHook() {
|
|
913
1031
|
const claudeDir = getClaudeSettingsDir2();
|
|
914
|
-
const settingsPath =
|
|
915
|
-
if (!
|
|
916
|
-
|
|
1032
|
+
const settingsPath = path5.join(claudeDir, "settings.json");
|
|
1033
|
+
if (!fs5.existsSync(claudeDir)) {
|
|
1034
|
+
fs5.mkdirSync(claudeDir, { recursive: true });
|
|
917
1035
|
}
|
|
918
1036
|
let settings = {};
|
|
919
1037
|
try {
|
|
920
|
-
if (
|
|
921
|
-
const content =
|
|
1038
|
+
if (fs5.existsSync(settingsPath)) {
|
|
1039
|
+
const content = fs5.readFileSync(settingsPath, "utf-8");
|
|
922
1040
|
settings = JSON.parse(content);
|
|
923
1041
|
}
|
|
924
1042
|
} catch {
|
|
@@ -927,7 +1045,7 @@ function installStopHook() {
|
|
|
927
1045
|
settings.hooks = {};
|
|
928
1046
|
}
|
|
929
1047
|
const hooks = settings.hooks;
|
|
930
|
-
const syncScriptPath =
|
|
1048
|
+
const syncScriptPath = path5.join(claudeDir, "ccgather-sync.js");
|
|
931
1049
|
const hookCommand = `node "${syncScriptPath}"`;
|
|
932
1050
|
if (!hooks.Stop || !Array.isArray(hooks.Stop)) {
|
|
933
1051
|
hooks.Stop = [];
|
|
@@ -941,16 +1059,16 @@ function installStopHook() {
|
|
|
941
1059
|
background: true
|
|
942
1060
|
});
|
|
943
1061
|
}
|
|
944
|
-
|
|
1062
|
+
fs5.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
945
1063
|
return { success: true, message: "Stop hook installed" };
|
|
946
1064
|
}
|
|
947
1065
|
function saveSyncScript(apiUrl, apiToken) {
|
|
948
1066
|
const claudeDir = getClaudeSettingsDir2();
|
|
949
|
-
const scriptPath =
|
|
1067
|
+
const scriptPath = path5.join(claudeDir, "ccgather-sync.js");
|
|
950
1068
|
const scriptContent = generateSyncScript(apiUrl, apiToken);
|
|
951
|
-
|
|
952
|
-
if (
|
|
953
|
-
|
|
1069
|
+
fs5.writeFileSync(scriptPath, scriptContent);
|
|
1070
|
+
if (os5.platform() !== "win32") {
|
|
1071
|
+
fs5.chmodSync(scriptPath, "755");
|
|
954
1072
|
}
|
|
955
1073
|
}
|
|
956
1074
|
async function setupAuto(options = {}) {
|
|
@@ -1067,20 +1185,41 @@ function displayResults(data) {
|
|
|
1067
1185
|
const gray = import_chalk5.default.gray;
|
|
1068
1186
|
const white = import_chalk5.default.white;
|
|
1069
1187
|
const bold = import_chalk5.default.bold;
|
|
1188
|
+
const cyan = import_chalk5.default.cyan;
|
|
1070
1189
|
console.log();
|
|
1071
1190
|
console.log(gray(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
1072
|
-
console.log(
|
|
1191
|
+
console.log(
|
|
1192
|
+
gray(" \u2502") + white(" \u{1F4CA} Usage Summary") + gray(" \u2502")
|
|
1193
|
+
);
|
|
1073
1194
|
console.log(gray(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
1074
|
-
console.log(
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
console.log(
|
|
1195
|
+
console.log(
|
|
1196
|
+
gray(" \u2502") + ` Total Tokens: ${orange(formatNumber3(data.usage.totalTokens).padEnd(15))}` + gray(" \u2502")
|
|
1197
|
+
);
|
|
1198
|
+
console.log(
|
|
1199
|
+
gray(" \u2502") + ` Total Cost: ${green("$" + data.usage.totalCost.toFixed(2).padEnd(14))}` + gray(" \u2502")
|
|
1200
|
+
);
|
|
1201
|
+
console.log(
|
|
1202
|
+
gray(" \u2502") + ` Input Tokens: ${white(formatNumber3(data.usage.inputTokens).padEnd(15))}` + gray(" \u2502")
|
|
1203
|
+
);
|
|
1204
|
+
console.log(
|
|
1205
|
+
gray(" \u2502") + ` Output Tokens: ${white(formatNumber3(data.usage.outputTokens).padEnd(15))}` + gray(" \u2502")
|
|
1206
|
+
);
|
|
1078
1207
|
console.log(gray(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
1079
|
-
console.log(
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
console.log(
|
|
1083
|
-
|
|
1208
|
+
console.log(
|
|
1209
|
+
gray(" \u2502") + white(" \u{1F4C8} Stats") + gray(" \u2502")
|
|
1210
|
+
);
|
|
1211
|
+
console.log(
|
|
1212
|
+
gray(" \u2502") + ` Days Tracked: ${white(data.stats.daysTracked.toString().padEnd(15))}` + gray(" \u2502")
|
|
1213
|
+
);
|
|
1214
|
+
console.log(
|
|
1215
|
+
gray(" \u2502") + ` Sessions: ${white(data.stats.sessionsCount.toString().padEnd(15))}` + gray(" \u2502")
|
|
1216
|
+
);
|
|
1217
|
+
console.log(
|
|
1218
|
+
gray(" \u2502") + ` First Used: ${gray((data.stats.firstUsed || "N/A").padEnd(15))}` + gray(" \u2502")
|
|
1219
|
+
);
|
|
1220
|
+
console.log(
|
|
1221
|
+
gray(" \u2502") + ` Last Used: ${gray((data.stats.lastUsed || "N/A").padEnd(15))}` + gray(" \u2502")
|
|
1222
|
+
);
|
|
1084
1223
|
console.log(gray(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1085
1224
|
if (Object.keys(data.models).length > 0) {
|
|
1086
1225
|
console.log();
|
|
@@ -1090,6 +1229,20 @@ function displayResults(data) {
|
|
|
1090
1229
|
console.log(gray(" \u2022 ") + white(shortModel.padEnd(22)) + orange(formatNumber3(tokens)));
|
|
1091
1230
|
}
|
|
1092
1231
|
}
|
|
1232
|
+
if (data.projects && Object.keys(data.projects).length > 0) {
|
|
1233
|
+
console.log();
|
|
1234
|
+
console.log(gray(" ") + bold("Top Projects:"));
|
|
1235
|
+
const sortedProjects = Object.entries(data.projects).sort(([, a], [, b]) => b.tokens - a.tokens).slice(0, 5);
|
|
1236
|
+
for (const [name, stats] of sortedProjects) {
|
|
1237
|
+
const displayName = name.length > 25 ? name.substring(0, 22) + "..." : name;
|
|
1238
|
+
console.log(
|
|
1239
|
+
gray(" \u{1F4C1} ") + cyan(displayName.padEnd(25)) + orange(formatNumber3(stats.tokens).padStart(8)) + green(` $${stats.cost.toFixed(2).padStart(8)}`) + gray(` (${stats.sessions} sessions)`)
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
if (Object.keys(data.projects).length > 5) {
|
|
1243
|
+
console.log(gray(` ... and ${Object.keys(data.projects).length - 5} more projects`));
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1093
1246
|
console.log();
|
|
1094
1247
|
console.log(gray(` \u{1F4C1} Saved to: ${getCCGatherJsonPath()}`));
|
|
1095
1248
|
console.log();
|