ccgather 1.3.4 → 1.3.6
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 +445 -1417
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -70,7 +70,168 @@ var init_config = __esm({
|
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
// src/commands/auth.ts
|
|
74
|
+
var auth_exports = {};
|
|
75
|
+
__export(auth_exports, {
|
|
76
|
+
auth: () => auth
|
|
77
|
+
});
|
|
78
|
+
async function auth(options) {
|
|
79
|
+
const config = getConfig();
|
|
80
|
+
console.log(import_chalk2.default.bold("\n\u{1F510} CCgather Authentication\n"));
|
|
81
|
+
const existingToken = config.get("apiToken");
|
|
82
|
+
if (existingToken) {
|
|
83
|
+
const { overwrite } = await import_inquirer2.default.prompt([
|
|
84
|
+
{
|
|
85
|
+
type: "confirm",
|
|
86
|
+
name: "overwrite",
|
|
87
|
+
message: "You are already authenticated. Do you want to re-authenticate?",
|
|
88
|
+
default: false
|
|
89
|
+
}
|
|
90
|
+
]);
|
|
91
|
+
if (!overwrite) {
|
|
92
|
+
console.log(import_chalk2.default.gray("Authentication cancelled.\n"));
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (options.token) {
|
|
97
|
+
await authenticateWithToken(options.token);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const apiUrl = getApiUrl();
|
|
101
|
+
const spinner = (0, import_ora3.default)("Initializing authentication...").start();
|
|
102
|
+
try {
|
|
103
|
+
const response = await fetch(`${apiUrl}/cli/auth/device`, {
|
|
104
|
+
method: "POST"
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
spinner.fail(import_chalk2.default.red("Failed to initialize authentication"));
|
|
108
|
+
console.log(import_chalk2.default.red("\nPlease check your internet connection and try again.\n"));
|
|
109
|
+
process.exit(1);
|
|
110
|
+
}
|
|
111
|
+
const deviceData = await response.json();
|
|
112
|
+
spinner.stop();
|
|
113
|
+
console.log(import_chalk2.default.gray(" Opening browser for authentication...\n"));
|
|
114
|
+
console.log(import_chalk2.default.gray(" If browser doesn't open, visit:"));
|
|
115
|
+
console.log(` \u{1F517} ${import_chalk2.default.cyan.underline(deviceData.verification_uri_complete)}`);
|
|
116
|
+
console.log();
|
|
117
|
+
try {
|
|
118
|
+
await (0, import_open.default)(deviceData.verification_uri_complete);
|
|
119
|
+
} catch {
|
|
120
|
+
console.log(import_chalk2.default.yellow(" Could not open browser automatically."));
|
|
121
|
+
console.log(import_chalk2.default.yellow(" Please open the URL above manually.\n"));
|
|
122
|
+
}
|
|
123
|
+
const pollSpinner = (0, import_ora3.default)("Waiting for authorization...").start();
|
|
124
|
+
const startTime = Date.now();
|
|
125
|
+
const expiresAt = startTime + deviceData.expires_in * 1e3;
|
|
126
|
+
const pollInterval = Math.max(deviceData.interval * 1e3, 5e3);
|
|
127
|
+
while (Date.now() < expiresAt) {
|
|
128
|
+
await sleep2(pollInterval);
|
|
129
|
+
try {
|
|
130
|
+
const pollResponse = await fetch(
|
|
131
|
+
`${apiUrl}/cli/auth/device/poll?device_code=${deviceData.device_code}`
|
|
132
|
+
);
|
|
133
|
+
const pollData = await pollResponse.json();
|
|
134
|
+
if (pollData.status === "authorized" && pollData.token) {
|
|
135
|
+
pollSpinner.succeed(import_chalk2.default.green("Authentication successful!"));
|
|
136
|
+
config.set("apiToken", pollData.token);
|
|
137
|
+
config.set("userId", pollData.userId);
|
|
138
|
+
config.set("username", pollData.username);
|
|
139
|
+
console.log(import_chalk2.default.gray(`
|
|
140
|
+
Welcome, ${import_chalk2.default.white(pollData.username)}!`));
|
|
141
|
+
console.log(import_chalk2.default.dim("You can now submit your usage data.\n"));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (pollData.status === "expired" || pollData.status === "used") {
|
|
145
|
+
pollSpinner.fail(import_chalk2.default.red("Authentication expired or already used"));
|
|
146
|
+
console.log(import_chalk2.default.gray("\nPlease try again.\n"));
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
const remaining = Math.ceil((expiresAt - Date.now()) / 1e3);
|
|
150
|
+
pollSpinner.text = `Waiting for authorization... (${remaining}s remaining)`;
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
pollSpinner.fail(import_chalk2.default.red("Authentication timed out"));
|
|
155
|
+
console.log(import_chalk2.default.gray("\nPlease try again.\n"));
|
|
156
|
+
process.exit(1);
|
|
157
|
+
} catch (error2) {
|
|
158
|
+
spinner.fail(import_chalk2.default.red("Authentication failed"));
|
|
159
|
+
console.log(import_chalk2.default.red(`
|
|
160
|
+
Error: ${error2 instanceof Error ? error2.message : "Unknown error"}
|
|
161
|
+
`));
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
async function authenticateWithToken(token) {
|
|
166
|
+
const config = getConfig();
|
|
167
|
+
const apiUrl = getApiUrl();
|
|
168
|
+
const spinner = (0, import_ora3.default)("Verifying token...").start();
|
|
169
|
+
try {
|
|
170
|
+
const response = await fetch(`${apiUrl}/cli/verify`, {
|
|
171
|
+
method: "POST",
|
|
172
|
+
headers: {
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
Authorization: `Bearer ${token}`
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
if (!response.ok) {
|
|
178
|
+
spinner.fail(import_chalk2.default.red("Authentication failed"));
|
|
179
|
+
const errorData = await response.json().catch(() => ({}));
|
|
180
|
+
console.log(import_chalk2.default.red(`Error: ${errorData.error || "Invalid token"}`));
|
|
181
|
+
console.log(import_chalk2.default.gray("\nMake sure your token is correct and try again.\n"));
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
const data = await response.json();
|
|
185
|
+
config.set("apiToken", token);
|
|
186
|
+
config.set("userId", data.userId);
|
|
187
|
+
config.set("username", data.username);
|
|
188
|
+
spinner.succeed(import_chalk2.default.green("Authentication successful!"));
|
|
189
|
+
console.log(import_chalk2.default.gray(`
|
|
190
|
+
Welcome, ${import_chalk2.default.white(data.username)}!`));
|
|
191
|
+
console.log(import_chalk2.default.dim("You can now submit your usage data.\n"));
|
|
192
|
+
} catch (error2) {
|
|
193
|
+
spinner.fail(import_chalk2.default.red("Authentication failed"));
|
|
194
|
+
console.log(import_chalk2.default.red(`
|
|
195
|
+
Error: ${error2 instanceof Error ? error2.message : "Unknown error"}
|
|
196
|
+
`));
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
function sleep2(ms) {
|
|
201
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
202
|
+
}
|
|
203
|
+
var import_chalk2, import_ora3, import_inquirer2, import_open;
|
|
204
|
+
var init_auth = __esm({
|
|
205
|
+
"src/commands/auth.ts"() {
|
|
206
|
+
"use strict";
|
|
207
|
+
import_chalk2 = __toESM(require("chalk"));
|
|
208
|
+
import_ora3 = __toESM(require("ora"));
|
|
209
|
+
import_inquirer2 = __toESM(require("inquirer"));
|
|
210
|
+
import_open = __toESM(require("open"));
|
|
211
|
+
init_config();
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// src/index.ts
|
|
216
|
+
var import_commander = require("commander");
|
|
217
|
+
var import_inquirer3 = __toESM(require("inquirer"));
|
|
218
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
219
|
+
var import_update_notifier = __toESM(require("update-notifier"));
|
|
220
|
+
|
|
221
|
+
// src/commands/submit.ts
|
|
222
|
+
var import_ora = __toESM(require("ora"));
|
|
223
|
+
var import_inquirer = __toESM(require("inquirer"));
|
|
224
|
+
init_config();
|
|
225
|
+
|
|
226
|
+
// src/lib/ccgather-json.ts
|
|
227
|
+
var fs2 = __toESM(require("fs"));
|
|
228
|
+
var path2 = __toESM(require("path"));
|
|
229
|
+
var os2 = __toESM(require("os"));
|
|
230
|
+
|
|
73
231
|
// src/lib/credentials.ts
|
|
232
|
+
var fs = __toESM(require("fs"));
|
|
233
|
+
var path = __toESM(require("path"));
|
|
234
|
+
var os = __toESM(require("os"));
|
|
74
235
|
function getCredentialsPath() {
|
|
75
236
|
return path.join(os.homedir(), ".claude", ".credentials.json");
|
|
76
237
|
}
|
|
@@ -116,17 +277,9 @@ function readCredentials() {
|
|
|
116
277
|
return defaultData;
|
|
117
278
|
}
|
|
118
279
|
}
|
|
119
|
-
var fs, path, os;
|
|
120
|
-
var init_credentials = __esm({
|
|
121
|
-
"src/lib/credentials.ts"() {
|
|
122
|
-
"use strict";
|
|
123
|
-
fs = __toESM(require("fs"));
|
|
124
|
-
path = __toESM(require("path"));
|
|
125
|
-
os = __toESM(require("os"));
|
|
126
|
-
}
|
|
127
|
-
});
|
|
128
280
|
|
|
129
281
|
// src/lib/ccgather-json.ts
|
|
282
|
+
var CCGATHER_JSON_VERSION = "1.2.0";
|
|
130
283
|
function extractProjectName(filePath) {
|
|
131
284
|
const parts = filePath.split(/[/\\]/);
|
|
132
285
|
const projectsIndex = parts.findIndex((p) => p === "projects");
|
|
@@ -164,12 +317,12 @@ function findJsonlFiles(dir) {
|
|
|
164
317
|
}
|
|
165
318
|
return files;
|
|
166
319
|
}
|
|
167
|
-
function estimateCost(model, inputTokens, outputTokens) {
|
|
320
|
+
function estimateCost(model, inputTokens, outputTokens, cacheWriteTokens = 0, cacheReadTokens = 0) {
|
|
168
321
|
const pricing = {
|
|
169
|
-
"claude-opus-4": { input: 15, output: 75 },
|
|
170
|
-
"claude-sonnet-4": { input: 3, output: 15 },
|
|
171
|
-
"claude-haiku": { input: 0.25, output: 1.25 },
|
|
172
|
-
default: { input: 3, output: 15 }
|
|
322
|
+
"claude-opus-4": { input: 15, output: 75, cacheWrite: 18.75, cacheRead: 1.5 },
|
|
323
|
+
"claude-sonnet-4": { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 },
|
|
324
|
+
"claude-haiku": { input: 0.25, output: 1.25, cacheWrite: 0.3125, cacheRead: 0.025 },
|
|
325
|
+
default: { input: 3, output: 15, cacheWrite: 3.75, cacheRead: 0.3 }
|
|
173
326
|
};
|
|
174
327
|
let modelKey = "default";
|
|
175
328
|
for (const key of Object.keys(pricing)) {
|
|
@@ -181,7 +334,9 @@ function estimateCost(model, inputTokens, outputTokens) {
|
|
|
181
334
|
const price = pricing[modelKey];
|
|
182
335
|
const inputCost = inputTokens / 1e6 * price.input;
|
|
183
336
|
const outputCost = outputTokens / 1e6 * price.output;
|
|
184
|
-
|
|
337
|
+
const cacheWriteCost = cacheWriteTokens / 1e6 * price.cacheWrite;
|
|
338
|
+
const cacheReadCost = cacheReadTokens / 1e6 * price.cacheRead;
|
|
339
|
+
return Math.round((inputCost + outputCost + cacheWriteCost + cacheReadCost) * 100) / 100;
|
|
185
340
|
}
|
|
186
341
|
function scanUsageData(options = {}) {
|
|
187
342
|
const projectsDir = getClaudeProjectsDir();
|
|
@@ -210,7 +365,12 @@ function scanUsageData(options = {}) {
|
|
|
210
365
|
let lastTimestamp = null;
|
|
211
366
|
const jsonlFiles = findJsonlFiles(projectsDir);
|
|
212
367
|
sessionsCount = jsonlFiles.length;
|
|
213
|
-
|
|
368
|
+
const { onProgress } = options;
|
|
369
|
+
for (let i = 0; i < jsonlFiles.length; i++) {
|
|
370
|
+
const filePath = jsonlFiles[i];
|
|
371
|
+
if (onProgress) {
|
|
372
|
+
onProgress(i + 1, jsonlFiles.length);
|
|
373
|
+
}
|
|
214
374
|
const projectName = extractProjectName(filePath);
|
|
215
375
|
if (!projects[projectName]) {
|
|
216
376
|
projects[projectName] = {
|
|
@@ -235,11 +395,13 @@ function scanUsageData(options = {}) {
|
|
|
235
395
|
const model = event.message.model || "unknown";
|
|
236
396
|
const inputTokens = usage.input_tokens || 0;
|
|
237
397
|
const outputTokens = usage.output_tokens || 0;
|
|
398
|
+
const cacheWrite = usage.cache_creation_input_tokens || 0;
|
|
399
|
+
const cacheRead = usage.cache_read_input_tokens || 0;
|
|
238
400
|
totalInputTokens += inputTokens;
|
|
239
401
|
totalOutputTokens += outputTokens;
|
|
240
|
-
totalCacheRead +=
|
|
241
|
-
totalCacheWrite +=
|
|
242
|
-
const messageCost = estimateCost(model, inputTokens, outputTokens);
|
|
402
|
+
totalCacheRead += cacheRead;
|
|
403
|
+
totalCacheWrite += cacheWrite;
|
|
404
|
+
const messageCost = estimateCost(model, inputTokens, outputTokens, cacheWrite, cacheRead);
|
|
243
405
|
totalCost += messageCost;
|
|
244
406
|
const totalModelTokens = inputTokens + outputTokens;
|
|
245
407
|
models[model] = (models[model] || 0) + totalModelTokens;
|
|
@@ -255,6 +417,8 @@ function scanUsageData(options = {}) {
|
|
|
255
417
|
cost: 0,
|
|
256
418
|
inputTokens: 0,
|
|
257
419
|
outputTokens: 0,
|
|
420
|
+
cacheWriteTokens: 0,
|
|
421
|
+
cacheReadTokens: 0,
|
|
258
422
|
sessions: /* @__PURE__ */ new Set(),
|
|
259
423
|
models: {}
|
|
260
424
|
};
|
|
@@ -263,6 +427,8 @@ function scanUsageData(options = {}) {
|
|
|
263
427
|
dailyData[date].cost += messageCost;
|
|
264
428
|
dailyData[date].inputTokens += inputTokens;
|
|
265
429
|
dailyData[date].outputTokens += outputTokens;
|
|
430
|
+
dailyData[date].cacheWriteTokens += cacheWrite;
|
|
431
|
+
dailyData[date].cacheReadTokens += cacheRead;
|
|
266
432
|
dailyData[date].sessions.add(filePath);
|
|
267
433
|
dailyData[date].models[model] = (dailyData[date].models[model] || 0) + totalModelTokens;
|
|
268
434
|
if (!firstTimestamp || event.timestamp < firstTimestamp) {
|
|
@@ -292,6 +458,8 @@ function scanUsageData(options = {}) {
|
|
|
292
458
|
cost: Math.round(data.cost * 100) / 100,
|
|
293
459
|
inputTokens: data.inputTokens,
|
|
294
460
|
outputTokens: data.outputTokens,
|
|
461
|
+
cacheWriteTokens: data.cacheWriteTokens,
|
|
462
|
+
cacheReadTokens: data.cacheReadTokens,
|
|
295
463
|
sessions: data.sessions.size,
|
|
296
464
|
models: data.models
|
|
297
465
|
})).sort((a, b) => a.date.localeCompare(b.date));
|
|
@@ -323,18 +491,6 @@ function scanUsageData(options = {}) {
|
|
|
323
491
|
}
|
|
324
492
|
};
|
|
325
493
|
}
|
|
326
|
-
function readCCGatherJson() {
|
|
327
|
-
const jsonPath = getCCGatherJsonPath();
|
|
328
|
-
if (!fs2.existsSync(jsonPath)) {
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
331
|
-
try {
|
|
332
|
-
const content = fs2.readFileSync(jsonPath, "utf-8");
|
|
333
|
-
return JSON.parse(content);
|
|
334
|
-
} catch {
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
494
|
function writeCCGatherJson(data) {
|
|
339
495
|
const jsonPath = getCCGatherJsonPath();
|
|
340
496
|
const claudeDir = path2.dirname(jsonPath);
|
|
@@ -350,22 +506,69 @@ function scanAndSave(options = {}) {
|
|
|
350
506
|
}
|
|
351
507
|
return data;
|
|
352
508
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
fs2 = __toESM(require("fs"));
|
|
358
|
-
path2 = __toESM(require("path"));
|
|
359
|
-
os2 = __toESM(require("os"));
|
|
360
|
-
init_credentials();
|
|
361
|
-
CCGATHER_JSON_VERSION = "1.2.0";
|
|
509
|
+
function getSessionFileCount() {
|
|
510
|
+
const projectsDir = getClaudeProjectsDir();
|
|
511
|
+
if (!fs2.existsSync(projectsDir)) {
|
|
512
|
+
return 0;
|
|
362
513
|
}
|
|
363
|
-
|
|
514
|
+
return findJsonlFiles(projectsDir).length;
|
|
515
|
+
}
|
|
364
516
|
|
|
365
517
|
// src/lib/ui.ts
|
|
518
|
+
var import_chalk = __toESM(require("chalk"));
|
|
519
|
+
var colors = {
|
|
520
|
+
primary: import_chalk.default.hex("#DA7756"),
|
|
521
|
+
// Claude coral
|
|
522
|
+
secondary: import_chalk.default.hex("#F7931E"),
|
|
523
|
+
// Orange accent
|
|
524
|
+
success: import_chalk.default.hex("#22C55E"),
|
|
525
|
+
// Green
|
|
526
|
+
warning: import_chalk.default.hex("#F59E0B"),
|
|
527
|
+
// Amber
|
|
528
|
+
error: import_chalk.default.hex("#EF4444"),
|
|
529
|
+
// Red
|
|
530
|
+
muted: import_chalk.default.hex("#71717A"),
|
|
531
|
+
// Gray
|
|
532
|
+
dim: import_chalk.default.hex("#52525B"),
|
|
533
|
+
// Dark gray
|
|
534
|
+
white: import_chalk.default.white,
|
|
535
|
+
cyan: import_chalk.default.cyan,
|
|
536
|
+
// CCplan colors
|
|
537
|
+
max: import_chalk.default.hex("#F59E0B"),
|
|
538
|
+
// Gold
|
|
539
|
+
pro: import_chalk.default.hex("#3B82F6"),
|
|
540
|
+
// Blue
|
|
541
|
+
team: import_chalk.default.hex("#8B5CF6"),
|
|
542
|
+
// Purple
|
|
543
|
+
free: import_chalk.default.hex("#6B7280")
|
|
544
|
+
// Gray
|
|
545
|
+
};
|
|
546
|
+
var LOGO = `
|
|
547
|
+
${colors.primary("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557")} ${colors.secondary("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
|
|
548
|
+
${colors.primary("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D")}${colors.secondary("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}
|
|
549
|
+
${colors.primary("\u2588\u2588\u2551 \u2588\u2588\u2551 ")}${colors.secondary("\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}
|
|
550
|
+
${colors.primary("\u2588\u2588\u2551 \u2588\u2588\u2551 ")}${colors.secondary("\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}
|
|
551
|
+
${colors.primary("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}${colors.secondary("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551")}
|
|
552
|
+
${colors.primary("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D")} ${colors.secondary("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D")}
|
|
553
|
+
`;
|
|
554
|
+
var LOGO_COMPACT = `
|
|
555
|
+
${colors.primary("CC")}${colors.secondary("gather")} ${colors.muted("- Where Claude Code Developers Gather")}
|
|
556
|
+
`;
|
|
557
|
+
var TAGLINE = colors.muted(" Where Claude Code Developers Gather");
|
|
558
|
+
var SLOGAN = colors.dim(" Gather. Compete. Rise.");
|
|
366
559
|
function getVersionLine(version) {
|
|
367
560
|
return colors.dim(` v${version} \u2022 ccgather.com`);
|
|
368
561
|
}
|
|
562
|
+
var box = {
|
|
563
|
+
topLeft: "\u250C",
|
|
564
|
+
topRight: "\u2510",
|
|
565
|
+
bottomLeft: "\u2514",
|
|
566
|
+
bottomRight: "\u2518",
|
|
567
|
+
horizontal: "\u2500",
|
|
568
|
+
vertical: "\u2502",
|
|
569
|
+
leftT: "\u251C",
|
|
570
|
+
rightT: "\u2524"
|
|
571
|
+
};
|
|
369
572
|
function createBox(lines, width = 47) {
|
|
370
573
|
const paddedLines = lines.map((line) => {
|
|
371
574
|
const visibleLength = stripAnsi(line).length;
|
|
@@ -431,22 +634,6 @@ function getLevelInfo(tokens) {
|
|
|
431
634
|
}
|
|
432
635
|
return levels[0];
|
|
433
636
|
}
|
|
434
|
-
function createWelcomeBox(user) {
|
|
435
|
-
const levelInfo = user.level && user.levelName && user.levelIcon ? `${user.levelIcon} Level ${user.level} \u2022 ${user.levelName}` : "";
|
|
436
|
-
const ccplanBadge = user.ccplan ? getCCplanBadge(user.ccplan) : "";
|
|
437
|
-
const lines = [`\u{1F44B} ${colors.white.bold(`Welcome back, ${user.username}!`)}`];
|
|
438
|
-
if (levelInfo || ccplanBadge) {
|
|
439
|
-
lines.push(`${levelInfo}${ccplanBadge ? ` ${ccplanBadge}` : ""}`);
|
|
440
|
-
}
|
|
441
|
-
if (user.globalRank) {
|
|
442
|
-
lines.push(`\u{1F30D} Global Rank: ${colors.primary(`#${user.globalRank}`)}`);
|
|
443
|
-
}
|
|
444
|
-
if (user.countryRank && user.countryCode) {
|
|
445
|
-
const flag = countryCodeToFlag(user.countryCode);
|
|
446
|
-
lines.push(`${flag} Country Rank: ${colors.primary(`#${user.countryRank}`)}`);
|
|
447
|
-
}
|
|
448
|
-
return createBox(lines);
|
|
449
|
-
}
|
|
450
637
|
function countryCodeToFlag(countryCode) {
|
|
451
638
|
if (!countryCode || countryCode.length !== 2) return "\u{1F310}";
|
|
452
639
|
const codePoints = countryCode.toUpperCase().split("").map((char) => 127462 + char.charCodeAt(0) - 65);
|
|
@@ -458,80 +645,92 @@ function success(message) {
|
|
|
458
645
|
function error(message) {
|
|
459
646
|
return `${colors.error("\u2717")} ${message}`;
|
|
460
647
|
}
|
|
461
|
-
function
|
|
462
|
-
|
|
648
|
+
function link(url) {
|
|
649
|
+
return colors.cyan.underline(url);
|
|
650
|
+
}
|
|
651
|
+
function sleep(ms) {
|
|
652
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
653
|
+
}
|
|
654
|
+
async function dotAnimation(message, durationMs = 800, speed = "normal") {
|
|
655
|
+
const frames = [".", "..", "..."];
|
|
656
|
+
const frameDelays = { slow: 200, normal: 120, fast: 80 };
|
|
657
|
+
const frameDelay = frameDelays[speed];
|
|
658
|
+
const iterations = Math.ceil(durationMs / (frames.length * frameDelay));
|
|
659
|
+
for (let i = 0; i < iterations; i++) {
|
|
660
|
+
for (const frame of frames) {
|
|
661
|
+
process.stdout.write(`\r ${colors.muted(message)}${colors.primary(frame)} `);
|
|
662
|
+
await sleep(frameDelay);
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
process.stdout.write("\r" + " ".repeat(message.length + 10) + "\r");
|
|
666
|
+
}
|
|
667
|
+
async function progressBar(current, total, message = "Processing") {
|
|
668
|
+
const width = 24;
|
|
669
|
+
const percent = Math.round(current / total * 100);
|
|
670
|
+
const filled = Math.round(current / total * width);
|
|
671
|
+
const empty = width - filled;
|
|
672
|
+
const bar = colors.primary("\u2588".repeat(filled)) + colors.dim("\u2591".repeat(empty));
|
|
673
|
+
const percentStr = colors.white(`${percent}%`);
|
|
674
|
+
process.stdout.write(`\r ${colors.muted(message)} [${bar}] ${percentStr} `);
|
|
675
|
+
if (current >= total) {
|
|
676
|
+
console.log();
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
async function printAnimatedHeader(version) {
|
|
680
|
+
const logoLines = [
|
|
681
|
+
` ${colors.primary("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557")} ${colors.secondary("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}`,
|
|
682
|
+
` ${colors.primary("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D")}${colors.secondary("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}`,
|
|
683
|
+
` ${colors.primary("\u2588\u2588\u2551 \u2588\u2588\u2551 ")}${colors.secondary("\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}`,
|
|
684
|
+
` ${colors.primary("\u2588\u2588\u2551 \u2588\u2588\u2551 ")}${colors.secondary("\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}`,
|
|
685
|
+
` ${colors.primary("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}${colors.secondary("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551")}`,
|
|
686
|
+
` ${colors.primary("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D")} ${colors.secondary("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D")}`
|
|
687
|
+
];
|
|
688
|
+
console.log();
|
|
689
|
+
for (const line of logoLines) {
|
|
690
|
+
console.log(line);
|
|
691
|
+
await sleep(50);
|
|
692
|
+
}
|
|
693
|
+
await sleep(100);
|
|
694
|
+
console.log();
|
|
463
695
|
console.log(TAGLINE);
|
|
696
|
+
await sleep(80);
|
|
464
697
|
console.log(SLOGAN);
|
|
698
|
+
await sleep(80);
|
|
465
699
|
console.log();
|
|
466
700
|
console.log(getVersionLine(version));
|
|
467
701
|
console.log();
|
|
468
702
|
}
|
|
469
|
-
function
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
}
|
|
478
|
-
var import_chalk, colors, LOGO, LOGO_COMPACT, TAGLINE, SLOGAN, box;
|
|
479
|
-
var init_ui = __esm({
|
|
480
|
-
"src/lib/ui.ts"() {
|
|
481
|
-
"use strict";
|
|
482
|
-
import_chalk = __toESM(require("chalk"));
|
|
483
|
-
colors = {
|
|
484
|
-
primary: import_chalk.default.hex("#DA7756"),
|
|
485
|
-
// Claude coral
|
|
486
|
-
secondary: import_chalk.default.hex("#F7931E"),
|
|
487
|
-
// Orange accent
|
|
488
|
-
success: import_chalk.default.hex("#22C55E"),
|
|
489
|
-
// Green
|
|
490
|
-
warning: import_chalk.default.hex("#F59E0B"),
|
|
491
|
-
// Amber
|
|
492
|
-
error: import_chalk.default.hex("#EF4444"),
|
|
493
|
-
// Red
|
|
494
|
-
muted: import_chalk.default.hex("#71717A"),
|
|
495
|
-
// Gray
|
|
496
|
-
dim: import_chalk.default.hex("#52525B"),
|
|
497
|
-
// Dark gray
|
|
498
|
-
white: import_chalk.default.white,
|
|
499
|
-
cyan: import_chalk.default.cyan,
|
|
500
|
-
// CCplan colors
|
|
501
|
-
max: import_chalk.default.hex("#F59E0B"),
|
|
502
|
-
// Gold
|
|
503
|
-
pro: import_chalk.default.hex("#3B82F6"),
|
|
504
|
-
// Blue
|
|
505
|
-
team: import_chalk.default.hex("#8B5CF6"),
|
|
506
|
-
// Purple
|
|
507
|
-
free: import_chalk.default.hex("#6B7280")
|
|
508
|
-
// Gray
|
|
509
|
-
};
|
|
510
|
-
LOGO = `
|
|
511
|
-
${colors.primary("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557")} ${colors.secondary("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
|
|
512
|
-
${colors.primary("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D")}${colors.secondary("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}
|
|
513
|
-
${colors.primary("\u2588\u2588\u2551 \u2588\u2588\u2551 ")}${colors.secondary("\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}
|
|
514
|
-
${colors.primary("\u2588\u2588\u2551 \u2588\u2588\u2551 ")}${colors.secondary("\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}
|
|
515
|
-
${colors.primary("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}${colors.secondary("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551")}
|
|
516
|
-
${colors.primary("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D")} ${colors.secondary("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D")}
|
|
517
|
-
`;
|
|
518
|
-
LOGO_COMPACT = `
|
|
519
|
-
${colors.primary("CC")}${colors.secondary("gather")} ${colors.muted("- Where Claude Code Developers Gather")}
|
|
520
|
-
`;
|
|
521
|
-
TAGLINE = colors.muted(" Where Claude Code Developers Gather");
|
|
522
|
-
SLOGAN = colors.dim(" Gather. Compete. Rise.");
|
|
523
|
-
box = {
|
|
524
|
-
topLeft: "\u250C",
|
|
525
|
-
topRight: "\u2510",
|
|
526
|
-
bottomLeft: "\u2514",
|
|
527
|
-
bottomRight: "\u2518",
|
|
528
|
-
horizontal: "\u2500",
|
|
529
|
-
vertical: "\u2502",
|
|
530
|
-
leftT: "\u251C",
|
|
531
|
-
rightT: "\u2524"
|
|
532
|
-
};
|
|
703
|
+
async function printAnimatedWelcomeBox(user) {
|
|
704
|
+
const lines = [];
|
|
705
|
+
const welcomeLine = `\u{1F44B} ${colors.white.bold(`Welcome back, ${user.username}!`)}`;
|
|
706
|
+
lines.push(welcomeLine);
|
|
707
|
+
const levelInfo = user.level && user.levelName && user.levelIcon ? `${user.levelIcon} Level ${user.level} \u2022 ${user.levelName}` : "";
|
|
708
|
+
const ccplanBadge = user.ccplan ? getCCplanBadge(user.ccplan) : "";
|
|
709
|
+
if (levelInfo || ccplanBadge) {
|
|
710
|
+
lines.push(`${levelInfo}${ccplanBadge ? ` ${ccplanBadge}` : ""}`);
|
|
533
711
|
}
|
|
534
|
-
|
|
712
|
+
if (user.globalRank) {
|
|
713
|
+
lines.push(`\u{1F30D} Global Rank: ${colors.primary(`#${user.globalRank}`)}`);
|
|
714
|
+
}
|
|
715
|
+
if (user.countryRank && user.countryCode) {
|
|
716
|
+
const flag = countryCodeToFlag(user.countryCode);
|
|
717
|
+
lines.push(`${flag} Country Rank: ${colors.primary(`#${user.countryRank}`)}`);
|
|
718
|
+
}
|
|
719
|
+
const maxVisibleLength = Math.max(...lines.map((l) => stripAnsi(l).length));
|
|
720
|
+
const boxWidth = Math.max(maxVisibleLength + 4, 47);
|
|
721
|
+
const top = colors.dim(` ${box.topLeft}${box.horizontal.repeat(boxWidth)}${box.topRight}`);
|
|
722
|
+
const bottom = colors.dim(` ${box.bottomLeft}${box.horizontal.repeat(boxWidth)}${box.bottomRight}`);
|
|
723
|
+
console.log(top);
|
|
724
|
+
await sleep(50);
|
|
725
|
+
for (const line of lines) {
|
|
726
|
+
const visibleLength = stripAnsi(line).length;
|
|
727
|
+
const padding = boxWidth - 2 - visibleLength;
|
|
728
|
+
const paddedLine = `${box.vertical} ${line}${" ".repeat(Math.max(0, padding))} ${box.vertical}`;
|
|
729
|
+
console.log(colors.dim(" ") + paddedLine);
|
|
730
|
+
await sleep(60);
|
|
731
|
+
}
|
|
732
|
+
console.log(bottom);
|
|
733
|
+
}
|
|
535
734
|
|
|
536
735
|
// src/commands/submit.ts
|
|
537
736
|
function ccgatherToUsageData(data) {
|
|
@@ -544,54 +743,16 @@ function ccgatherToUsageData(data) {
|
|
|
544
743
|
cacheWriteTokens: data.usage.cacheWriteTokens,
|
|
545
744
|
daysTracked: data.stats.daysTracked,
|
|
546
745
|
ccplan: data.account?.ccplan || null,
|
|
547
|
-
rateLimitTier: data.account?.rateLimitTier || null
|
|
746
|
+
rateLimitTier: data.account?.rateLimitTier || null,
|
|
747
|
+
dailyUsage: data.dailyUsage || []
|
|
548
748
|
};
|
|
549
749
|
}
|
|
550
|
-
function findCcJson() {
|
|
551
|
-
const possiblePaths = [
|
|
552
|
-
path3.join(process.cwd(), "cc.json"),
|
|
553
|
-
path3.join(os3.homedir(), "cc.json"),
|
|
554
|
-
path3.join(os3.homedir(), ".claude", "cc.json")
|
|
555
|
-
];
|
|
556
|
-
for (const p of possiblePaths) {
|
|
557
|
-
if (fs3.existsSync(p)) {
|
|
558
|
-
return p;
|
|
559
|
-
}
|
|
560
|
-
}
|
|
561
|
-
return null;
|
|
562
|
-
}
|
|
563
|
-
function parseCcJson(filePath) {
|
|
564
|
-
try {
|
|
565
|
-
const content = fs3.readFileSync(filePath, "utf-8");
|
|
566
|
-
const data = JSON.parse(content);
|
|
567
|
-
return {
|
|
568
|
-
totalTokens: data.totalTokens || data.total_tokens || 0,
|
|
569
|
-
totalCost: data.totalCost || data.total_cost || data.costUSD || 0,
|
|
570
|
-
inputTokens: data.inputTokens || data.input_tokens || 0,
|
|
571
|
-
outputTokens: data.outputTokens || data.output_tokens || 0,
|
|
572
|
-
cacheReadTokens: data.cacheReadTokens || data.cache_read_tokens || 0,
|
|
573
|
-
cacheWriteTokens: data.cacheWriteTokens || data.cache_write_tokens || 0,
|
|
574
|
-
daysTracked: data.daysTracked || data.days_tracked || calculateDaysTracked(data)
|
|
575
|
-
};
|
|
576
|
-
} catch {
|
|
577
|
-
return null;
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
function calculateDaysTracked(data) {
|
|
581
|
-
if (data.dailyStats && Array.isArray(data.dailyStats)) {
|
|
582
|
-
return data.dailyStats.length;
|
|
583
|
-
}
|
|
584
|
-
if (data.daily && typeof data.daily === "object") {
|
|
585
|
-
return Object.keys(data.daily).length;
|
|
586
|
-
}
|
|
587
|
-
return 1;
|
|
588
|
-
}
|
|
589
750
|
async function submitToServer(data) {
|
|
590
751
|
const apiUrl = getApiUrl();
|
|
591
752
|
const config = getConfig();
|
|
592
753
|
const apiToken = config.get("apiToken");
|
|
593
754
|
if (!apiToken) {
|
|
594
|
-
return { success: false, error: "Not authenticated.
|
|
755
|
+
return { success: false, error: "Not authenticated." };
|
|
595
756
|
}
|
|
596
757
|
try {
|
|
597
758
|
const response = await fetch(`${apiUrl}/cli/submit`, {
|
|
@@ -610,7 +771,8 @@ async function submitToServer(data) {
|
|
|
610
771
|
daysTracked: data.daysTracked,
|
|
611
772
|
ccplan: data.ccplan,
|
|
612
773
|
rateLimitTier: data.rateLimitTier,
|
|
613
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
774
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
775
|
+
dailyUsage: data.dailyUsage
|
|
614
776
|
})
|
|
615
777
|
});
|
|
616
778
|
if (!response.ok) {
|
|
@@ -618,19 +780,16 @@ async function submitToServer(data) {
|
|
|
618
780
|
return { success: false, error: errorData.error || `HTTP ${response.status}` };
|
|
619
781
|
}
|
|
620
782
|
const result = await response.json();
|
|
621
|
-
return { success: true, profileUrl: result.profileUrl
|
|
783
|
+
return { success: true, profileUrl: result.profileUrl };
|
|
622
784
|
} catch (err) {
|
|
623
785
|
return { success: false, error: err instanceof Error ? err.message : "Unknown error" };
|
|
624
786
|
}
|
|
625
787
|
}
|
|
626
788
|
async function submit(options) {
|
|
627
|
-
printCompactHeader("1.3.4");
|
|
628
789
|
console.log(header("Submit Usage Data", "\u{1F4E4}"));
|
|
629
790
|
if (!isAuthenticated()) {
|
|
630
791
|
console.log(`
|
|
631
792
|
${error("Not authenticated.")}`);
|
|
632
|
-
console.log(` ${colors.muted("Please run:")} ${colors.white("npx ccgather auth")}
|
|
633
|
-
`);
|
|
634
793
|
process.exit(1);
|
|
635
794
|
}
|
|
636
795
|
const config = getConfig();
|
|
@@ -640,65 +799,36 @@ async function submit(options) {
|
|
|
640
799
|
${colors.muted("Logged in as:")} ${colors.white(username)}`);
|
|
641
800
|
}
|
|
642
801
|
let usageData = null;
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
if (ccgatherData) {
|
|
646
|
-
usageData = ccgatherToUsageData(ccgatherData);
|
|
647
|
-
dataSource = "ccgather.json";
|
|
802
|
+
const totalFiles = getSessionFileCount();
|
|
803
|
+
if (totalFiles > 0) {
|
|
648
804
|
console.log(`
|
|
649
|
-
${
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
console.log(` ${colors.dim("CCplan:")} ${colors.primary(usageData.ccplan.toUpperCase())}`);
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
if (!usageData) {
|
|
658
|
-
const ccJsonPath = findCcJson();
|
|
659
|
-
if (ccJsonPath) {
|
|
660
|
-
const inquirer4 = await import("inquirer");
|
|
661
|
-
const { useCcJson } = await inquirer4.default.prompt([
|
|
662
|
-
{
|
|
663
|
-
type: "confirm",
|
|
664
|
-
name: "useCcJson",
|
|
665
|
-
message: "Found existing cc.json. Use this file?",
|
|
666
|
-
default: true
|
|
667
|
-
}
|
|
668
|
-
]);
|
|
669
|
-
if (useCcJson) {
|
|
670
|
-
usageData = parseCcJson(ccJsonPath);
|
|
671
|
-
dataSource = "cc.json";
|
|
805
|
+
${colors.muted("Scanning")} ${colors.white(totalFiles.toString())} ${colors.muted("sessions...")}
|
|
806
|
+
`);
|
|
807
|
+
const scannedData = scanAndSave({
|
|
808
|
+
onProgress: (current, total) => {
|
|
809
|
+
progressBar(current, total, "Scanning");
|
|
672
810
|
}
|
|
811
|
+
});
|
|
812
|
+
await sleep(200);
|
|
813
|
+
if (scannedData) {
|
|
814
|
+
usageData = ccgatherToUsageData(scannedData);
|
|
815
|
+
console.log(` ${success("Scan complete!")}`);
|
|
673
816
|
}
|
|
674
|
-
}
|
|
675
|
-
if (!usageData) {
|
|
676
|
-
const parseSpinner = (0, import_ora.default)({
|
|
677
|
-
text: "Scanning Claude Code usage data...",
|
|
678
|
-
color: "cyan"
|
|
679
|
-
}).start();
|
|
817
|
+
} else {
|
|
680
818
|
const scannedData = scanAndSave();
|
|
681
|
-
parseSpinner.stop();
|
|
682
819
|
if (scannedData) {
|
|
683
820
|
usageData = ccgatherToUsageData(scannedData);
|
|
684
|
-
dataSource = "Claude Code logs";
|
|
685
821
|
console.log(`
|
|
686
|
-
${success("
|
|
687
|
-
console.log(` ${colors.dim(`Path: ${getCCGatherJsonPath()}`)}`);
|
|
822
|
+
${success("Scan complete!")}`);
|
|
688
823
|
}
|
|
689
824
|
}
|
|
690
825
|
if (!usageData) {
|
|
691
826
|
console.log(`
|
|
692
827
|
${error("No usage data found.")}`);
|
|
693
|
-
console.log(` ${colors.muted("Make sure you have used Claude Code.")}
|
|
694
|
-
console.log(` ${colors.muted("Run:")} ${colors.white("npx ccgather scan")}
|
|
828
|
+
console.log(` ${colors.muted("Make sure you have used Claude Code at least once.")}
|
|
695
829
|
`);
|
|
696
830
|
process.exit(1);
|
|
697
831
|
}
|
|
698
|
-
if (dataSource && dataSource !== "Claude Code logs") {
|
|
699
|
-
console.log(`
|
|
700
|
-
${success(`Using ${dataSource}`)}`);
|
|
701
|
-
}
|
|
702
832
|
console.log();
|
|
703
833
|
const summaryLines = [
|
|
704
834
|
`${colors.muted("Total Cost")} ${colors.success(formatCost(usageData.totalCost))}`,
|
|
@@ -712,22 +842,19 @@ async function submit(options) {
|
|
|
712
842
|
}
|
|
713
843
|
console.log(createBox(summaryLines));
|
|
714
844
|
console.log();
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
if (!confirmSubmit) {
|
|
726
|
-
console.log(`
|
|
845
|
+
const { confirmSubmit } = await import_inquirer.default.prompt([
|
|
846
|
+
{
|
|
847
|
+
type: "confirm",
|
|
848
|
+
name: "confirmSubmit",
|
|
849
|
+
message: "Submit to CCgather leaderboard?",
|
|
850
|
+
default: true
|
|
851
|
+
}
|
|
852
|
+
]);
|
|
853
|
+
if (!confirmSubmit) {
|
|
854
|
+
console.log(`
|
|
727
855
|
${colors.muted("Submission cancelled.")}
|
|
728
856
|
`);
|
|
729
|
-
|
|
730
|
-
}
|
|
857
|
+
return;
|
|
731
858
|
}
|
|
732
859
|
const submitSpinner = (0, import_ora.default)({
|
|
733
860
|
text: "Submitting to CCgather...",
|
|
@@ -735,47 +862,36 @@ async function submit(options) {
|
|
|
735
862
|
}).start();
|
|
736
863
|
const result = await submitToServer(usageData);
|
|
737
864
|
if (result.success) {
|
|
738
|
-
submitSpinner.succeed(colors.success("Successfully submitted
|
|
865
|
+
submitSpinner.succeed(colors.success("Successfully submitted!"));
|
|
739
866
|
console.log();
|
|
867
|
+
const leaderboardUrl = `https://ccgather.com/leaderboard?u=${username}`;
|
|
740
868
|
const successLines = [
|
|
741
869
|
`${colors.success("\u2713")} ${colors.white.bold("Submission Complete!")}`,
|
|
742
870
|
"",
|
|
743
|
-
`${colors.muted("
|
|
871
|
+
`${colors.muted("Check your rank on the leaderboard:")}`,
|
|
872
|
+
`${link(leaderboardUrl)}`
|
|
744
873
|
];
|
|
745
|
-
if (result.rank) {
|
|
746
|
-
successLines.push(`${colors.muted("Rank:")} ${colors.warning(`#${result.rank}`)}`);
|
|
747
|
-
}
|
|
748
874
|
console.log(createBox(successLines));
|
|
749
875
|
console.log();
|
|
750
|
-
console.log(` ${colors.dim("View leaderboard:")} ${link("https://ccgather.com/leaderboard")}`);
|
|
751
|
-
console.log();
|
|
752
876
|
} else {
|
|
753
877
|
submitSpinner.fail(colors.error("Failed to submit"));
|
|
754
878
|
console.log(`
|
|
755
879
|
${error(result.error || "Unknown error")}`);
|
|
756
880
|
if (result.error?.includes("auth") || result.error?.includes("token")) {
|
|
757
|
-
console.log(
|
|
758
|
-
${colors.muted("Try
|
|
881
|
+
console.log();
|
|
882
|
+
console.log(` ${colors.muted("Try re-authenticating from Settings menu.")}`);
|
|
759
883
|
}
|
|
760
884
|
console.log();
|
|
761
885
|
process.exit(1);
|
|
762
886
|
}
|
|
763
887
|
}
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
import_ora = __toESM(require("ora"));
|
|
769
|
-
fs3 = __toESM(require("fs"));
|
|
770
|
-
path3 = __toESM(require("path"));
|
|
771
|
-
os3 = __toESM(require("os"));
|
|
772
|
-
init_config();
|
|
773
|
-
init_ccgather_json();
|
|
774
|
-
init_ui();
|
|
775
|
-
}
|
|
776
|
-
});
|
|
888
|
+
|
|
889
|
+
// src/commands/status.ts
|
|
890
|
+
var import_ora2 = __toESM(require("ora"));
|
|
891
|
+
init_config();
|
|
777
892
|
|
|
778
893
|
// src/lib/api.ts
|
|
894
|
+
init_config();
|
|
779
895
|
async function fetchApi(endpoint, options = {}) {
|
|
780
896
|
const config = getConfig();
|
|
781
897
|
const apiToken = config.get("apiToken");
|
|
@@ -802,523 +918,47 @@ async function fetchApi(endpoint, options = {}) {
|
|
|
802
918
|
return { success: false, error: message };
|
|
803
919
|
}
|
|
804
920
|
}
|
|
805
|
-
async function syncUsage(payload) {
|
|
806
|
-
return fetchApi("/cli/sync", {
|
|
807
|
-
method: "POST",
|
|
808
|
-
body: JSON.stringify(payload)
|
|
809
|
-
});
|
|
810
|
-
}
|
|
811
921
|
async function getStatus() {
|
|
812
922
|
return fetchApi("/cli/status");
|
|
813
923
|
}
|
|
814
|
-
var init_api = __esm({
|
|
815
|
-
"src/lib/api.ts"() {
|
|
816
|
-
"use strict";
|
|
817
|
-
init_config();
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
// src/commands/reset.ts
|
|
822
|
-
var reset_exports = {};
|
|
823
|
-
__export(reset_exports, {
|
|
824
|
-
reset: () => reset
|
|
825
|
-
});
|
|
826
|
-
function getClaudeSettingsDir() {
|
|
827
|
-
return path4.join(os4.homedir(), ".claude");
|
|
828
|
-
}
|
|
829
|
-
function removeStopHook() {
|
|
830
|
-
const claudeDir = getClaudeSettingsDir();
|
|
831
|
-
const settingsPath = path4.join(claudeDir, "settings.json");
|
|
832
|
-
if (!fs4.existsSync(settingsPath)) {
|
|
833
|
-
return { success: true, message: "No settings file found" };
|
|
834
|
-
}
|
|
835
|
-
try {
|
|
836
|
-
const content = fs4.readFileSync(settingsPath, "utf-8");
|
|
837
|
-
const settings = JSON.parse(content);
|
|
838
|
-
if (settings.hooks && typeof settings.hooks === "object") {
|
|
839
|
-
const hooks = settings.hooks;
|
|
840
|
-
if (hooks.Stop && Array.isArray(hooks.Stop)) {
|
|
841
|
-
hooks.Stop = hooks.Stop.filter((hook) => {
|
|
842
|
-
if (typeof hook === "object" && hook !== null) {
|
|
843
|
-
const h = hook;
|
|
844
|
-
return typeof h.command !== "string" || !h.command.includes("ccgather");
|
|
845
|
-
}
|
|
846
|
-
return true;
|
|
847
|
-
});
|
|
848
|
-
if (hooks.Stop.length === 0) {
|
|
849
|
-
delete hooks.Stop;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
fs4.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
854
|
-
return { success: true, message: "Hook removed" };
|
|
855
|
-
} catch (err) {
|
|
856
|
-
return {
|
|
857
|
-
success: false,
|
|
858
|
-
message: err instanceof Error ? err.message : "Unknown error"
|
|
859
|
-
};
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
function removeSyncScript() {
|
|
863
|
-
const claudeDir = getClaudeSettingsDir();
|
|
864
|
-
const scriptPath = path4.join(claudeDir, "ccgather-sync.js");
|
|
865
|
-
if (fs4.existsSync(scriptPath)) {
|
|
866
|
-
fs4.unlinkSync(scriptPath);
|
|
867
|
-
}
|
|
868
|
-
}
|
|
869
|
-
async function reset() {
|
|
870
|
-
const config = getConfig();
|
|
871
|
-
console.log(import_chalk2.default.bold("\n\u{1F504} CCgather Reset\n"));
|
|
872
|
-
if (!config.get("apiToken")) {
|
|
873
|
-
console.log(import_chalk2.default.yellow("CCgather is not configured."));
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
const { confirmReset } = await import_inquirer.default.prompt([
|
|
877
|
-
{
|
|
878
|
-
type: "confirm",
|
|
879
|
-
name: "confirmReset",
|
|
880
|
-
message: "This will remove the CCgather hook and local configuration. Continue?",
|
|
881
|
-
default: false
|
|
882
|
-
}
|
|
883
|
-
]);
|
|
884
|
-
if (!confirmReset) {
|
|
885
|
-
console.log(import_chalk2.default.gray("Reset cancelled."));
|
|
886
|
-
return;
|
|
887
|
-
}
|
|
888
|
-
const hookSpinner = (0, import_ora3.default)("Removing Claude Code hook...").start();
|
|
889
|
-
const hookResult = removeStopHook();
|
|
890
|
-
if (hookResult.success) {
|
|
891
|
-
hookSpinner.succeed(import_chalk2.default.green("Hook removed"));
|
|
892
|
-
} else {
|
|
893
|
-
hookSpinner.warn(import_chalk2.default.yellow(`Could not remove hook: ${hookResult.message}`));
|
|
894
|
-
}
|
|
895
|
-
const scriptSpinner = (0, import_ora3.default)("Removing sync script...").start();
|
|
896
|
-
try {
|
|
897
|
-
removeSyncScript();
|
|
898
|
-
scriptSpinner.succeed(import_chalk2.default.green("Sync script removed"));
|
|
899
|
-
} catch {
|
|
900
|
-
scriptSpinner.warn(import_chalk2.default.yellow("Could not remove sync script"));
|
|
901
|
-
}
|
|
902
|
-
const { deleteAccount } = await import_inquirer.default.prompt([
|
|
903
|
-
{
|
|
904
|
-
type: "confirm",
|
|
905
|
-
name: "deleteAccount",
|
|
906
|
-
message: import_chalk2.default.red("Do you also want to delete your account from the leaderboard? (This cannot be undone)"),
|
|
907
|
-
default: false
|
|
908
|
-
}
|
|
909
|
-
]);
|
|
910
|
-
if (deleteAccount) {
|
|
911
|
-
console.log(import_chalk2.default.yellow("\nAccount deletion is not yet implemented."));
|
|
912
|
-
console.log(import_chalk2.default.gray("Please contact support to delete your account."));
|
|
913
|
-
}
|
|
914
|
-
const configSpinner = (0, import_ora3.default)("Resetting local configuration...").start();
|
|
915
|
-
resetConfig();
|
|
916
|
-
configSpinner.succeed(import_chalk2.default.green("Local configuration reset"));
|
|
917
|
-
console.log();
|
|
918
|
-
console.log(import_chalk2.default.green.bold("\u2705 Reset complete!"));
|
|
919
|
-
console.log();
|
|
920
|
-
console.log(import_chalk2.default.gray("Your usage will no longer be tracked."));
|
|
921
|
-
console.log(import_chalk2.default.gray("Run `npx ccgather` to set up again."));
|
|
922
|
-
console.log();
|
|
923
|
-
}
|
|
924
|
-
var import_chalk2, import_ora3, fs4, path4, os4, import_inquirer;
|
|
925
|
-
var init_reset = __esm({
|
|
926
|
-
"src/commands/reset.ts"() {
|
|
927
|
-
"use strict";
|
|
928
|
-
import_chalk2 = __toESM(require("chalk"));
|
|
929
|
-
import_ora3 = __toESM(require("ora"));
|
|
930
|
-
fs4 = __toESM(require("fs"));
|
|
931
|
-
path4 = __toESM(require("path"));
|
|
932
|
-
os4 = __toESM(require("os"));
|
|
933
|
-
import_inquirer = __toESM(require("inquirer"));
|
|
934
|
-
init_config();
|
|
935
|
-
}
|
|
936
|
-
});
|
|
937
|
-
|
|
938
|
-
// src/lib/claude.ts
|
|
939
|
-
function getClaudeConfigDir() {
|
|
940
|
-
const platform3 = os6.platform();
|
|
941
|
-
if (platform3 === "win32") {
|
|
942
|
-
return path6.join(os6.homedir(), "AppData", "Roaming", "claude-code");
|
|
943
|
-
} else if (platform3 === "darwin") {
|
|
944
|
-
return path6.join(os6.homedir(), "Library", "Application Support", "claude-code");
|
|
945
|
-
} else {
|
|
946
|
-
return path6.join(os6.homedir(), ".config", "claude-code");
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
function getUsageFilePaths() {
|
|
950
|
-
const configDir = getClaudeConfigDir();
|
|
951
|
-
return [
|
|
952
|
-
path6.join(configDir, "usage.json"),
|
|
953
|
-
path6.join(configDir, "stats.json"),
|
|
954
|
-
path6.join(configDir, "data", "usage.json"),
|
|
955
|
-
path6.join(os6.homedir(), ".claude", "usage.json"),
|
|
956
|
-
path6.join(os6.homedir(), ".claude-code", "usage.json")
|
|
957
|
-
];
|
|
958
|
-
}
|
|
959
|
-
function readClaudeUsage() {
|
|
960
|
-
const possiblePaths = getUsageFilePaths();
|
|
961
|
-
for (const filePath of possiblePaths) {
|
|
962
|
-
try {
|
|
963
|
-
if (fs6.existsSync(filePath)) {
|
|
964
|
-
const content = fs6.readFileSync(filePath, "utf-8");
|
|
965
|
-
const data = JSON.parse(content);
|
|
966
|
-
if (data.usage) {
|
|
967
|
-
return {
|
|
968
|
-
totalTokens: data.usage.total_tokens || 0,
|
|
969
|
-
totalSpent: data.usage.total_cost || 0,
|
|
970
|
-
modelBreakdown: data.usage.by_model || {},
|
|
971
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
972
|
-
};
|
|
973
|
-
}
|
|
974
|
-
if (data.sessions && data.sessions.length > 0) {
|
|
975
|
-
const breakdown = {};
|
|
976
|
-
let totalTokens = 0;
|
|
977
|
-
let totalSpent = 0;
|
|
978
|
-
for (const session of data.sessions) {
|
|
979
|
-
totalTokens += session.tokens_used || 0;
|
|
980
|
-
totalSpent += session.cost || 0;
|
|
981
|
-
breakdown[session.model] = (breakdown[session.model] || 0) + session.tokens_used;
|
|
982
|
-
}
|
|
983
|
-
return {
|
|
984
|
-
totalTokens,
|
|
985
|
-
totalSpent,
|
|
986
|
-
modelBreakdown: breakdown,
|
|
987
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
} catch (error2) {
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
return null;
|
|
995
|
-
}
|
|
996
|
-
function isClaudeCodeInstalled() {
|
|
997
|
-
const configDir = getClaudeConfigDir();
|
|
998
|
-
return fs6.existsSync(configDir);
|
|
999
|
-
}
|
|
1000
|
-
function getMockUsageData() {
|
|
1001
|
-
return {
|
|
1002
|
-
totalTokens: Math.floor(Math.random() * 1e6) + 1e4,
|
|
1003
|
-
totalSpent: Math.random() * 50 + 5,
|
|
1004
|
-
modelBreakdown: {
|
|
1005
|
-
"claude-3-5-sonnet-20241022": Math.floor(Math.random() * 5e5),
|
|
1006
|
-
"claude-3-opus-20240229": Math.floor(Math.random() * 1e5),
|
|
1007
|
-
"claude-3-haiku-20240307": Math.floor(Math.random() * 2e5)
|
|
1008
|
-
},
|
|
1009
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1010
|
-
};
|
|
1011
|
-
}
|
|
1012
|
-
var fs6, path6, os6;
|
|
1013
|
-
var init_claude = __esm({
|
|
1014
|
-
"src/lib/claude.ts"() {
|
|
1015
|
-
"use strict";
|
|
1016
|
-
fs6 = __toESM(require("fs"));
|
|
1017
|
-
path6 = __toESM(require("path"));
|
|
1018
|
-
os6 = __toESM(require("os"));
|
|
1019
|
-
}
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
// src/commands/sync.ts
|
|
1023
|
-
var sync_exports = {};
|
|
1024
|
-
__export(sync_exports, {
|
|
1025
|
-
sync: () => sync
|
|
1026
|
-
});
|
|
1027
|
-
function formatNumber2(num) {
|
|
1028
|
-
if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
|
|
1029
|
-
if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`;
|
|
1030
|
-
return num.toString();
|
|
1031
|
-
}
|
|
1032
|
-
async function sync(options) {
|
|
1033
|
-
const config = getConfig();
|
|
1034
|
-
console.log(import_chalk4.default.bold("\n\u{1F504} CCgather Sync\n"));
|
|
1035
|
-
if (!isAuthenticated()) {
|
|
1036
|
-
console.log(import_chalk4.default.red("Not authenticated."));
|
|
1037
|
-
console.log(import_chalk4.default.gray("Run: npx ccgather auth\n"));
|
|
1038
|
-
process.exit(1);
|
|
1039
|
-
}
|
|
1040
|
-
if (!isClaudeCodeInstalled()) {
|
|
1041
|
-
console.log(import_chalk4.default.yellow("\u26A0\uFE0F Claude Code installation not detected."));
|
|
1042
|
-
console.log(
|
|
1043
|
-
import_chalk4.default.gray("Make sure Claude Code is installed and has been used at least once.\n")
|
|
1044
|
-
);
|
|
1045
|
-
if (process.env.CCGATHER_DEMO === "true") {
|
|
1046
|
-
console.log(import_chalk4.default.gray("Demo mode: Using mock data..."));
|
|
1047
|
-
} else {
|
|
1048
|
-
process.exit(1);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
const lastSync = config.get("lastSync");
|
|
1052
|
-
if (lastSync && !options.force) {
|
|
1053
|
-
const lastSyncDate = new Date(lastSync);
|
|
1054
|
-
const minInterval = 5 * 60 * 1e3;
|
|
1055
|
-
const timeSinceSync = Date.now() - lastSyncDate.getTime();
|
|
1056
|
-
if (timeSinceSync < minInterval) {
|
|
1057
|
-
const remaining = Math.ceil((minInterval - timeSinceSync) / 1e3 / 60);
|
|
1058
|
-
console.log(import_chalk4.default.yellow(`\u23F3 Please wait ${remaining} minutes before syncing again.`));
|
|
1059
|
-
console.log(import_chalk4.default.gray("Use --force to override.\n"));
|
|
1060
|
-
process.exit(0);
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
const spinner = (0, import_ora6.default)("Reading Claude Code usage data...").start();
|
|
1064
|
-
let usageData = readClaudeUsage();
|
|
1065
|
-
if (!usageData && process.env.CCGATHER_DEMO === "true") {
|
|
1066
|
-
usageData = getMockUsageData();
|
|
1067
|
-
}
|
|
1068
|
-
if (!usageData) {
|
|
1069
|
-
spinner.fail(import_chalk4.default.red("Failed to read usage data"));
|
|
1070
|
-
console.log(import_chalk4.default.gray("\nPossible reasons:"));
|
|
1071
|
-
console.log(import_chalk4.default.gray(" - Claude Code has not been used yet"));
|
|
1072
|
-
console.log(import_chalk4.default.gray(" - Usage data file is missing or corrupted"));
|
|
1073
|
-
console.log(import_chalk4.default.gray(" - Insufficient permissions to read the file\n"));
|
|
1074
|
-
process.exit(1);
|
|
1075
|
-
}
|
|
1076
|
-
spinner.text = "Syncing to CCgather...";
|
|
1077
|
-
const result = await syncUsage({
|
|
1078
|
-
totalTokens: usageData.totalTokens,
|
|
1079
|
-
totalSpent: usageData.totalSpent,
|
|
1080
|
-
modelBreakdown: usageData.modelBreakdown,
|
|
1081
|
-
timestamp: usageData.lastUpdated
|
|
1082
|
-
});
|
|
1083
|
-
if (!result.success) {
|
|
1084
|
-
spinner.fail(import_chalk4.default.red("Sync failed"));
|
|
1085
|
-
console.log(import_chalk4.default.red(`Error: ${result.error}
|
|
1086
|
-
`));
|
|
1087
|
-
process.exit(1);
|
|
1088
|
-
}
|
|
1089
|
-
config.set("lastSync", (/* @__PURE__ */ new Date()).toISOString());
|
|
1090
|
-
spinner.succeed(import_chalk4.default.green("Sync complete!"));
|
|
1091
|
-
console.log("\n" + import_chalk4.default.gray("\u2500".repeat(40)));
|
|
1092
|
-
console.log(import_chalk4.default.bold("\u{1F4CA} Your Stats"));
|
|
1093
|
-
console.log(import_chalk4.default.gray("\u2500".repeat(40)));
|
|
1094
|
-
console.log(` ${import_chalk4.default.gray("Tokens:")} ${import_chalk4.default.white(formatNumber2(usageData.totalTokens))}`);
|
|
1095
|
-
console.log(
|
|
1096
|
-
` ${import_chalk4.default.gray("Spent:")} ${import_chalk4.default.green("$" + usageData.totalSpent.toFixed(2))}`
|
|
1097
|
-
);
|
|
1098
|
-
console.log(` ${import_chalk4.default.gray("Rank:")} ${import_chalk4.default.yellow("#" + result.data?.rank)}`);
|
|
1099
|
-
if (options.verbose) {
|
|
1100
|
-
console.log("\n" + import_chalk4.default.gray("Model Breakdown:"));
|
|
1101
|
-
for (const [model, tokens] of Object.entries(usageData.modelBreakdown)) {
|
|
1102
|
-
const shortModel = model.replace("claude-", "").replace(/-\d+$/, "");
|
|
1103
|
-
console.log(` ${import_chalk4.default.gray(shortModel + ":")} ${formatNumber2(tokens)}`);
|
|
1104
|
-
}
|
|
1105
|
-
}
|
|
1106
|
-
console.log(import_chalk4.default.gray("\u2500".repeat(40)));
|
|
1107
|
-
console.log(import_chalk4.default.gray("\nView full leaderboard: https://ccgather.com/leaderboard\n"));
|
|
1108
|
-
}
|
|
1109
|
-
var import_chalk4, import_ora6;
|
|
1110
|
-
var init_sync = __esm({
|
|
1111
|
-
"src/commands/sync.ts"() {
|
|
1112
|
-
"use strict";
|
|
1113
|
-
import_chalk4 = __toESM(require("chalk"));
|
|
1114
|
-
import_ora6 = __toESM(require("ora"));
|
|
1115
|
-
init_config();
|
|
1116
|
-
init_api();
|
|
1117
|
-
init_claude();
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
1120
|
-
|
|
1121
|
-
// src/commands/auth.ts
|
|
1122
|
-
var auth_exports = {};
|
|
1123
|
-
__export(auth_exports, {
|
|
1124
|
-
auth: () => auth
|
|
1125
|
-
});
|
|
1126
|
-
async function auth(options) {
|
|
1127
|
-
const config = getConfig();
|
|
1128
|
-
console.log(import_chalk5.default.bold("\n\u{1F510} CCgather Authentication\n"));
|
|
1129
|
-
const existingToken = config.get("apiToken");
|
|
1130
|
-
if (existingToken) {
|
|
1131
|
-
const { overwrite } = await import_inquirer2.default.prompt([
|
|
1132
|
-
{
|
|
1133
|
-
type: "confirm",
|
|
1134
|
-
name: "overwrite",
|
|
1135
|
-
message: "You are already authenticated. Do you want to re-authenticate?",
|
|
1136
|
-
default: false
|
|
1137
|
-
}
|
|
1138
|
-
]);
|
|
1139
|
-
if (!overwrite) {
|
|
1140
|
-
console.log(import_chalk5.default.gray("Authentication cancelled."));
|
|
1141
|
-
return;
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
if (options.token) {
|
|
1145
|
-
await authenticateWithToken(options.token);
|
|
1146
|
-
return;
|
|
1147
|
-
}
|
|
1148
|
-
const apiUrl = getApiUrl();
|
|
1149
|
-
const spinner = (0, import_ora7.default)("Initializing authentication...").start();
|
|
1150
|
-
try {
|
|
1151
|
-
const response = await fetch(`${apiUrl}/cli/auth/device`, {
|
|
1152
|
-
method: "POST"
|
|
1153
|
-
});
|
|
1154
|
-
if (!response.ok) {
|
|
1155
|
-
spinner.fail(import_chalk5.default.red("Failed to initialize authentication"));
|
|
1156
|
-
console.log(import_chalk5.default.red("\nPlease check your internet connection and try again."));
|
|
1157
|
-
process.exit(1);
|
|
1158
|
-
}
|
|
1159
|
-
const deviceData = await response.json();
|
|
1160
|
-
spinner.stop();
|
|
1161
|
-
console.log(import_chalk5.default.gray(" Opening browser for authentication...\n"));
|
|
1162
|
-
console.log(import_chalk5.default.gray(" If browser doesn't open, visit:"));
|
|
1163
|
-
console.log(` \u{1F517} ${import_chalk5.default.cyan.underline(deviceData.verification_uri_complete)}`);
|
|
1164
|
-
console.log();
|
|
1165
|
-
try {
|
|
1166
|
-
await (0, import_open.default)(deviceData.verification_uri_complete);
|
|
1167
|
-
} catch {
|
|
1168
|
-
console.log(import_chalk5.default.yellow(" Could not open browser automatically."));
|
|
1169
|
-
console.log(import_chalk5.default.yellow(" Please open the URL above manually."));
|
|
1170
|
-
}
|
|
1171
|
-
const pollSpinner = (0, import_ora7.default)("Waiting for authorization...").start();
|
|
1172
|
-
const startTime = Date.now();
|
|
1173
|
-
const expiresAt = startTime + deviceData.expires_in * 1e3;
|
|
1174
|
-
const pollInterval = Math.max(deviceData.interval * 1e3, 5e3);
|
|
1175
|
-
while (Date.now() < expiresAt) {
|
|
1176
|
-
await sleep(pollInterval);
|
|
1177
|
-
try {
|
|
1178
|
-
const pollResponse = await fetch(
|
|
1179
|
-
`${apiUrl}/cli/auth/device/poll?device_code=${deviceData.device_code}`
|
|
1180
|
-
);
|
|
1181
|
-
const pollData = await pollResponse.json();
|
|
1182
|
-
if (pollData.status === "authorized" && pollData.token) {
|
|
1183
|
-
pollSpinner.succeed(import_chalk5.default.green("Authentication successful!"));
|
|
1184
|
-
config.set("apiToken", pollData.token);
|
|
1185
|
-
config.set("userId", pollData.userId);
|
|
1186
|
-
config.set("username", pollData.username);
|
|
1187
|
-
console.log(import_chalk5.default.gray(`
|
|
1188
|
-
Welcome, ${import_chalk5.default.white(pollData.username)}!
|
|
1189
|
-
`));
|
|
1190
|
-
console.log(import_chalk5.default.bold("\u{1F4CA} Submitting your usage data...\n"));
|
|
1191
|
-
await submit({ yes: true });
|
|
1192
|
-
return;
|
|
1193
|
-
}
|
|
1194
|
-
if (pollData.status === "expired" || pollData.status === "used") {
|
|
1195
|
-
pollSpinner.fail(import_chalk5.default.red("Authentication expired or already used"));
|
|
1196
|
-
console.log(import_chalk5.default.gray('\nPlease run "ccgather auth" to try again.\n'));
|
|
1197
|
-
process.exit(1);
|
|
1198
|
-
}
|
|
1199
|
-
const remaining = Math.ceil((expiresAt - Date.now()) / 1e3);
|
|
1200
|
-
pollSpinner.text = `Waiting for authorization... (${remaining}s remaining)`;
|
|
1201
|
-
} catch {
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
pollSpinner.fail(import_chalk5.default.red("Authentication timed out"));
|
|
1205
|
-
console.log(import_chalk5.default.gray('\nPlease run "ccgather auth" to try again.\n'));
|
|
1206
|
-
process.exit(1);
|
|
1207
|
-
} catch (error2) {
|
|
1208
|
-
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
1209
|
-
console.log(import_chalk5.default.red(`
|
|
1210
|
-
Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`));
|
|
1211
|
-
process.exit(1);
|
|
1212
|
-
}
|
|
1213
|
-
}
|
|
1214
|
-
async function authenticateWithToken(token) {
|
|
1215
|
-
const config = getConfig();
|
|
1216
|
-
const apiUrl = getApiUrl();
|
|
1217
|
-
const spinner = (0, import_ora7.default)("Verifying token...").start();
|
|
1218
|
-
try {
|
|
1219
|
-
const response = await fetch(`${apiUrl}/cli/verify`, {
|
|
1220
|
-
method: "POST",
|
|
1221
|
-
headers: {
|
|
1222
|
-
"Content-Type": "application/json",
|
|
1223
|
-
Authorization: `Bearer ${token}`
|
|
1224
|
-
}
|
|
1225
|
-
});
|
|
1226
|
-
if (!response.ok) {
|
|
1227
|
-
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
1228
|
-
const errorData = await response.json().catch(() => ({}));
|
|
1229
|
-
console.log(import_chalk5.default.red(`Error: ${errorData.error || "Invalid token"}`));
|
|
1230
|
-
console.log(import_chalk5.default.gray("\nMake sure your token is correct and try again."));
|
|
1231
|
-
process.exit(1);
|
|
1232
|
-
}
|
|
1233
|
-
const data = await response.json();
|
|
1234
|
-
config.set("apiToken", token);
|
|
1235
|
-
config.set("userId", data.userId);
|
|
1236
|
-
config.set("username", data.username);
|
|
1237
|
-
spinner.succeed(import_chalk5.default.green("Authentication successful!"));
|
|
1238
|
-
console.log(import_chalk5.default.gray(`
|
|
1239
|
-
Welcome, ${import_chalk5.default.white(data.username)}!
|
|
1240
|
-
`));
|
|
1241
|
-
console.log(import_chalk5.default.bold("\u{1F4CA} Submitting your usage data...\n"));
|
|
1242
|
-
await submit({ yes: true });
|
|
1243
|
-
} catch (error2) {
|
|
1244
|
-
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
1245
|
-
console.log(import_chalk5.default.red(`
|
|
1246
|
-
Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`));
|
|
1247
|
-
process.exit(1);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
function sleep(ms) {
|
|
1251
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1252
|
-
}
|
|
1253
|
-
var import_chalk5, import_ora7, import_inquirer2, import_open;
|
|
1254
|
-
var init_auth = __esm({
|
|
1255
|
-
"src/commands/auth.ts"() {
|
|
1256
|
-
"use strict";
|
|
1257
|
-
import_chalk5 = __toESM(require("chalk"));
|
|
1258
|
-
import_ora7 = __toESM(require("ora"));
|
|
1259
|
-
import_inquirer2 = __toESM(require("inquirer"));
|
|
1260
|
-
import_open = __toESM(require("open"));
|
|
1261
|
-
init_config();
|
|
1262
|
-
init_submit();
|
|
1263
|
-
}
|
|
1264
|
-
});
|
|
1265
|
-
|
|
1266
|
-
// src/index.ts
|
|
1267
|
-
var import_commander = require("commander");
|
|
1268
|
-
var import_inquirer3 = __toESM(require("inquirer"));
|
|
1269
|
-
var import_chalk6 = __toESM(require("chalk"));
|
|
1270
|
-
var import_update_notifier = __toESM(require("update-notifier"));
|
|
1271
|
-
init_submit();
|
|
1272
924
|
|
|
1273
925
|
// src/commands/status.ts
|
|
1274
|
-
var import_ora2 = __toESM(require("ora"));
|
|
1275
|
-
init_config();
|
|
1276
|
-
init_api();
|
|
1277
|
-
init_ui();
|
|
1278
926
|
async function status(options) {
|
|
1279
927
|
if (!isAuthenticated()) {
|
|
1280
|
-
|
|
1281
|
-
console.log(JSON.stringify({ error: "Not authenticated" }));
|
|
1282
|
-
} else {
|
|
1283
|
-
console.log(`
|
|
928
|
+
console.log(`
|
|
1284
929
|
${error("Not authenticated.")}`);
|
|
1285
|
-
console.log(` ${colors.muted("Run:")} ${colors.white("npx ccgather auth")}
|
|
1286
|
-
`);
|
|
1287
|
-
}
|
|
1288
930
|
process.exit(1);
|
|
1289
931
|
}
|
|
1290
|
-
const spinner =
|
|
932
|
+
const spinner = (0, import_ora2.default)({
|
|
1291
933
|
text: "Fetching your stats...",
|
|
1292
934
|
color: "cyan"
|
|
1293
935
|
}).start();
|
|
1294
936
|
const result = await getStatus();
|
|
1295
937
|
if (!result.success) {
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
console.log(JSON.stringify({ error: result.error }));
|
|
1299
|
-
} else {
|
|
1300
|
-
console.log(`
|
|
938
|
+
spinner.fail(colors.error("Failed to fetch status"));
|
|
939
|
+
console.log(`
|
|
1301
940
|
${error(result.error || "Unknown error")}
|
|
1302
941
|
`);
|
|
1303
|
-
}
|
|
1304
942
|
process.exit(1);
|
|
1305
943
|
}
|
|
1306
944
|
const stats = result.data;
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
}
|
|
1311
|
-
spinner?.succeed(colors.success("Status retrieved"));
|
|
1312
|
-
printCompactHeader("1.2.1");
|
|
945
|
+
const config = getConfig();
|
|
946
|
+
const username = config.get("username");
|
|
947
|
+
spinner.succeed(colors.success("Status retrieved"));
|
|
1313
948
|
console.log(header("Your CCgather Stats", "\u{1F4CA}"));
|
|
1314
949
|
const levelInfo = getLevelInfo(stats.totalTokens);
|
|
1315
950
|
const medal = getRankMedal(stats.rank);
|
|
1316
951
|
console.log();
|
|
1317
|
-
console.log(` ${medal} ${colors.white.bold(
|
|
1318
|
-
|
|
952
|
+
console.log(` \u{1F30D} ${colors.muted("Global")} ${medal} ${colors.white.bold(`#${stats.rank}`)}`);
|
|
953
|
+
if (stats.countryRank && stats.countryCode) {
|
|
954
|
+
const countryFlag = countryCodeToFlag(stats.countryCode);
|
|
955
|
+
const countryMedal = getRankMedal(stats.countryRank);
|
|
956
|
+
console.log(` ${countryFlag} ${colors.muted("Country")} ${countryMedal} ${colors.white.bold(`#${stats.countryRank}`)}`);
|
|
957
|
+
}
|
|
958
|
+
console.log(` ${colors.dim(`Top ${stats.percentile.toFixed(1)}% globally`)}`);
|
|
1319
959
|
console.log();
|
|
1320
960
|
console.log(
|
|
1321
|
-
` ${levelInfo.icon} ${levelInfo.color(`Level ${levelInfo.level}
|
|
961
|
+
` ${levelInfo.icon} ${levelInfo.color(`Level ${levelInfo.level} - ${levelInfo.name}`)}`
|
|
1322
962
|
);
|
|
1323
963
|
if (stats.tier) {
|
|
1324
964
|
const badge = getCCplanBadge(stats.tier);
|
|
@@ -1330,7 +970,7 @@ async function status(options) {
|
|
|
1330
970
|
const statsLines = [
|
|
1331
971
|
`${colors.muted("Total Tokens")} ${colors.primary(formatNumber(stats.totalTokens))}`,
|
|
1332
972
|
`${colors.muted("Total Spent")} ${colors.success(formatCost(stats.totalSpent))}`,
|
|
1333
|
-
`${colors.muted("
|
|
973
|
+
`${colors.muted("CCplan")} ${colors.cyan(stats.tier || "Unknown")}`
|
|
1334
974
|
];
|
|
1335
975
|
console.log(createBox(statsLines));
|
|
1336
976
|
if (stats.badges && stats.badges.length > 0) {
|
|
@@ -1338,531 +978,21 @@ async function status(options) {
|
|
|
1338
978
|
console.log(` ${colors.muted("Badges")}`);
|
|
1339
979
|
console.log(` ${stats.badges.join(" ")}`);
|
|
1340
980
|
}
|
|
981
|
+
const leaderboardUrl = `https://ccgather.com/leaderboard?u=${username}`;
|
|
1341
982
|
console.log();
|
|
1342
983
|
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1343
|
-
console.log(` ${colors.muted("View leaderboard:")} ${link(
|
|
1344
|
-
console.log();
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
// src/commands/setup-auto.ts
|
|
1348
|
-
var import_chalk3 = __toESM(require("chalk"));
|
|
1349
|
-
var import_ora4 = __toESM(require("ora"));
|
|
1350
|
-
var http = __toESM(require("http"));
|
|
1351
|
-
var fs5 = __toESM(require("fs"));
|
|
1352
|
-
var path5 = __toESM(require("path"));
|
|
1353
|
-
var os5 = __toESM(require("os"));
|
|
1354
|
-
init_config();
|
|
1355
|
-
init_config();
|
|
1356
|
-
var CALLBACK_PORT = 9876;
|
|
1357
|
-
function getClaudeSettingsDir2() {
|
|
1358
|
-
return path5.join(os5.homedir(), ".claude");
|
|
1359
|
-
}
|
|
1360
|
-
async function openBrowser(url) {
|
|
1361
|
-
const { default: open2 } = await import("open");
|
|
1362
|
-
await open2(url);
|
|
1363
|
-
}
|
|
1364
|
-
function createCallbackServer() {
|
|
1365
|
-
return new Promise((resolve, reject) => {
|
|
1366
|
-
const server = http.createServer((req, res) => {
|
|
1367
|
-
const url = new URL(req.url || "", `http://localhost:${CALLBACK_PORT}`);
|
|
1368
|
-
if (url.pathname === "/callback") {
|
|
1369
|
-
const token = url.searchParams.get("token");
|
|
1370
|
-
const userId = url.searchParams.get("userId");
|
|
1371
|
-
const username = url.searchParams.get("username");
|
|
1372
|
-
const error2 = url.searchParams.get("error");
|
|
1373
|
-
if (error2) {
|
|
1374
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1375
|
-
res.end(`
|
|
1376
|
-
<html>
|
|
1377
|
-
<head><title>CCgather - Error</title></head>
|
|
1378
|
-
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a1a; color: #fff;">
|
|
1379
|
-
<div style="text-align: center;">
|
|
1380
|
-
<h1 style="color: #ef4444;">\u274C Authentication Failed</h1>
|
|
1381
|
-
<p>${error2}</p>
|
|
1382
|
-
<p style="color: #888;">You can close this window.</p>
|
|
1383
|
-
</div>
|
|
1384
|
-
</body>
|
|
1385
|
-
</html>
|
|
1386
|
-
`);
|
|
1387
|
-
server.close();
|
|
1388
|
-
reject(new Error(error2));
|
|
1389
|
-
return;
|
|
1390
|
-
}
|
|
1391
|
-
if (token && userId && username) {
|
|
1392
|
-
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1393
|
-
res.end(`
|
|
1394
|
-
<html>
|
|
1395
|
-
<head><title>CCgather - Success</title></head>
|
|
1396
|
-
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a1a; color: #fff;">
|
|
1397
|
-
<div style="text-align: center;">
|
|
1398
|
-
<h1 style="color: #22c55e;">\u2705 Authentication Successful!</h1>
|
|
1399
|
-
<p>Welcome, <strong>${username}</strong>!</p>
|
|
1400
|
-
<p style="color: #888;">You can close this window and return to your terminal.</p>
|
|
1401
|
-
</div>
|
|
1402
|
-
</body>
|
|
1403
|
-
</html>
|
|
1404
|
-
`);
|
|
1405
|
-
server.close();
|
|
1406
|
-
resolve({ token, userId, username });
|
|
1407
|
-
} else {
|
|
1408
|
-
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
1409
|
-
res.end("Missing required parameters");
|
|
1410
|
-
}
|
|
1411
|
-
} else {
|
|
1412
|
-
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
1413
|
-
res.end("Not found");
|
|
1414
|
-
}
|
|
1415
|
-
});
|
|
1416
|
-
server.listen(CALLBACK_PORT, () => {
|
|
1417
|
-
});
|
|
1418
|
-
server.on("error", (err) => {
|
|
1419
|
-
reject(new Error(`Failed to start callback server: ${err.message}`));
|
|
1420
|
-
});
|
|
1421
|
-
setTimeout(
|
|
1422
|
-
() => {
|
|
1423
|
-
server.close();
|
|
1424
|
-
reject(new Error("Authentication timed out"));
|
|
1425
|
-
},
|
|
1426
|
-
5 * 60 * 1e3
|
|
1427
|
-
);
|
|
1428
|
-
});
|
|
1429
|
-
}
|
|
1430
|
-
function generateSyncScript(apiUrl, apiToken) {
|
|
1431
|
-
return `#!/usr/bin/env node
|
|
1432
|
-
/**
|
|
1433
|
-
* CCgather Auto-Sync Script
|
|
1434
|
-
* This script is automatically called by Claude Code's Stop hook
|
|
1435
|
-
* to sync your usage data to the leaderboard.
|
|
1436
|
-
*/
|
|
1437
|
-
|
|
1438
|
-
const https = require('https');
|
|
1439
|
-
const http = require('http');
|
|
1440
|
-
const fs = require('fs');
|
|
1441
|
-
const path = require('path');
|
|
1442
|
-
const os = require('os');
|
|
1443
|
-
|
|
1444
|
-
const API_URL = '${apiUrl}';
|
|
1445
|
-
const API_TOKEN = '${apiToken}';
|
|
1446
|
-
|
|
1447
|
-
// Get Claude Code projects directory for JSONL files
|
|
1448
|
-
function getClaudeProjectsDir() {
|
|
1449
|
-
const platform = os.platform();
|
|
1450
|
-
if (platform === 'win32') {
|
|
1451
|
-
return path.join(os.homedir(), '.claude', 'projects');
|
|
1452
|
-
}
|
|
1453
|
-
return path.join(os.homedir(), '.claude', 'projects');
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
// Parse JSONL files for usage data
|
|
1457
|
-
function parseUsageFromJsonl() {
|
|
1458
|
-
const projectsDir = getClaudeProjectsDir();
|
|
1459
|
-
|
|
1460
|
-
if (!fs.existsSync(projectsDir)) {
|
|
1461
|
-
return null;
|
|
1462
|
-
}
|
|
1463
|
-
|
|
1464
|
-
let totalInputTokens = 0;
|
|
1465
|
-
let totalOutputTokens = 0;
|
|
1466
|
-
let totalCacheRead = 0;
|
|
1467
|
-
let totalCacheWrite = 0;
|
|
1468
|
-
const modelBreakdown = {};
|
|
1469
|
-
|
|
1470
|
-
// Recursively find all .jsonl files
|
|
1471
|
-
function findJsonlFiles(dir) {
|
|
1472
|
-
const files = [];
|
|
1473
|
-
try {
|
|
1474
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1475
|
-
for (const entry of entries) {
|
|
1476
|
-
const fullPath = path.join(dir, entry.name);
|
|
1477
|
-
if (entry.isDirectory()) {
|
|
1478
|
-
files.push(...findJsonlFiles(fullPath));
|
|
1479
|
-
} else if (entry.name.endsWith('.jsonl')) {
|
|
1480
|
-
files.push(fullPath);
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
} catch (e) {
|
|
1484
|
-
// Ignore permission errors
|
|
1485
|
-
}
|
|
1486
|
-
return files;
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
const jsonlFiles = findJsonlFiles(projectsDir);
|
|
1490
|
-
|
|
1491
|
-
for (const filePath of jsonlFiles) {
|
|
1492
|
-
try {
|
|
1493
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
1494
|
-
const lines = content.split('\\n').filter(line => line.trim());
|
|
1495
|
-
|
|
1496
|
-
for (const line of lines) {
|
|
1497
|
-
try {
|
|
1498
|
-
const event = JSON.parse(line);
|
|
1499
|
-
|
|
1500
|
-
if (event.type === 'assistant' && event.message?.usage) {
|
|
1501
|
-
const usage = event.message.usage;
|
|
1502
|
-
totalInputTokens += usage.input_tokens || 0;
|
|
1503
|
-
totalOutputTokens += usage.output_tokens || 0;
|
|
1504
|
-
totalCacheRead += usage.cache_read_input_tokens || 0;
|
|
1505
|
-
totalCacheWrite += usage.cache_creation_input_tokens || 0;
|
|
1506
|
-
|
|
1507
|
-
const model = event.message.model || 'unknown';
|
|
1508
|
-
modelBreakdown[model] = (modelBreakdown[model] || 0) +
|
|
1509
|
-
(usage.input_tokens || 0) + (usage.output_tokens || 0);
|
|
1510
|
-
}
|
|
1511
|
-
} catch (e) {
|
|
1512
|
-
// Skip invalid JSON lines
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
} catch (e) {
|
|
1516
|
-
// Skip unreadable files
|
|
1517
|
-
}
|
|
1518
|
-
}
|
|
1519
|
-
|
|
1520
|
-
const totalTokens = totalInputTokens + totalOutputTokens;
|
|
1521
|
-
|
|
1522
|
-
if (totalTokens === 0) {
|
|
1523
|
-
return null;
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
// Estimate cost (rough approximation)
|
|
1527
|
-
const costPerMillion = 3; // Average cost
|
|
1528
|
-
const totalCost = (totalTokens / 1000000) * costPerMillion;
|
|
1529
|
-
|
|
1530
|
-
return {
|
|
1531
|
-
totalTokens,
|
|
1532
|
-
totalCost: Math.round(totalCost * 100) / 100,
|
|
1533
|
-
inputTokens: totalInputTokens,
|
|
1534
|
-
outputTokens: totalOutputTokens,
|
|
1535
|
-
cacheReadTokens: totalCacheRead,
|
|
1536
|
-
cacheWriteTokens: totalCacheWrite,
|
|
1537
|
-
modelBreakdown
|
|
1538
|
-
};
|
|
1539
|
-
}
|
|
1540
|
-
|
|
1541
|
-
// Send data to CCgather API
|
|
1542
|
-
function syncToServer(data) {
|
|
1543
|
-
const urlObj = new URL(API_URL + '/cli/sync');
|
|
1544
|
-
const isHttps = urlObj.protocol === 'https:';
|
|
1545
|
-
const client = isHttps ? https : http;
|
|
1546
|
-
|
|
1547
|
-
const postData = JSON.stringify({
|
|
1548
|
-
totalTokens: data.totalTokens,
|
|
1549
|
-
totalSpent: data.totalCost,
|
|
1550
|
-
inputTokens: data.inputTokens,
|
|
1551
|
-
outputTokens: data.outputTokens,
|
|
1552
|
-
cacheReadTokens: data.cacheReadTokens,
|
|
1553
|
-
cacheWriteTokens: data.cacheWriteTokens,
|
|
1554
|
-
modelBreakdown: data.modelBreakdown,
|
|
1555
|
-
timestamp: new Date().toISOString()
|
|
1556
|
-
});
|
|
1557
|
-
|
|
1558
|
-
const options = {
|
|
1559
|
-
hostname: urlObj.hostname,
|
|
1560
|
-
port: urlObj.port || (isHttps ? 443 : 80),
|
|
1561
|
-
path: urlObj.pathname,
|
|
1562
|
-
method: 'POST',
|
|
1563
|
-
headers: {
|
|
1564
|
-
'Content-Type': 'application/json',
|
|
1565
|
-
'Content-Length': Buffer.byteLength(postData),
|
|
1566
|
-
'Authorization': 'Bearer ' + API_TOKEN
|
|
1567
|
-
}
|
|
1568
|
-
};
|
|
1569
|
-
|
|
1570
|
-
const req = client.request(options, (res) => {
|
|
1571
|
-
// Silent success
|
|
1572
|
-
});
|
|
1573
|
-
|
|
1574
|
-
req.on('error', (e) => {
|
|
1575
|
-
// Silent failure - don't interrupt user's workflow
|
|
1576
|
-
});
|
|
1577
|
-
|
|
1578
|
-
req.write(postData);
|
|
1579
|
-
req.end();
|
|
1580
|
-
}
|
|
1581
|
-
|
|
1582
|
-
// Main execution
|
|
1583
|
-
const usageData = parseUsageFromJsonl();
|
|
1584
|
-
if (usageData) {
|
|
1585
|
-
syncToServer(usageData);
|
|
1586
|
-
}
|
|
1587
|
-
`;
|
|
1588
|
-
}
|
|
1589
|
-
function installStopHook() {
|
|
1590
|
-
const claudeDir = getClaudeSettingsDir2();
|
|
1591
|
-
const settingsPath = path5.join(claudeDir, "settings.json");
|
|
1592
|
-
if (!fs5.existsSync(claudeDir)) {
|
|
1593
|
-
fs5.mkdirSync(claudeDir, { recursive: true });
|
|
1594
|
-
}
|
|
1595
|
-
let settings = {};
|
|
1596
|
-
try {
|
|
1597
|
-
if (fs5.existsSync(settingsPath)) {
|
|
1598
|
-
const content = fs5.readFileSync(settingsPath, "utf-8");
|
|
1599
|
-
settings = JSON.parse(content);
|
|
1600
|
-
}
|
|
1601
|
-
} catch {
|
|
1602
|
-
}
|
|
1603
|
-
if (!settings.hooks || typeof settings.hooks !== "object") {
|
|
1604
|
-
settings.hooks = {};
|
|
1605
|
-
}
|
|
1606
|
-
const hooks = settings.hooks;
|
|
1607
|
-
const syncScriptPath = path5.join(claudeDir, "ccgather-sync.js");
|
|
1608
|
-
const hookCommand = `node "${syncScriptPath}"`;
|
|
1609
|
-
if (!hooks.Stop || !Array.isArray(hooks.Stop)) {
|
|
1610
|
-
hooks.Stop = [];
|
|
1611
|
-
}
|
|
1612
|
-
const existingHook = hooks.Stop.find(
|
|
1613
|
-
(h) => typeof h === "object" && h !== null && h.command === hookCommand
|
|
1614
|
-
);
|
|
1615
|
-
if (!existingHook) {
|
|
1616
|
-
hooks.Stop.push({
|
|
1617
|
-
command: hookCommand,
|
|
1618
|
-
background: true
|
|
1619
|
-
});
|
|
1620
|
-
}
|
|
1621
|
-
fs5.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
1622
|
-
return { success: true, message: "Stop hook installed" };
|
|
1623
|
-
}
|
|
1624
|
-
function saveSyncScript(apiUrl, apiToken) {
|
|
1625
|
-
const claudeDir = getClaudeSettingsDir2();
|
|
1626
|
-
const scriptPath = path5.join(claudeDir, "ccgather-sync.js");
|
|
1627
|
-
const scriptContent = generateSyncScript(apiUrl, apiToken);
|
|
1628
|
-
fs5.writeFileSync(scriptPath, scriptContent);
|
|
1629
|
-
if (os5.platform() !== "win32") {
|
|
1630
|
-
fs5.chmodSync(scriptPath, "755");
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
async function setupAuto(options = {}) {
|
|
1634
|
-
if (options.manual) {
|
|
1635
|
-
console.log(import_chalk3.default.bold("\n\u{1F527} Disabling Auto-Sync\n"));
|
|
1636
|
-
const { reset: reset2 } = await Promise.resolve().then(() => (init_reset(), reset_exports));
|
|
1637
|
-
await reset2();
|
|
1638
|
-
console.log(import_chalk3.default.green("\u2713 Auto-sync disabled. Use `npx ccgather` to submit manually."));
|
|
1639
|
-
return;
|
|
1640
|
-
}
|
|
1641
|
-
console.log(import_chalk3.default.bold("\n\u26A0\uFE0F Auto-Sync Mode (Optional)\n"));
|
|
1642
|
-
console.log(import_chalk3.default.gray("This will install a hook that automatically syncs"));
|
|
1643
|
-
console.log(import_chalk3.default.gray("your usage data when Claude Code sessions end."));
|
|
1644
|
-
console.log();
|
|
1645
|
-
console.log(
|
|
1646
|
-
import_chalk3.default.yellow("Note: Manual submission (`npx ccgather`) is recommended for most users.")
|
|
1647
|
-
);
|
|
1648
|
-
console.log();
|
|
1649
|
-
const inquirer4 = await import("inquirer");
|
|
1650
|
-
const { proceed } = await inquirer4.default.prompt([
|
|
1651
|
-
{
|
|
1652
|
-
type: "confirm",
|
|
1653
|
-
name: "proceed",
|
|
1654
|
-
message: "Continue with auto-sync setup?",
|
|
1655
|
-
default: false
|
|
1656
|
-
}
|
|
1657
|
-
]);
|
|
1658
|
-
if (!proceed) {
|
|
1659
|
-
console.log(import_chalk3.default.gray("\nSetup cancelled. Use `npx ccgather` to submit manually."));
|
|
1660
|
-
return;
|
|
1661
|
-
}
|
|
1662
|
-
const config = getConfig();
|
|
1663
|
-
const apiUrl = getApiUrl();
|
|
1664
|
-
console.log(import_chalk3.default.bold("\n\u{1F310} CCgather Setup\n"));
|
|
1665
|
-
const existingToken = config.get("apiToken");
|
|
1666
|
-
if (existingToken) {
|
|
1667
|
-
const inquirer5 = await import("inquirer");
|
|
1668
|
-
const { reconfigure } = await inquirer5.default.prompt([
|
|
1669
|
-
{
|
|
1670
|
-
type: "confirm",
|
|
1671
|
-
name: "reconfigure",
|
|
1672
|
-
message: "You are already set up. Do you want to reconfigure?",
|
|
1673
|
-
default: false
|
|
1674
|
-
}
|
|
1675
|
-
]);
|
|
1676
|
-
if (!reconfigure) {
|
|
1677
|
-
console.log(import_chalk3.default.gray("Setup cancelled."));
|
|
1678
|
-
return;
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
|
-
console.log(import_chalk3.default.gray("Opening browser for GitHub authentication...\n"));
|
|
1682
|
-
const callbackUrl = `http://localhost:${CALLBACK_PORT}/callback`;
|
|
1683
|
-
const baseUrl = apiUrl.replace("/api", "");
|
|
1684
|
-
const authUrl = `${baseUrl}/cli/auth?callback=${encodeURIComponent(callbackUrl)}`;
|
|
1685
|
-
const serverPromise = createCallbackServer();
|
|
1686
|
-
try {
|
|
1687
|
-
await openBrowser(authUrl);
|
|
1688
|
-
} catch {
|
|
1689
|
-
console.log(import_chalk3.default.yellow("Could not open browser automatically."));
|
|
1690
|
-
console.log(import_chalk3.default.gray("Please open this URL manually:"));
|
|
1691
|
-
console.log(import_chalk3.default.cyan(authUrl));
|
|
1692
|
-
console.log();
|
|
1693
|
-
}
|
|
1694
|
-
const spinner = (0, import_ora4.default)("Waiting for authentication...").start();
|
|
1695
|
-
try {
|
|
1696
|
-
const authData = await serverPromise;
|
|
1697
|
-
spinner.succeed(import_chalk3.default.green("Authentication successful!"));
|
|
1698
|
-
config.set("apiToken", authData.token);
|
|
1699
|
-
config.set("userId", authData.userId);
|
|
1700
|
-
const hookSpinner = (0, import_ora4.default)("Installing Claude Code hook...").start();
|
|
1701
|
-
try {
|
|
1702
|
-
saveSyncScript(apiUrl, authData.token);
|
|
1703
|
-
const hookResult = installStopHook();
|
|
1704
|
-
if (hookResult.success) {
|
|
1705
|
-
hookSpinner.succeed(import_chalk3.default.green("Hook installed successfully!"));
|
|
1706
|
-
} else {
|
|
1707
|
-
hookSpinner.fail(import_chalk3.default.red("Failed to install hook"));
|
|
1708
|
-
console.log(import_chalk3.default.red(hookResult.message));
|
|
1709
|
-
}
|
|
1710
|
-
} catch (err) {
|
|
1711
|
-
hookSpinner.fail(import_chalk3.default.red("Failed to install hook"));
|
|
1712
|
-
console.log(import_chalk3.default.red(err instanceof Error ? err.message : "Unknown error"));
|
|
1713
|
-
}
|
|
1714
|
-
console.log();
|
|
1715
|
-
console.log(import_chalk3.default.green.bold("\u2705 Setup complete!"));
|
|
1716
|
-
console.log();
|
|
1717
|
-
console.log(import_chalk3.default.gray(`Welcome, ${import_chalk3.default.white(authData.username)}!`));
|
|
1718
|
-
console.log();
|
|
1719
|
-
console.log(import_chalk3.default.gray("Your Claude Code usage will now be automatically synced"));
|
|
1720
|
-
console.log(import_chalk3.default.gray("to the leaderboard when each session ends."));
|
|
1721
|
-
console.log();
|
|
1722
|
-
console.log(import_chalk3.default.gray("View your stats:"));
|
|
1723
|
-
console.log(import_chalk3.default.cyan(" npx ccgather status"));
|
|
1724
|
-
console.log();
|
|
1725
|
-
console.log(import_chalk3.default.gray("View the leaderboard:"));
|
|
1726
|
-
console.log(import_chalk3.default.cyan(" https://ccgather.com/leaderboard"));
|
|
1727
|
-
console.log();
|
|
1728
|
-
} catch (err) {
|
|
1729
|
-
spinner.fail(import_chalk3.default.red("Authentication failed"));
|
|
1730
|
-
console.log(import_chalk3.default.red(err instanceof Error ? err.message : "Unknown error"));
|
|
1731
|
-
process.exit(1);
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
// src/commands/scan.ts
|
|
1736
|
-
var import_ora5 = __toESM(require("ora"));
|
|
1737
|
-
init_ccgather_json();
|
|
1738
|
-
init_ui();
|
|
1739
|
-
function displayResults(data) {
|
|
1740
|
-
console.log();
|
|
1741
|
-
const usageLines = [
|
|
1742
|
-
`${colors.white.bold("\u{1F4CA} Usage Summary")}`,
|
|
1743
|
-
"",
|
|
1744
|
-
`${colors.muted("Total Tokens")} ${colors.primary(formatNumber(data.usage.totalTokens))}`,
|
|
1745
|
-
`${colors.muted("Total Cost")} ${colors.success(formatCost(data.usage.totalCost))}`,
|
|
1746
|
-
`${colors.muted("Input Tokens")} ${colors.white(formatNumber(data.usage.inputTokens))}`,
|
|
1747
|
-
`${colors.muted("Output Tokens")} ${colors.white(formatNumber(data.usage.outputTokens))}`
|
|
1748
|
-
];
|
|
1749
|
-
if (data.usage.cacheReadTokens > 0 || data.usage.cacheWriteTokens > 0) {
|
|
1750
|
-
usageLines.push(
|
|
1751
|
-
`${colors.muted("Cache Read")} ${colors.dim(formatNumber(data.usage.cacheReadTokens))}`
|
|
1752
|
-
);
|
|
1753
|
-
usageLines.push(
|
|
1754
|
-
`${colors.muted("Cache Write")} ${colors.dim(formatNumber(data.usage.cacheWriteTokens))}`
|
|
1755
|
-
);
|
|
1756
|
-
}
|
|
1757
|
-
console.log(createBox(usageLines));
|
|
1758
|
-
console.log();
|
|
1759
|
-
const statsLines = [
|
|
1760
|
-
`${colors.white.bold("\u{1F4C8} Statistics")}`,
|
|
1761
|
-
"",
|
|
1762
|
-
`${colors.muted("Days Tracked")} ${colors.warning(data.stats.daysTracked.toString())}`,
|
|
1763
|
-
`${colors.muted("Sessions")} ${colors.white(data.stats.sessionsCount.toString())}`,
|
|
1764
|
-
`${colors.muted("First Used")} ${colors.dim(data.stats.firstUsed || "N/A")}`,
|
|
1765
|
-
`${colors.muted("Last Used")} ${colors.dim(data.stats.lastUsed || "N/A")}`
|
|
1766
|
-
];
|
|
1767
|
-
console.log(createBox(statsLines));
|
|
1768
|
-
if (data.account?.ccplan) {
|
|
1769
|
-
console.log();
|
|
1770
|
-
const badge = getCCplanBadge(data.account.ccplan);
|
|
1771
|
-
console.log(` ${colors.muted("Account Plan:")} ${badge}`);
|
|
1772
|
-
if (data.account.rateLimitTier) {
|
|
1773
|
-
console.log(` ${colors.muted("Rate Limit:")} ${colors.dim(data.account.rateLimitTier)}`);
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
if (Object.keys(data.models).length > 0) {
|
|
1777
|
-
console.log();
|
|
1778
|
-
console.log(` ${colors.white.bold("\u{1F916} Model Breakdown")}`);
|
|
1779
|
-
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1780
|
-
const sortedModels = Object.entries(data.models).sort(([, a], [, b]) => b - a).slice(0, 5);
|
|
1781
|
-
for (const [model, tokens] of sortedModels) {
|
|
1782
|
-
const shortModel = model.replace("claude-", "").substring(0, 20);
|
|
1783
|
-
console.log(
|
|
1784
|
-
` ${colors.dim("\u2022")} ${colors.white(shortModel.padEnd(22))} ${colors.primary(formatNumber(tokens))}`
|
|
1785
|
-
);
|
|
1786
|
-
}
|
|
1787
|
-
if (Object.keys(data.models).length > 5) {
|
|
1788
|
-
console.log(` ${colors.dim(`... and ${Object.keys(data.models).length - 5} more models`)}`);
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
if (data.projects && Object.keys(data.projects).length > 0) {
|
|
1792
|
-
console.log();
|
|
1793
|
-
console.log(` ${colors.white.bold("\u{1F4C1} Top Projects")}`);
|
|
1794
|
-
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1795
|
-
const sortedProjects = Object.entries(data.projects).sort(([, a], [, b]) => b.tokens - a.tokens).slice(0, 5);
|
|
1796
|
-
for (const [name, stats] of sortedProjects) {
|
|
1797
|
-
const displayName = name.length > 20 ? name.substring(0, 17) + "..." : name;
|
|
1798
|
-
console.log(
|
|
1799
|
-
` ${colors.cyan(displayName.padEnd(20))} ${colors.primary(formatNumber(stats.tokens).padStart(8))} ${colors.success(formatCost(stats.cost).padStart(10))} ${colors.dim(`(${stats.sessions} sess)`)}`
|
|
1800
|
-
);
|
|
1801
|
-
}
|
|
1802
|
-
if (Object.keys(data.projects).length > 5) {
|
|
1803
|
-
console.log(
|
|
1804
|
-
` ${colors.dim(`... and ${Object.keys(data.projects).length - 5} more projects`)}`
|
|
1805
|
-
);
|
|
1806
|
-
}
|
|
1807
|
-
}
|
|
1808
|
-
console.log();
|
|
1809
|
-
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1810
|
-
console.log(` ${colors.muted("Saved to:")} ${colors.dim(getCCGatherJsonPath())}`);
|
|
1811
|
-
console.log();
|
|
1812
|
-
}
|
|
1813
|
-
async function scan(options = {}) {
|
|
1814
|
-
printCompactHeader("1.2.1");
|
|
1815
|
-
console.log(header("Scan Claude Code Usage", "\u{1F50D}"));
|
|
1816
|
-
let days;
|
|
1817
|
-
if (options.all) {
|
|
1818
|
-
days = 0;
|
|
1819
|
-
console.log(` ${colors.muted("Mode:")} ${colors.primary("All time")} (no date limit)
|
|
1820
|
-
`);
|
|
1821
|
-
} else if (options.days) {
|
|
1822
|
-
days = options.days;
|
|
1823
|
-
console.log(` ${colors.muted("Mode:")} ${colors.primary(`Last ${days} days`)}
|
|
1824
|
-
`);
|
|
1825
|
-
} else {
|
|
1826
|
-
days = 30;
|
|
1827
|
-
console.log(` ${colors.muted("Mode:")} ${colors.primary("Last 30 days")} (default)
|
|
1828
|
-
`);
|
|
1829
|
-
}
|
|
1830
|
-
const spinner = (0, import_ora5.default)({
|
|
1831
|
-
text: "Scanning JSONL files...",
|
|
1832
|
-
color: "cyan"
|
|
1833
|
-
}).start();
|
|
1834
|
-
const data = scanAndSave({ days });
|
|
1835
|
-
if (!data) {
|
|
1836
|
-
spinner.fail(colors.error("No usage data found"));
|
|
1837
|
-
console.log();
|
|
1838
|
-
console.log(` ${error("Possible reasons:")}`);
|
|
1839
|
-
console.log(` ${colors.dim("\u2022")} Claude Code has not been used yet`);
|
|
1840
|
-
console.log(` ${colors.dim("\u2022")} ~/.claude/projects/ directory is empty`);
|
|
1841
|
-
console.log(` ${colors.dim("\u2022")} No permission to read files`);
|
|
1842
|
-
console.log();
|
|
1843
|
-
process.exit(1);
|
|
1844
|
-
}
|
|
1845
|
-
spinner.succeed(colors.success("Scan complete!"));
|
|
1846
|
-
displayResults(data);
|
|
1847
|
-
console.log(` ${colors.muted("Next:")} Run ${colors.white("npx ccgather")} to submit your data`);
|
|
1848
|
-
console.log();
|
|
1849
|
-
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1850
|
-
console.log(
|
|
1851
|
-
` ${colors.muted("Tip:")} Use ${colors.white("--all")} for all-time data or ${colors.white("--days <n>")} for custom range`
|
|
1852
|
-
);
|
|
984
|
+
console.log(` ${colors.muted("View on leaderboard:")} ${link(leaderboardUrl)}`);
|
|
1853
985
|
console.log();
|
|
1854
986
|
}
|
|
1855
987
|
|
|
1856
988
|
// src/index.ts
|
|
1857
|
-
init_ui();
|
|
1858
989
|
init_config();
|
|
1859
|
-
|
|
1860
|
-
var VERSION = "1.3.4";
|
|
990
|
+
var VERSION = "1.3.5";
|
|
1861
991
|
var pkg = { name: "ccgather", version: VERSION };
|
|
1862
992
|
var notifier = (0, import_update_notifier.default)({ pkg, updateCheckInterval: 1e3 * 60 * 60 });
|
|
1863
993
|
notifier.notify({
|
|
1864
|
-
message: `${
|
|
1865
|
-
${
|
|
994
|
+
message: `${import_chalk3.default.yellow("Update")} available ${import_chalk3.default.dim(VERSION)} \u2192 ${import_chalk3.default.yellow(notifier.update?.latest || "")}
|
|
995
|
+
${import_chalk3.default.yellow("Run")} ${import_chalk3.default.cyan("npx ccgather@latest")} to update`,
|
|
1866
996
|
boxenOptions: {
|
|
1867
997
|
padding: 1,
|
|
1868
998
|
margin: 1,
|
|
@@ -1871,24 +1001,9 @@ ${import_chalk6.default.yellow("Run")} ${import_chalk6.default.cyan("npx ccgathe
|
|
|
1871
1001
|
}
|
|
1872
1002
|
});
|
|
1873
1003
|
var program = new import_commander.Command();
|
|
1874
|
-
program.name("ccgather").description("Submit your Claude Code usage to the CCgather leaderboard").version(VERSION)
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
program.command("status").description("View your current rank and stats").option("--json", "Output as JSON").action((opts) => status(opts));
|
|
1878
|
-
program.command("sync").description("Sync usage data to CCgather").action(async () => {
|
|
1879
|
-
const { sync: sync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
1880
|
-
await sync2({});
|
|
1881
|
-
});
|
|
1882
|
-
program.command("auth").description("Authenticate with CCgather").action(async () => {
|
|
1883
|
-
const { auth: auth2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
1884
|
-
await auth2({});
|
|
1885
|
-
});
|
|
1886
|
-
program.command("reset").description("Remove auto-sync hook and clear config").action(async () => {
|
|
1887
|
-
const { reset: reset2 } = await Promise.resolve().then(() => (init_reset(), reset_exports));
|
|
1888
|
-
await reset2();
|
|
1889
|
-
});
|
|
1890
|
-
async function showInteractiveMenu() {
|
|
1891
|
-
printHeader(VERSION);
|
|
1004
|
+
program.name("ccgather").description("Submit your Claude Code usage to the CCgather leaderboard").version(VERSION);
|
|
1005
|
+
async function showMainMenu() {
|
|
1006
|
+
await printAnimatedHeader(VERSION);
|
|
1892
1007
|
if (!isAuthenticated()) {
|
|
1893
1008
|
console.log(colors.warning("\n \u{1F510} Authentication required\n"));
|
|
1894
1009
|
console.log(colors.dim(" To submit your Claude Code usage, you need to log in first.\n"));
|
|
@@ -1905,43 +1020,45 @@ async function showInteractiveMenu() {
|
|
|
1905
1020
|
await auth2({});
|
|
1906
1021
|
console.log();
|
|
1907
1022
|
} else {
|
|
1908
|
-
console.log(colors.dim("\n
|
|
1023
|
+
console.log(colors.dim("\n Goodbye!\n"));
|
|
1024
|
+
process.exit(0);
|
|
1909
1025
|
}
|
|
1910
1026
|
}
|
|
1911
1027
|
if (isAuthenticated()) {
|
|
1912
1028
|
try {
|
|
1913
|
-
const
|
|
1029
|
+
const statusPromise = getStatus();
|
|
1030
|
+
await dotAnimation("Loading profile", 1e3, "fast");
|
|
1031
|
+
const result = await statusPromise;
|
|
1914
1032
|
if (result.success && result.data) {
|
|
1915
1033
|
const stats = result.data;
|
|
1916
1034
|
const levelInfo = getLevelInfo(stats.totalTokens);
|
|
1917
|
-
|
|
1035
|
+
await printAnimatedWelcomeBox({
|
|
1918
1036
|
username: getConfig().get("username") || "User",
|
|
1919
1037
|
level: levelInfo.level,
|
|
1920
1038
|
levelName: levelInfo.name,
|
|
1921
1039
|
levelIcon: levelInfo.icon,
|
|
1922
1040
|
globalRank: stats.rank,
|
|
1041
|
+
countryRank: stats.countryRank || void 0,
|
|
1042
|
+
countryCode: stats.countryCode || void 0,
|
|
1923
1043
|
ccplan: stats.tier
|
|
1924
1044
|
});
|
|
1925
|
-
console.log(welcomeBox);
|
|
1926
1045
|
console.log();
|
|
1927
1046
|
}
|
|
1928
1047
|
} catch {
|
|
1929
1048
|
}
|
|
1930
1049
|
}
|
|
1050
|
+
await showMenuOnly();
|
|
1051
|
+
}
|
|
1052
|
+
async function showMenuOnly() {
|
|
1931
1053
|
const { action } = await import_inquirer3.default.prompt([
|
|
1932
1054
|
{
|
|
1933
1055
|
type: "list",
|
|
1934
1056
|
name: "action",
|
|
1935
1057
|
message: "What would you like to do?",
|
|
1936
1058
|
choices: [
|
|
1937
|
-
{ name: `\u{1F4E4} ${
|
|
1938
|
-
{ name: `\u{1F4CA} ${
|
|
1939
|
-
{ name: `\
|
|
1940
|
-
{ name: `\u2699\uFE0F ${import_chalk6.default.white("Setup auto-sync")}`, value: "setup" },
|
|
1941
|
-
{ name: `\u{1F527} ${import_chalk6.default.white("Settings")}`, value: "settings" },
|
|
1942
|
-
new import_inquirer3.default.Separator(),
|
|
1943
|
-
{ name: `\u2753 ${import_chalk6.default.gray("Help")}`, value: "help" },
|
|
1944
|
-
{ name: `\u{1F6AA} ${import_chalk6.default.gray("Exit")}`, value: "exit" }
|
|
1059
|
+
{ name: `\u{1F4E4} ${import_chalk3.default.white("Submit usage data")}`, value: "submit" },
|
|
1060
|
+
{ name: `\u{1F4CA} ${import_chalk3.default.white("View my rank")}`, value: "rank" },
|
|
1061
|
+
{ name: `\u2699\uFE0F ${import_chalk3.default.white("Settings")}`, value: "settings" }
|
|
1945
1062
|
],
|
|
1946
1063
|
loop: false
|
|
1947
1064
|
}
|
|
@@ -1949,70 +1066,30 @@ async function showInteractiveMenu() {
|
|
|
1949
1066
|
console.log();
|
|
1950
1067
|
switch (action) {
|
|
1951
1068
|
case "submit":
|
|
1952
|
-
await
|
|
1953
|
-
|
|
1954
|
-
case "status":
|
|
1955
|
-
await status({ json: false });
|
|
1956
|
-
break;
|
|
1957
|
-
case "scan":
|
|
1958
|
-
await scan();
|
|
1069
|
+
await dotAnimation("Preparing", 400);
|
|
1070
|
+
await submit({});
|
|
1959
1071
|
break;
|
|
1960
|
-
case "
|
|
1961
|
-
await
|
|
1072
|
+
case "rank":
|
|
1073
|
+
await dotAnimation("Fetching stats", 400);
|
|
1074
|
+
await status({});
|
|
1962
1075
|
break;
|
|
1963
1076
|
case "settings":
|
|
1964
1077
|
await showSettingsMenu();
|
|
1965
1078
|
break;
|
|
1966
|
-
case "help":
|
|
1967
|
-
showHelp();
|
|
1968
|
-
break;
|
|
1969
|
-
case "exit":
|
|
1970
|
-
console.log(colors.muted(" Goodbye! Happy coding with Claude! \u{1F44B}\n"));
|
|
1971
|
-
process.exit(0);
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
async function showSetupMenu() {
|
|
1975
|
-
const { setupAction } = await import_inquirer3.default.prompt([
|
|
1976
|
-
{
|
|
1977
|
-
type: "list",
|
|
1978
|
-
name: "setupAction",
|
|
1979
|
-
message: "Auto-sync settings:",
|
|
1980
|
-
choices: [
|
|
1981
|
-
{ name: `\u2705 ${import_chalk6.default.white("Enable auto-sync")}`, value: "enable" },
|
|
1982
|
-
{ name: `\u274C ${import_chalk6.default.white("Disable auto-sync")}`, value: "disable" },
|
|
1983
|
-
{ name: `\u2B05\uFE0F ${import_chalk6.default.gray("Back")}`, value: "back" }
|
|
1984
|
-
]
|
|
1985
|
-
}
|
|
1986
|
-
]);
|
|
1987
|
-
switch (setupAction) {
|
|
1988
|
-
case "enable":
|
|
1989
|
-
await setupAuto({ auto: true });
|
|
1990
|
-
break;
|
|
1991
|
-
case "disable":
|
|
1992
|
-
await setupAuto({ manual: true });
|
|
1993
|
-
break;
|
|
1994
|
-
case "back":
|
|
1995
|
-
await showInteractiveMenu();
|
|
1996
|
-
break;
|
|
1997
1079
|
}
|
|
1998
1080
|
}
|
|
1999
1081
|
async function showSettingsMenu() {
|
|
2000
|
-
const config = getConfig();
|
|
2001
|
-
const isAuth = isAuthenticated();
|
|
2002
1082
|
const { settingsAction } = await import_inquirer3.default.prompt([
|
|
2003
1083
|
{
|
|
2004
1084
|
type: "list",
|
|
2005
1085
|
name: "settingsAction",
|
|
2006
1086
|
message: "Settings:",
|
|
2007
1087
|
choices: [
|
|
2008
|
-
{
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
{ name: `\u{1F4CB} ${import_chalk6.default.white("Show current config")}`, value: "show" },
|
|
2014
|
-
{ name: `\u2B05\uFE0F ${import_chalk6.default.gray("Back")}`, value: "back" }
|
|
2015
|
-
]
|
|
1088
|
+
{ name: `\u{1F510} ${import_chalk3.default.white("Re-authenticate")}`, value: "auth" },
|
|
1089
|
+
{ name: `\u{1F5D1}\uFE0F ${import_chalk3.default.white("Disconnect from CCgather")}`, value: "disconnect" },
|
|
1090
|
+
{ name: `\u2B05\uFE0F ${import_chalk3.default.gray("Back")}`, value: "back" }
|
|
1091
|
+
],
|
|
1092
|
+
loop: false
|
|
2016
1093
|
}
|
|
2017
1094
|
]);
|
|
2018
1095
|
switch (settingsAction) {
|
|
@@ -2021,98 +1098,49 @@ async function showSettingsMenu() {
|
|
|
2021
1098
|
await auth2({});
|
|
2022
1099
|
break;
|
|
2023
1100
|
}
|
|
2024
|
-
case "
|
|
2025
|
-
|
|
2026
|
-
{
|
|
2027
|
-
type: "confirm",
|
|
2028
|
-
name: "confirmReset",
|
|
2029
|
-
message: import_chalk6.default.yellow("Are you sure you want to reset all settings?"),
|
|
2030
|
-
default: false
|
|
2031
|
-
}
|
|
2032
|
-
]);
|
|
2033
|
-
if (confirmReset) {
|
|
2034
|
-
const { reset: reset2 } = await Promise.resolve().then(() => (init_reset(), reset_exports));
|
|
2035
|
-
await reset2();
|
|
2036
|
-
}
|
|
1101
|
+
case "disconnect": {
|
|
1102
|
+
await handleDisconnect();
|
|
2037
1103
|
break;
|
|
2038
1104
|
}
|
|
2039
|
-
case "show":
|
|
2040
|
-
console.log();
|
|
2041
|
-
console.log(colors.muted(" Current Configuration:"));
|
|
2042
|
-
console.log(colors.dim(" \u2500".repeat(25)));
|
|
2043
|
-
console.log(
|
|
2044
|
-
` ${colors.muted("Username:")} ${config.get("username") || colors.dim("(not set)")}`
|
|
2045
|
-
);
|
|
2046
|
-
console.log(
|
|
2047
|
-
` ${colors.muted("API Token:")} ${config.get("apiToken") ? colors.success("\u2713 Set") : colors.error("\u2717 Not set")}`
|
|
2048
|
-
);
|
|
2049
|
-
console.log(
|
|
2050
|
-
` ${colors.muted("Auto-sync:")} ${config.get("autoSync") ? colors.success("Enabled") : colors.dim("Disabled")}`
|
|
2051
|
-
);
|
|
2052
|
-
console.log(
|
|
2053
|
-
` ${colors.muted("Last Sync:")} ${config.get("lastSync") || colors.dim("Never")}`
|
|
2054
|
-
);
|
|
2055
|
-
console.log();
|
|
2056
|
-
await promptContinue();
|
|
2057
|
-
await showSettingsMenu();
|
|
2058
|
-
break;
|
|
2059
1105
|
case "back":
|
|
2060
|
-
await
|
|
1106
|
+
await showMenuOnly();
|
|
2061
1107
|
break;
|
|
2062
1108
|
}
|
|
2063
1109
|
}
|
|
2064
|
-
function
|
|
2065
|
-
|
|
2066
|
-
console.log(colors.dim(" \u2500".repeat(25)));
|
|
2067
|
-
console.log();
|
|
2068
|
-
console.log(` ${colors.white("npx ccgather")} Interactive menu (default)`);
|
|
2069
|
-
console.log(
|
|
2070
|
-
` ${colors.white("npx ccgather scan")} Scan local Claude Code usage (last 30 days)`
|
|
2071
|
-
);
|
|
2072
|
-
console.log(` ${colors.white("npx ccgather scan --all")} Scan all-time usage (no date limit)`);
|
|
2073
|
-
console.log(` ${colors.white("npx ccgather scan -d 90")} Scan last 90 days of usage`);
|
|
2074
|
-
console.log(` ${colors.white("npx ccgather rank")} View your current rank`);
|
|
2075
|
-
console.log(` ${colors.white("npx ccgather sync")} Sync usage data`);
|
|
2076
|
-
console.log(` ${colors.white("npx ccgather auth")} Authenticate with CCgather`);
|
|
2077
|
-
console.log(` ${colors.white("npx ccgather reset")} Reset all settings`);
|
|
2078
|
-
console.log();
|
|
2079
|
-
console.log(colors.dim(" \u2500".repeat(25)));
|
|
2080
|
-
console.log(` ${colors.muted("Scan Options:")}`);
|
|
2081
|
-
console.log(` ${colors.white("-a, --all")} Scan all-time usage (no date limit)`);
|
|
2082
|
-
console.log(` ${colors.white("-d, --days <n>")} Scan last N days (default: 30)`);
|
|
2083
|
-
console.log();
|
|
2084
|
-
console.log(` ${colors.muted("General Options:")}`);
|
|
2085
|
-
console.log(` ${colors.white("-y, --yes")} Skip confirmation prompts`);
|
|
2086
|
-
console.log(` ${colors.white("--auto")} Enable auto-sync`);
|
|
2087
|
-
console.log(` ${colors.white("--manual")} Disable auto-sync`);
|
|
2088
|
-
console.log(` ${colors.white("--no-menu")} Skip interactive menu`);
|
|
2089
|
-
console.log();
|
|
2090
|
-
console.log(` ${colors.muted("Documentation:")} ${link("https://ccgather.com/docs")}`);
|
|
2091
|
-
console.log(` ${colors.muted("Leaderboard:")} ${link("https://ccgather.com/leaderboard")}`);
|
|
2092
|
-
console.log();
|
|
2093
|
-
}
|
|
2094
|
-
async function promptContinue() {
|
|
2095
|
-
await import_inquirer3.default.prompt([
|
|
1110
|
+
async function handleDisconnect() {
|
|
1111
|
+
const { confirmFirst } = await import_inquirer3.default.prompt([
|
|
2096
1112
|
{
|
|
2097
|
-
type: "
|
|
2098
|
-
name: "
|
|
2099
|
-
message:
|
|
1113
|
+
type: "confirm",
|
|
1114
|
+
name: "confirmFirst",
|
|
1115
|
+
message: import_chalk3.default.yellow("Are you sure you want to disconnect from CCgather?"),
|
|
1116
|
+
default: false
|
|
2100
1117
|
}
|
|
2101
1118
|
]);
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
await setupAuto({ auto: true });
|
|
2106
|
-
return;
|
|
2107
|
-
}
|
|
2108
|
-
if (options.manual) {
|
|
2109
|
-
await setupAuto({ manual: true });
|
|
1119
|
+
if (!confirmFirst) {
|
|
1120
|
+
console.log(colors.dim("\n Cancelled.\n"));
|
|
1121
|
+
await showSettingsMenu();
|
|
2110
1122
|
return;
|
|
2111
1123
|
}
|
|
2112
|
-
|
|
2113
|
-
|
|
1124
|
+
const { confirmSecond } = await import_inquirer3.default.prompt([
|
|
1125
|
+
{
|
|
1126
|
+
type: "confirm",
|
|
1127
|
+
name: "confirmSecond",
|
|
1128
|
+
message: import_chalk3.default.red("This will remove all local settings. Are you really sure?"),
|
|
1129
|
+
default: false
|
|
1130
|
+
}
|
|
1131
|
+
]);
|
|
1132
|
+
if (!confirmSecond) {
|
|
1133
|
+
console.log(colors.dim("\n Cancelled.\n"));
|
|
1134
|
+
await showSettingsMenu();
|
|
2114
1135
|
return;
|
|
2115
1136
|
}
|
|
2116
|
-
|
|
1137
|
+
resetConfig();
|
|
1138
|
+
console.log();
|
|
1139
|
+
console.log(colors.success(" \u2713 Disconnected from CCgather"));
|
|
1140
|
+
console.log(colors.dim(" Your CCgather settings have been removed."));
|
|
1141
|
+
console.log(colors.dim(" Run npx ccgather to set up again.\n"));
|
|
1142
|
+
}
|
|
1143
|
+
program.action(async () => {
|
|
1144
|
+
await showMainMenu();
|
|
2117
1145
|
});
|
|
2118
1146
|
program.parse();
|