ccgather 1.1.0 → 1.2.1
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 +911 -244
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -70,6 +70,69 @@ var init_config = __esm({
|
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
+
// src/lib/api.ts
|
|
74
|
+
async function fetchApi(endpoint, options = {}) {
|
|
75
|
+
const config = getConfig();
|
|
76
|
+
const apiToken = config.get("apiToken");
|
|
77
|
+
const apiUrl = getApiUrl();
|
|
78
|
+
if (!apiToken) {
|
|
79
|
+
return { success: false, error: "Not authenticated. Run: npx ccgather auth" };
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const response = await fetch(`${apiUrl}${endpoint}`, {
|
|
83
|
+
...options,
|
|
84
|
+
headers: {
|
|
85
|
+
"Content-Type": "application/json",
|
|
86
|
+
Authorization: `Bearer ${apiToken}`,
|
|
87
|
+
...options.headers
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
const data = await response.json();
|
|
91
|
+
if (!response.ok) {
|
|
92
|
+
return { success: false, error: data.error || `HTTP ${response.status}` };
|
|
93
|
+
}
|
|
94
|
+
return { success: true, data };
|
|
95
|
+
} catch (error2) {
|
|
96
|
+
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
97
|
+
return { success: false, error: message };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
async function syncUsage(payload) {
|
|
101
|
+
return fetchApi("/cli/sync", {
|
|
102
|
+
method: "POST",
|
|
103
|
+
body: JSON.stringify(payload)
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
async function getStatus() {
|
|
107
|
+
return fetchApi("/cli/status");
|
|
108
|
+
}
|
|
109
|
+
async function verifyToken(token) {
|
|
110
|
+
try {
|
|
111
|
+
const apiUrl = getApiUrl();
|
|
112
|
+
const response = await fetch(`${apiUrl}/cli/verify`, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers: {
|
|
115
|
+
"Content-Type": "application/json",
|
|
116
|
+
Authorization: `Bearer ${token}`
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
return { success: false, error: data.error || "Invalid token" };
|
|
122
|
+
}
|
|
123
|
+
return { success: true, data };
|
|
124
|
+
} catch (error2) {
|
|
125
|
+
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
126
|
+
return { success: false, error: message };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
var init_api = __esm({
|
|
130
|
+
"src/lib/api.ts"() {
|
|
131
|
+
"use strict";
|
|
132
|
+
init_config();
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
73
136
|
// src/commands/reset.ts
|
|
74
137
|
var reset_exports = {};
|
|
75
138
|
__export(reset_exports, {
|
|
@@ -120,9 +183,9 @@ function removeSyncScript() {
|
|
|
120
183
|
}
|
|
121
184
|
async function reset() {
|
|
122
185
|
const config = getConfig();
|
|
123
|
-
console.log(
|
|
186
|
+
console.log(import_chalk2.default.bold("\n\u{1F504} CCgather Reset\n"));
|
|
124
187
|
if (!config.get("apiToken")) {
|
|
125
|
-
console.log(
|
|
188
|
+
console.log(import_chalk2.default.yellow("CCgather is not configured."));
|
|
126
189
|
return;
|
|
127
190
|
}
|
|
128
191
|
const { confirmReset } = await import_inquirer.default.prompt([
|
|
@@ -134,50 +197,50 @@ async function reset() {
|
|
|
134
197
|
}
|
|
135
198
|
]);
|
|
136
199
|
if (!confirmReset) {
|
|
137
|
-
console.log(
|
|
200
|
+
console.log(import_chalk2.default.gray("Reset cancelled."));
|
|
138
201
|
return;
|
|
139
202
|
}
|
|
140
203
|
const hookSpinner = (0, import_ora3.default)("Removing Claude Code hook...").start();
|
|
141
204
|
const hookResult = removeStopHook();
|
|
142
205
|
if (hookResult.success) {
|
|
143
|
-
hookSpinner.succeed(
|
|
206
|
+
hookSpinner.succeed(import_chalk2.default.green("Hook removed"));
|
|
144
207
|
} else {
|
|
145
|
-
hookSpinner.warn(
|
|
208
|
+
hookSpinner.warn(import_chalk2.default.yellow(`Could not remove hook: ${hookResult.message}`));
|
|
146
209
|
}
|
|
147
210
|
const scriptSpinner = (0, import_ora3.default)("Removing sync script...").start();
|
|
148
211
|
try {
|
|
149
212
|
removeSyncScript();
|
|
150
|
-
scriptSpinner.succeed(
|
|
213
|
+
scriptSpinner.succeed(import_chalk2.default.green("Sync script removed"));
|
|
151
214
|
} catch {
|
|
152
|
-
scriptSpinner.warn(
|
|
215
|
+
scriptSpinner.warn(import_chalk2.default.yellow("Could not remove sync script"));
|
|
153
216
|
}
|
|
154
217
|
const { deleteAccount } = await import_inquirer.default.prompt([
|
|
155
218
|
{
|
|
156
219
|
type: "confirm",
|
|
157
220
|
name: "deleteAccount",
|
|
158
|
-
message:
|
|
221
|
+
message: import_chalk2.default.red("Do you also want to delete your account from the leaderboard? (This cannot be undone)"),
|
|
159
222
|
default: false
|
|
160
223
|
}
|
|
161
224
|
]);
|
|
162
225
|
if (deleteAccount) {
|
|
163
|
-
console.log(
|
|
164
|
-
console.log(
|
|
226
|
+
console.log(import_chalk2.default.yellow("\nAccount deletion is not yet implemented."));
|
|
227
|
+
console.log(import_chalk2.default.gray("Please contact support to delete your account."));
|
|
165
228
|
}
|
|
166
229
|
const configSpinner = (0, import_ora3.default)("Resetting local configuration...").start();
|
|
167
230
|
resetConfig();
|
|
168
|
-
configSpinner.succeed(
|
|
231
|
+
configSpinner.succeed(import_chalk2.default.green("Local configuration reset"));
|
|
169
232
|
console.log();
|
|
170
|
-
console.log(
|
|
233
|
+
console.log(import_chalk2.default.green.bold("\u2705 Reset complete!"));
|
|
171
234
|
console.log();
|
|
172
|
-
console.log(
|
|
173
|
-
console.log(
|
|
235
|
+
console.log(import_chalk2.default.gray("Your usage will no longer be tracked."));
|
|
236
|
+
console.log(import_chalk2.default.gray("Run `npx ccgather` to set up again."));
|
|
174
237
|
console.log();
|
|
175
238
|
}
|
|
176
|
-
var
|
|
239
|
+
var import_chalk2, import_ora3, fs4, path4, os4, import_inquirer;
|
|
177
240
|
var init_reset = __esm({
|
|
178
241
|
"src/commands/reset.ts"() {
|
|
179
242
|
"use strict";
|
|
180
|
-
|
|
243
|
+
import_chalk2 = __toESM(require("chalk"));
|
|
181
244
|
import_ora3 = __toESM(require("ora"));
|
|
182
245
|
fs4 = __toESM(require("fs"));
|
|
183
246
|
path4 = __toESM(require("path"));
|
|
@@ -187,11 +250,261 @@ var init_reset = __esm({
|
|
|
187
250
|
}
|
|
188
251
|
});
|
|
189
252
|
|
|
253
|
+
// src/lib/claude.ts
|
|
254
|
+
function getClaudeConfigDir() {
|
|
255
|
+
const platform3 = os6.platform();
|
|
256
|
+
if (platform3 === "win32") {
|
|
257
|
+
return path6.join(os6.homedir(), "AppData", "Roaming", "claude-code");
|
|
258
|
+
} else if (platform3 === "darwin") {
|
|
259
|
+
return path6.join(os6.homedir(), "Library", "Application Support", "claude-code");
|
|
260
|
+
} else {
|
|
261
|
+
return path6.join(os6.homedir(), ".config", "claude-code");
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
function getUsageFilePaths() {
|
|
265
|
+
const configDir = getClaudeConfigDir();
|
|
266
|
+
return [
|
|
267
|
+
path6.join(configDir, "usage.json"),
|
|
268
|
+
path6.join(configDir, "stats.json"),
|
|
269
|
+
path6.join(configDir, "data", "usage.json"),
|
|
270
|
+
path6.join(os6.homedir(), ".claude", "usage.json"),
|
|
271
|
+
path6.join(os6.homedir(), ".claude-code", "usage.json")
|
|
272
|
+
];
|
|
273
|
+
}
|
|
274
|
+
function readClaudeUsage() {
|
|
275
|
+
const possiblePaths = getUsageFilePaths();
|
|
276
|
+
for (const filePath of possiblePaths) {
|
|
277
|
+
try {
|
|
278
|
+
if (fs6.existsSync(filePath)) {
|
|
279
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
280
|
+
const data = JSON.parse(content);
|
|
281
|
+
if (data.usage) {
|
|
282
|
+
return {
|
|
283
|
+
totalTokens: data.usage.total_tokens || 0,
|
|
284
|
+
totalSpent: data.usage.total_cost || 0,
|
|
285
|
+
modelBreakdown: data.usage.by_model || {},
|
|
286
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
if (data.sessions && data.sessions.length > 0) {
|
|
290
|
+
const breakdown = {};
|
|
291
|
+
let totalTokens = 0;
|
|
292
|
+
let totalSpent = 0;
|
|
293
|
+
for (const session of data.sessions) {
|
|
294
|
+
totalTokens += session.tokens_used || 0;
|
|
295
|
+
totalSpent += session.cost || 0;
|
|
296
|
+
breakdown[session.model] = (breakdown[session.model] || 0) + session.tokens_used;
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
totalTokens,
|
|
300
|
+
totalSpent,
|
|
301
|
+
modelBreakdown: breakdown,
|
|
302
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
} catch (error2) {
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
function isClaudeCodeInstalled() {
|
|
312
|
+
const configDir = getClaudeConfigDir();
|
|
313
|
+
return fs6.existsSync(configDir);
|
|
314
|
+
}
|
|
315
|
+
function getMockUsageData() {
|
|
316
|
+
return {
|
|
317
|
+
totalTokens: Math.floor(Math.random() * 1e6) + 1e4,
|
|
318
|
+
totalSpent: Math.random() * 50 + 5,
|
|
319
|
+
modelBreakdown: {
|
|
320
|
+
"claude-3-5-sonnet-20241022": Math.floor(Math.random() * 5e5),
|
|
321
|
+
"claude-3-opus-20240229": Math.floor(Math.random() * 1e5),
|
|
322
|
+
"claude-3-haiku-20240307": Math.floor(Math.random() * 2e5)
|
|
323
|
+
},
|
|
324
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
var fs6, path6, os6;
|
|
328
|
+
var init_claude = __esm({
|
|
329
|
+
"src/lib/claude.ts"() {
|
|
330
|
+
"use strict";
|
|
331
|
+
fs6 = __toESM(require("fs"));
|
|
332
|
+
path6 = __toESM(require("path"));
|
|
333
|
+
os6 = __toESM(require("os"));
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
// src/commands/sync.ts
|
|
338
|
+
var sync_exports = {};
|
|
339
|
+
__export(sync_exports, {
|
|
340
|
+
sync: () => sync
|
|
341
|
+
});
|
|
342
|
+
function formatNumber2(num) {
|
|
343
|
+
if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
|
|
344
|
+
if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`;
|
|
345
|
+
return num.toString();
|
|
346
|
+
}
|
|
347
|
+
async function sync(options) {
|
|
348
|
+
const config = getConfig();
|
|
349
|
+
console.log(import_chalk4.default.bold("\n\u{1F504} CCgather Sync\n"));
|
|
350
|
+
if (!isAuthenticated()) {
|
|
351
|
+
console.log(import_chalk4.default.red("Not authenticated."));
|
|
352
|
+
console.log(import_chalk4.default.gray("Run: npx ccgather auth\n"));
|
|
353
|
+
process.exit(1);
|
|
354
|
+
}
|
|
355
|
+
if (!isClaudeCodeInstalled()) {
|
|
356
|
+
console.log(import_chalk4.default.yellow("\u26A0\uFE0F Claude Code installation not detected."));
|
|
357
|
+
console.log(import_chalk4.default.gray("Make sure Claude Code is installed and has been used at least once.\n"));
|
|
358
|
+
if (process.env.CCGATHER_DEMO === "true") {
|
|
359
|
+
console.log(import_chalk4.default.gray("Demo mode: Using mock data..."));
|
|
360
|
+
} else {
|
|
361
|
+
process.exit(1);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
const lastSync = config.get("lastSync");
|
|
365
|
+
if (lastSync && !options.force) {
|
|
366
|
+
const lastSyncDate = new Date(lastSync);
|
|
367
|
+
const minInterval = 5 * 60 * 1e3;
|
|
368
|
+
const timeSinceSync = Date.now() - lastSyncDate.getTime();
|
|
369
|
+
if (timeSinceSync < minInterval) {
|
|
370
|
+
const remaining = Math.ceil((minInterval - timeSinceSync) / 1e3 / 60);
|
|
371
|
+
console.log(import_chalk4.default.yellow(`\u23F3 Please wait ${remaining} minutes before syncing again.`));
|
|
372
|
+
console.log(import_chalk4.default.gray("Use --force to override.\n"));
|
|
373
|
+
process.exit(0);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
const spinner = (0, import_ora6.default)("Reading Claude Code usage data...").start();
|
|
377
|
+
let usageData = readClaudeUsage();
|
|
378
|
+
if (!usageData && process.env.CCGATHER_DEMO === "true") {
|
|
379
|
+
usageData = getMockUsageData();
|
|
380
|
+
}
|
|
381
|
+
if (!usageData) {
|
|
382
|
+
spinner.fail(import_chalk4.default.red("Failed to read usage data"));
|
|
383
|
+
console.log(import_chalk4.default.gray("\nPossible reasons:"));
|
|
384
|
+
console.log(import_chalk4.default.gray(" - Claude Code has not been used yet"));
|
|
385
|
+
console.log(import_chalk4.default.gray(" - Usage data file is missing or corrupted"));
|
|
386
|
+
console.log(import_chalk4.default.gray(" - Insufficient permissions to read the file\n"));
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
spinner.text = "Syncing to CCgather...";
|
|
390
|
+
const result = await syncUsage({
|
|
391
|
+
totalTokens: usageData.totalTokens,
|
|
392
|
+
totalSpent: usageData.totalSpent,
|
|
393
|
+
modelBreakdown: usageData.modelBreakdown,
|
|
394
|
+
timestamp: usageData.lastUpdated
|
|
395
|
+
});
|
|
396
|
+
if (!result.success) {
|
|
397
|
+
spinner.fail(import_chalk4.default.red("Sync failed"));
|
|
398
|
+
console.log(import_chalk4.default.red(`Error: ${result.error}
|
|
399
|
+
`));
|
|
400
|
+
process.exit(1);
|
|
401
|
+
}
|
|
402
|
+
config.set("lastSync", (/* @__PURE__ */ new Date()).toISOString());
|
|
403
|
+
spinner.succeed(import_chalk4.default.green("Sync complete!"));
|
|
404
|
+
console.log("\n" + import_chalk4.default.gray("\u2500".repeat(40)));
|
|
405
|
+
console.log(import_chalk4.default.bold("\u{1F4CA} Your Stats"));
|
|
406
|
+
console.log(import_chalk4.default.gray("\u2500".repeat(40)));
|
|
407
|
+
console.log(` ${import_chalk4.default.gray("Tokens:")} ${import_chalk4.default.white(formatNumber2(usageData.totalTokens))}`);
|
|
408
|
+
console.log(` ${import_chalk4.default.gray("Spent:")} ${import_chalk4.default.green("$" + usageData.totalSpent.toFixed(2))}`);
|
|
409
|
+
console.log(` ${import_chalk4.default.gray("Rank:")} ${import_chalk4.default.yellow("#" + result.data?.rank)}`);
|
|
410
|
+
if (options.verbose) {
|
|
411
|
+
console.log("\n" + import_chalk4.default.gray("Model Breakdown:"));
|
|
412
|
+
for (const [model, tokens] of Object.entries(usageData.modelBreakdown)) {
|
|
413
|
+
const shortModel = model.replace("claude-", "").replace(/-\d+$/, "");
|
|
414
|
+
console.log(` ${import_chalk4.default.gray(shortModel + ":")} ${formatNumber2(tokens)}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
console.log(import_chalk4.default.gray("\u2500".repeat(40)));
|
|
418
|
+
console.log(import_chalk4.default.gray("\nView full leaderboard: https://ccgather.dev/leaderboard\n"));
|
|
419
|
+
}
|
|
420
|
+
var import_chalk4, import_ora6;
|
|
421
|
+
var init_sync = __esm({
|
|
422
|
+
"src/commands/sync.ts"() {
|
|
423
|
+
"use strict";
|
|
424
|
+
import_chalk4 = __toESM(require("chalk"));
|
|
425
|
+
import_ora6 = __toESM(require("ora"));
|
|
426
|
+
init_config();
|
|
427
|
+
init_api();
|
|
428
|
+
init_claude();
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// src/commands/auth.ts
|
|
433
|
+
var auth_exports = {};
|
|
434
|
+
__export(auth_exports, {
|
|
435
|
+
auth: () => auth
|
|
436
|
+
});
|
|
437
|
+
async function auth(options) {
|
|
438
|
+
const config = getConfig();
|
|
439
|
+
console.log(import_chalk5.default.bold("\n\u{1F310} CCgather Authentication\n"));
|
|
440
|
+
const existingToken = config.get("apiToken");
|
|
441
|
+
if (existingToken) {
|
|
442
|
+
const { overwrite } = await import_inquirer2.default.prompt([
|
|
443
|
+
{
|
|
444
|
+
type: "confirm",
|
|
445
|
+
name: "overwrite",
|
|
446
|
+
message: "You are already authenticated. Do you want to re-authenticate?",
|
|
447
|
+
default: false
|
|
448
|
+
}
|
|
449
|
+
]);
|
|
450
|
+
if (!overwrite) {
|
|
451
|
+
console.log(import_chalk5.default.gray("Authentication cancelled."));
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
let token = options.token;
|
|
456
|
+
if (!token) {
|
|
457
|
+
console.log(import_chalk5.default.gray("Get your API token from: https://ccgather.dev/settings/api\n"));
|
|
458
|
+
const answers = await import_inquirer2.default.prompt([
|
|
459
|
+
{
|
|
460
|
+
type: "password",
|
|
461
|
+
name: "token",
|
|
462
|
+
message: "Enter your API token:",
|
|
463
|
+
mask: "*",
|
|
464
|
+
validate: (input) => {
|
|
465
|
+
if (!input || input.length < 10) {
|
|
466
|
+
return "Please enter a valid API token";
|
|
467
|
+
}
|
|
468
|
+
return true;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
]);
|
|
472
|
+
token = answers.token;
|
|
473
|
+
}
|
|
474
|
+
const spinner = (0, import_ora7.default)("Verifying token...").start();
|
|
475
|
+
const result = await verifyToken(token);
|
|
476
|
+
if (!result.success) {
|
|
477
|
+
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
478
|
+
console.log(import_chalk5.default.red(`Error: ${result.error}`));
|
|
479
|
+
console.log(import_chalk5.default.gray("\nMake sure your token is correct and try again."));
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
config.set("apiToken", token);
|
|
483
|
+
config.set("userId", result.data?.userId);
|
|
484
|
+
spinner.succeed(import_chalk5.default.green("Authentication successful!"));
|
|
485
|
+
console.log(import_chalk5.default.gray(`
|
|
486
|
+
Welcome, ${import_chalk5.default.white(result.data?.username)}!`));
|
|
487
|
+
console.log(import_chalk5.default.gray("\nNext step: Sync your usage data:"));
|
|
488
|
+
console.log(import_chalk5.default.cyan(" npx ccgather sync\n"));
|
|
489
|
+
}
|
|
490
|
+
var import_chalk5, import_ora7, import_inquirer2;
|
|
491
|
+
var init_auth = __esm({
|
|
492
|
+
"src/commands/auth.ts"() {
|
|
493
|
+
"use strict";
|
|
494
|
+
import_chalk5 = __toESM(require("chalk"));
|
|
495
|
+
import_ora7 = __toESM(require("ora"));
|
|
496
|
+
import_inquirer2 = __toESM(require("inquirer"));
|
|
497
|
+
init_config();
|
|
498
|
+
init_api();
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
|
|
190
502
|
// src/index.ts
|
|
191
503
|
var import_commander = require("commander");
|
|
504
|
+
var import_inquirer3 = __toESM(require("inquirer"));
|
|
505
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
192
506
|
|
|
193
507
|
// src/commands/submit.ts
|
|
194
|
-
var import_chalk = __toESM(require("chalk"));
|
|
195
508
|
var import_ora = __toESM(require("ora"));
|
|
196
509
|
var fs3 = __toESM(require("fs"));
|
|
197
510
|
var path3 = __toESM(require("path"));
|
|
@@ -248,7 +561,7 @@ function readCredentials() {
|
|
|
248
561
|
ccplan,
|
|
249
562
|
rateLimitTier
|
|
250
563
|
};
|
|
251
|
-
} catch (
|
|
564
|
+
} catch (error2) {
|
|
252
565
|
return defaultData;
|
|
253
566
|
}
|
|
254
567
|
}
|
|
@@ -468,6 +781,173 @@ function scanAndSave() {
|
|
|
468
781
|
return data;
|
|
469
782
|
}
|
|
470
783
|
|
|
784
|
+
// src/lib/ui.ts
|
|
785
|
+
var import_chalk = __toESM(require("chalk"));
|
|
786
|
+
var colors = {
|
|
787
|
+
primary: import_chalk.default.hex("#DA7756"),
|
|
788
|
+
// Claude coral
|
|
789
|
+
secondary: import_chalk.default.hex("#F7931E"),
|
|
790
|
+
// Orange accent
|
|
791
|
+
success: import_chalk.default.hex("#22C55E"),
|
|
792
|
+
// Green
|
|
793
|
+
warning: import_chalk.default.hex("#F59E0B"),
|
|
794
|
+
// Amber
|
|
795
|
+
error: import_chalk.default.hex("#EF4444"),
|
|
796
|
+
// Red
|
|
797
|
+
muted: import_chalk.default.hex("#71717A"),
|
|
798
|
+
// Gray
|
|
799
|
+
dim: import_chalk.default.hex("#52525B"),
|
|
800
|
+
// Dark gray
|
|
801
|
+
white: import_chalk.default.white,
|
|
802
|
+
cyan: import_chalk.default.cyan,
|
|
803
|
+
// CCplan colors
|
|
804
|
+
max: import_chalk.default.hex("#F59E0B"),
|
|
805
|
+
// Gold
|
|
806
|
+
pro: import_chalk.default.hex("#3B82F6"),
|
|
807
|
+
// Blue
|
|
808
|
+
team: import_chalk.default.hex("#8B5CF6"),
|
|
809
|
+
// Purple
|
|
810
|
+
free: import_chalk.default.hex("#6B7280")
|
|
811
|
+
// Gray
|
|
812
|
+
};
|
|
813
|
+
var LOGO = `
|
|
814
|
+
${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")}
|
|
815
|
+
${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")}
|
|
816
|
+
${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")}
|
|
817
|
+
${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")}
|
|
818
|
+
${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")}
|
|
819
|
+
${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")}
|
|
820
|
+
`;
|
|
821
|
+
var LOGO_COMPACT = `
|
|
822
|
+
${colors.primary("CC")}${colors.secondary("gather")} ${colors.muted("- Where Claude Code Developers Gather")}
|
|
823
|
+
`;
|
|
824
|
+
var TAGLINE = colors.muted(" Where Claude Code Developers Gather");
|
|
825
|
+
var SLOGAN = colors.dim(" Gather. Compete. Rise.");
|
|
826
|
+
function getVersionLine(version) {
|
|
827
|
+
return colors.dim(` v${version} \u2022 ccgather.dev`);
|
|
828
|
+
}
|
|
829
|
+
var box = {
|
|
830
|
+
topLeft: "\u250C",
|
|
831
|
+
topRight: "\u2510",
|
|
832
|
+
bottomLeft: "\u2514",
|
|
833
|
+
bottomRight: "\u2518",
|
|
834
|
+
horizontal: "\u2500",
|
|
835
|
+
vertical: "\u2502",
|
|
836
|
+
leftT: "\u251C",
|
|
837
|
+
rightT: "\u2524"
|
|
838
|
+
};
|
|
839
|
+
function createBox(lines, width = 47) {
|
|
840
|
+
const paddedLines = lines.map((line) => {
|
|
841
|
+
const visibleLength = stripAnsi(line).length;
|
|
842
|
+
const padding = width - 2 - visibleLength;
|
|
843
|
+
return `${box.vertical} ${line}${" ".repeat(Math.max(0, padding))} ${box.vertical}`;
|
|
844
|
+
});
|
|
845
|
+
const top = colors.dim(` ${box.topLeft}${box.horizontal.repeat(width)}${box.topRight}`);
|
|
846
|
+
const bottom = colors.dim(` ${box.bottomLeft}${box.horizontal.repeat(width)}${box.bottomRight}`);
|
|
847
|
+
return [top, ...paddedLines.map((l) => colors.dim(" ") + l), bottom].join("\n");
|
|
848
|
+
}
|
|
849
|
+
function stripAnsi(str) {
|
|
850
|
+
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
851
|
+
}
|
|
852
|
+
function header(title, icon = "") {
|
|
853
|
+
const iconPart = icon ? `${icon} ` : "";
|
|
854
|
+
return `
|
|
855
|
+
${colors.primary("\u2501".repeat(50))}
|
|
856
|
+
${iconPart}${colors.white.bold(title)}
|
|
857
|
+
${colors.primary("\u2501".repeat(50))}`;
|
|
858
|
+
}
|
|
859
|
+
function formatNumber(num) {
|
|
860
|
+
if (num >= 1e9) return `${(num / 1e9).toFixed(2)}B`;
|
|
861
|
+
if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
|
|
862
|
+
if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`;
|
|
863
|
+
return num.toLocaleString();
|
|
864
|
+
}
|
|
865
|
+
function formatCost(cost) {
|
|
866
|
+
return `$${cost.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
867
|
+
}
|
|
868
|
+
function getRankMedal(rank) {
|
|
869
|
+
if (rank === 1) return "\u{1F947}";
|
|
870
|
+
if (rank === 2) return "\u{1F948}";
|
|
871
|
+
if (rank === 3) return "\u{1F949}";
|
|
872
|
+
if (rank <= 10) return "\u{1F3C5}";
|
|
873
|
+
if (rank <= 100) return "\u{1F396}\uFE0F";
|
|
874
|
+
return "\u{1F4CA}";
|
|
875
|
+
}
|
|
876
|
+
function getCCplanBadge(ccplan) {
|
|
877
|
+
if (!ccplan) return "";
|
|
878
|
+
const badges = {
|
|
879
|
+
max: `${colors.max("\u{1F947} MAX")}`,
|
|
880
|
+
pro: `${colors.pro("\u{1F948} PRO")}`,
|
|
881
|
+
team: `${colors.team("\u{1F3E2} TEAM")}`,
|
|
882
|
+
free: `${colors.free("\u2B50 FREE")}`
|
|
883
|
+
};
|
|
884
|
+
return badges[ccplan.toLowerCase()] || "";
|
|
885
|
+
}
|
|
886
|
+
function getLevelInfo(tokens) {
|
|
887
|
+
const levels = [
|
|
888
|
+
{ min: 0, level: 1, name: "Novice", icon: "\u{1F331}", color: colors.dim },
|
|
889
|
+
{ min: 1e5, level: 2, name: "Apprentice", icon: "\u{1F4DA}", color: colors.muted },
|
|
890
|
+
{ min: 5e5, level: 3, name: "Journeyman", icon: "\u26A1", color: colors.cyan },
|
|
891
|
+
{ min: 1e6, level: 4, name: "Expert", icon: "\u{1F48E}", color: colors.pro },
|
|
892
|
+
{ min: 5e6, level: 5, name: "Master", icon: "\u{1F525}", color: colors.warning },
|
|
893
|
+
{ min: 1e7, level: 6, name: "Grandmaster", icon: "\u{1F451}", color: colors.max },
|
|
894
|
+
{ min: 5e7, level: 7, name: "Legend", icon: "\u{1F31F}", color: colors.primary },
|
|
895
|
+
{ min: 1e8, level: 8, name: "Mythic", icon: "\u{1F3C6}", color: colors.secondary }
|
|
896
|
+
];
|
|
897
|
+
for (let i = levels.length - 1; i >= 0; i--) {
|
|
898
|
+
if (tokens >= levels[i].min) {
|
|
899
|
+
return levels[i];
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
return levels[0];
|
|
903
|
+
}
|
|
904
|
+
function createWelcomeBox(user) {
|
|
905
|
+
const levelInfo = user.level && user.levelName && user.levelIcon ? `${user.levelIcon} Level ${user.level} \u2022 ${user.levelName}` : "";
|
|
906
|
+
const ccplanBadge = user.ccplan ? getCCplanBadge(user.ccplan) : "";
|
|
907
|
+
const lines = [
|
|
908
|
+
`\u{1F44B} ${colors.white.bold(`Welcome back, ${user.username}!`)}`
|
|
909
|
+
];
|
|
910
|
+
if (levelInfo || ccplanBadge) {
|
|
911
|
+
lines.push(`${levelInfo}${ccplanBadge ? ` ${ccplanBadge}` : ""}`);
|
|
912
|
+
}
|
|
913
|
+
if (user.globalRank) {
|
|
914
|
+
lines.push(`\u{1F30D} Global Rank: ${colors.primary(`#${user.globalRank}`)}`);
|
|
915
|
+
}
|
|
916
|
+
if (user.countryRank && user.countryCode) {
|
|
917
|
+
const flag = countryCodeToFlag(user.countryCode);
|
|
918
|
+
lines.push(`${flag} Country Rank: ${colors.primary(`#${user.countryRank}`)}`);
|
|
919
|
+
}
|
|
920
|
+
return createBox(lines);
|
|
921
|
+
}
|
|
922
|
+
function countryCodeToFlag(countryCode) {
|
|
923
|
+
if (!countryCode || countryCode.length !== 2) return "\u{1F310}";
|
|
924
|
+
const codePoints = countryCode.toUpperCase().split("").map((char) => 127462 + char.charCodeAt(0) - 65);
|
|
925
|
+
return String.fromCodePoint(...codePoints);
|
|
926
|
+
}
|
|
927
|
+
function success(message) {
|
|
928
|
+
return `${colors.success("\u2713")} ${message}`;
|
|
929
|
+
}
|
|
930
|
+
function error(message) {
|
|
931
|
+
return `${colors.error("\u2717")} ${message}`;
|
|
932
|
+
}
|
|
933
|
+
function printHeader(version) {
|
|
934
|
+
console.log(LOGO);
|
|
935
|
+
console.log(TAGLINE);
|
|
936
|
+
console.log(SLOGAN);
|
|
937
|
+
console.log();
|
|
938
|
+
console.log(getVersionLine(version));
|
|
939
|
+
console.log();
|
|
940
|
+
}
|
|
941
|
+
function printCompactHeader(version) {
|
|
942
|
+
console.log();
|
|
943
|
+
console.log(LOGO_COMPACT);
|
|
944
|
+
console.log(colors.dim(` v${version}`));
|
|
945
|
+
console.log();
|
|
946
|
+
}
|
|
947
|
+
function link(url) {
|
|
948
|
+
return colors.cyan.underline(url);
|
|
949
|
+
}
|
|
950
|
+
|
|
471
951
|
// src/commands/submit.ts
|
|
472
952
|
function ccgatherToUsageData(data) {
|
|
473
953
|
return {
|
|
@@ -477,7 +957,9 @@ function ccgatherToUsageData(data) {
|
|
|
477
957
|
outputTokens: data.usage.outputTokens,
|
|
478
958
|
cacheReadTokens: data.usage.cacheReadTokens,
|
|
479
959
|
cacheWriteTokens: data.usage.cacheWriteTokens,
|
|
480
|
-
daysTracked: data.stats.daysTracked
|
|
960
|
+
daysTracked: data.stats.daysTracked,
|
|
961
|
+
ccplan: data.account?.ccplan || null,
|
|
962
|
+
rateLimitTier: data.account?.rateLimitTier || null
|
|
481
963
|
};
|
|
482
964
|
}
|
|
483
965
|
function findCcJson() {
|
|
@@ -519,9 +1001,6 @@ function calculateDaysTracked(data) {
|
|
|
519
1001
|
}
|
|
520
1002
|
return 1;
|
|
521
1003
|
}
|
|
522
|
-
function formatNumber(num) {
|
|
523
|
-
return num.toLocaleString();
|
|
524
|
-
}
|
|
525
1004
|
async function detectGitHubUsername() {
|
|
526
1005
|
try {
|
|
527
1006
|
const { execSync } = await import("child_process");
|
|
@@ -557,6 +1036,8 @@ async function submitToServer(username, data) {
|
|
|
557
1036
|
cacheReadTokens: data.cacheReadTokens,
|
|
558
1037
|
cacheWriteTokens: data.cacheWriteTokens,
|
|
559
1038
|
daysTracked: data.daysTracked,
|
|
1039
|
+
ccplan: data.ccplan,
|
|
1040
|
+
rateLimitTier: data.rateLimitTier,
|
|
560
1041
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
561
1042
|
})
|
|
562
1043
|
});
|
|
@@ -565,25 +1046,30 @@ async function submitToServer(username, data) {
|
|
|
565
1046
|
return { success: false, error: errorData.error || `HTTP ${response.status}` };
|
|
566
1047
|
}
|
|
567
1048
|
const result = await response.json();
|
|
568
|
-
return { success: true, profileUrl: result.profileUrl };
|
|
1049
|
+
return { success: true, profileUrl: result.profileUrl, rank: result.rank };
|
|
569
1050
|
} catch (err) {
|
|
570
1051
|
return { success: false, error: err instanceof Error ? err.message : "Unknown error" };
|
|
571
1052
|
}
|
|
572
1053
|
}
|
|
573
1054
|
async function submit(options) {
|
|
574
|
-
|
|
575
|
-
|
|
1055
|
+
printCompactHeader("1.2.1");
|
|
1056
|
+
console.log(header("Submit Usage Data", "\u{1F4E4}"));
|
|
1057
|
+
const spinner = (0, import_ora.default)({
|
|
1058
|
+
text: "Detecting GitHub username...",
|
|
1059
|
+
color: "cyan"
|
|
1060
|
+
}).start();
|
|
576
1061
|
let username = await detectGitHubUsername();
|
|
577
1062
|
spinner.stop();
|
|
578
1063
|
if (username) {
|
|
579
|
-
console.log(
|
|
1064
|
+
console.log(`
|
|
1065
|
+
${colors.muted("Detected:")} ${colors.white(username)}`);
|
|
580
1066
|
}
|
|
581
|
-
const
|
|
582
|
-
const { confirmedUsername } = await
|
|
1067
|
+
const inquirer4 = await import("inquirer");
|
|
1068
|
+
const { confirmedUsername } = await inquirer4.default.prompt([
|
|
583
1069
|
{
|
|
584
1070
|
type: "input",
|
|
585
1071
|
name: "confirmedUsername",
|
|
586
|
-
message: "GitHub username:",
|
|
1072
|
+
message: colors.muted("GitHub username:"),
|
|
587
1073
|
default: username || "",
|
|
588
1074
|
validate: (input) => input.trim().length > 0 || "Username is required"
|
|
589
1075
|
}
|
|
@@ -595,18 +1081,21 @@ async function submit(options) {
|
|
|
595
1081
|
if (ccgatherData) {
|
|
596
1082
|
usageData = ccgatherToUsageData(ccgatherData);
|
|
597
1083
|
dataSource = "ccgather.json";
|
|
598
|
-
console.log(
|
|
599
|
-
|
|
600
|
-
`));
|
|
1084
|
+
console.log(`
|
|
1085
|
+
${success(`Found ${dataSource}`)}`);
|
|
1086
|
+
console.log(` ${colors.dim(`Last scanned: ${new Date(ccgatherData.lastScanned).toLocaleString()}`)}`);
|
|
1087
|
+
if (usageData.ccplan) {
|
|
1088
|
+
console.log(` ${colors.dim("CCplan:")} ${colors.primary(usageData.ccplan.toUpperCase())}`);
|
|
1089
|
+
}
|
|
601
1090
|
}
|
|
602
1091
|
if (!usageData) {
|
|
603
1092
|
const ccJsonPath = findCcJson();
|
|
604
1093
|
if (ccJsonPath) {
|
|
605
|
-
const { useCcJson } = await
|
|
1094
|
+
const { useCcJson } = await inquirer4.default.prompt([
|
|
606
1095
|
{
|
|
607
1096
|
type: "confirm",
|
|
608
1097
|
name: "useCcJson",
|
|
609
|
-
message:
|
|
1098
|
+
message: "Found existing cc.json. Use this file?",
|
|
610
1099
|
default: true
|
|
611
1100
|
}
|
|
612
1101
|
]);
|
|
@@ -617,34 +1106,45 @@ async function submit(options) {
|
|
|
617
1106
|
}
|
|
618
1107
|
}
|
|
619
1108
|
if (!usageData) {
|
|
620
|
-
const parseSpinner = (0, import_ora.default)(
|
|
1109
|
+
const parseSpinner = (0, import_ora.default)({
|
|
1110
|
+
text: "Scanning Claude Code usage data...",
|
|
1111
|
+
color: "cyan"
|
|
1112
|
+
}).start();
|
|
621
1113
|
const scannedData = scanAndSave();
|
|
622
1114
|
parseSpinner.stop();
|
|
623
1115
|
if (scannedData) {
|
|
624
1116
|
usageData = ccgatherToUsageData(scannedData);
|
|
625
1117
|
dataSource = "Claude Code logs";
|
|
626
|
-
console.log(
|
|
627
|
-
|
|
628
|
-
`));
|
|
1118
|
+
console.log(`
|
|
1119
|
+
${success("Scanned and saved to ccgather.json")}`);
|
|
1120
|
+
console.log(` ${colors.dim(`Path: ${getCCGatherJsonPath()}`)}`);
|
|
629
1121
|
}
|
|
630
1122
|
}
|
|
631
1123
|
if (!usageData) {
|
|
632
|
-
console.log(
|
|
633
|
-
|
|
634
|
-
console.log(
|
|
1124
|
+
console.log(`
|
|
1125
|
+
${error("No usage data found.")}`);
|
|
1126
|
+
console.log(` ${colors.muted("Make sure you have used Claude Code.")}`);
|
|
1127
|
+
console.log(` ${colors.muted("Run:")} ${colors.white("npx ccgather scan")}
|
|
1128
|
+
`);
|
|
635
1129
|
process.exit(1);
|
|
636
1130
|
}
|
|
637
1131
|
if (dataSource && dataSource !== "Claude Code logs") {
|
|
638
|
-
console.log(
|
|
639
|
-
`));
|
|
1132
|
+
console.log(`
|
|
1133
|
+
${success(`Using ${dataSource}`)}`);
|
|
640
1134
|
}
|
|
641
|
-
console.log(
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1135
|
+
console.log();
|
|
1136
|
+
const summaryLines = [
|
|
1137
|
+
`${colors.muted("Total Cost")} ${colors.success(formatCost(usageData.totalCost))}`,
|
|
1138
|
+
`${colors.muted("Total Tokens")} ${colors.primary(formatNumber(usageData.totalTokens))}`,
|
|
1139
|
+
`${colors.muted("Days Tracked")} ${colors.warning(usageData.daysTracked.toString())}`
|
|
1140
|
+
];
|
|
1141
|
+
if (usageData.ccplan) {
|
|
1142
|
+
summaryLines.push(`${colors.muted("CCplan")} ${colors.cyan(usageData.ccplan.toUpperCase())}`);
|
|
1143
|
+
}
|
|
1144
|
+
console.log(createBox(summaryLines));
|
|
645
1145
|
console.log();
|
|
646
1146
|
if (!options.yes) {
|
|
647
|
-
const { confirmSubmit } = await
|
|
1147
|
+
const { confirmSubmit } = await inquirer4.default.prompt([
|
|
648
1148
|
{
|
|
649
1149
|
type: "confirm",
|
|
650
1150
|
name: "confirmSubmit",
|
|
@@ -653,106 +1153,70 @@ async function submit(options) {
|
|
|
653
1153
|
}
|
|
654
1154
|
]);
|
|
655
1155
|
if (!confirmSubmit) {
|
|
656
|
-
console.log(
|
|
1156
|
+
console.log(`
|
|
1157
|
+
${colors.muted("Submission cancelled.")}
|
|
1158
|
+
`);
|
|
657
1159
|
return;
|
|
658
1160
|
}
|
|
659
1161
|
}
|
|
660
|
-
const submitSpinner = (0, import_ora.default)(
|
|
1162
|
+
const submitSpinner = (0, import_ora.default)({
|
|
1163
|
+
text: "Submitting to CCgather...",
|
|
1164
|
+
color: "cyan"
|
|
1165
|
+
}).start();
|
|
661
1166
|
const result = await submitToServer(username, usageData);
|
|
662
1167
|
if (result.success) {
|
|
663
|
-
submitSpinner.succeed(
|
|
1168
|
+
submitSpinner.succeed(colors.success("Successfully submitted to CCgather!"));
|
|
1169
|
+
console.log();
|
|
1170
|
+
const successLines = [
|
|
1171
|
+
`${colors.success("\u2713")} ${colors.white.bold("Submission Complete!")}`,
|
|
1172
|
+
"",
|
|
1173
|
+
`${colors.muted("Profile:")} ${link(result.profileUrl || `https://ccgather.dev/u/${username}`)}`
|
|
1174
|
+
];
|
|
1175
|
+
if (result.rank) {
|
|
1176
|
+
successLines.push(`${colors.muted("Rank:")} ${colors.warning(`#${result.rank}`)}`);
|
|
1177
|
+
}
|
|
1178
|
+
console.log(createBox(successLines));
|
|
664
1179
|
console.log();
|
|
665
|
-
console.log(
|
|
1180
|
+
console.log(` ${colors.dim("View leaderboard:")} ${link("https://ccgather.dev/leaderboard")}`);
|
|
666
1181
|
console.log();
|
|
667
|
-
console.log(import_chalk.default.bold("Done! \u{1F389}"));
|
|
668
1182
|
} else {
|
|
669
|
-
submitSpinner.fail(
|
|
670
|
-
console.log(
|
|
1183
|
+
submitSpinner.fail(colors.error("Failed to submit"));
|
|
1184
|
+
console.log(`
|
|
1185
|
+
${error(result.error || "Unknown error")}
|
|
1186
|
+
`);
|
|
671
1187
|
process.exit(1);
|
|
672
1188
|
}
|
|
673
|
-
console.log();
|
|
674
1189
|
}
|
|
675
1190
|
|
|
676
1191
|
// src/commands/status.ts
|
|
677
|
-
var import_chalk2 = __toESM(require("chalk"));
|
|
678
1192
|
var import_ora2 = __toESM(require("ora"));
|
|
679
1193
|
init_config();
|
|
680
|
-
|
|
681
|
-
// src/lib/api.ts
|
|
682
|
-
init_config();
|
|
683
|
-
async function fetchApi(endpoint, options = {}) {
|
|
684
|
-
const config = getConfig();
|
|
685
|
-
const apiToken = config.get("apiToken");
|
|
686
|
-
const apiUrl = getApiUrl();
|
|
687
|
-
if (!apiToken) {
|
|
688
|
-
return { success: false, error: "Not authenticated. Run: npx ccgather auth" };
|
|
689
|
-
}
|
|
690
|
-
try {
|
|
691
|
-
const response = await fetch(`${apiUrl}${endpoint}`, {
|
|
692
|
-
...options,
|
|
693
|
-
headers: {
|
|
694
|
-
"Content-Type": "application/json",
|
|
695
|
-
Authorization: `Bearer ${apiToken}`,
|
|
696
|
-
...options.headers
|
|
697
|
-
}
|
|
698
|
-
});
|
|
699
|
-
const data = await response.json();
|
|
700
|
-
if (!response.ok) {
|
|
701
|
-
return { success: false, error: data.error || `HTTP ${response.status}` };
|
|
702
|
-
}
|
|
703
|
-
return { success: true, data };
|
|
704
|
-
} catch (error) {
|
|
705
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
706
|
-
return { success: false, error: message };
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
async function getStatus() {
|
|
710
|
-
return fetchApi("/cli/status");
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// src/commands/status.ts
|
|
714
|
-
function formatNumber2(num) {
|
|
715
|
-
if (num >= 1e9) return `${(num / 1e9).toFixed(2)}B`;
|
|
716
|
-
if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
|
|
717
|
-
if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`;
|
|
718
|
-
return num.toString();
|
|
719
|
-
}
|
|
720
|
-
function getTierEmoji(tier) {
|
|
721
|
-
const emojis = {
|
|
722
|
-
free: "\u{1F193}",
|
|
723
|
-
pro: "\u2B50",
|
|
724
|
-
team: "\u{1F465}",
|
|
725
|
-
enterprise: "\u{1F3E2}"
|
|
726
|
-
};
|
|
727
|
-
return emojis[tier.toLowerCase()] || "\u{1F3AF}";
|
|
728
|
-
}
|
|
729
|
-
function getRankMedal(rank) {
|
|
730
|
-
if (rank === 1) return "\u{1F947}";
|
|
731
|
-
if (rank === 2) return "\u{1F948}";
|
|
732
|
-
if (rank === 3) return "\u{1F949}";
|
|
733
|
-
if (rank <= 10) return "\u{1F3C5}";
|
|
734
|
-
if (rank <= 100) return "\u{1F396}\uFE0F";
|
|
735
|
-
return "\u{1F4CA}";
|
|
736
|
-
}
|
|
1194
|
+
init_api();
|
|
737
1195
|
async function status(options) {
|
|
738
1196
|
if (!isAuthenticated()) {
|
|
739
1197
|
if (options.json) {
|
|
740
1198
|
console.log(JSON.stringify({ error: "Not authenticated" }));
|
|
741
1199
|
} else {
|
|
742
|
-
console.log(
|
|
743
|
-
|
|
1200
|
+
console.log(`
|
|
1201
|
+
${error("Not authenticated.")}`);
|
|
1202
|
+
console.log(` ${colors.muted("Run:")} ${colors.white("npx ccgather auth")}
|
|
1203
|
+
`);
|
|
744
1204
|
}
|
|
745
1205
|
process.exit(1);
|
|
746
1206
|
}
|
|
747
|
-
const spinner = options.json ? null : (0, import_ora2.default)(
|
|
1207
|
+
const spinner = options.json ? null : (0, import_ora2.default)({
|
|
1208
|
+
text: "Fetching your stats...",
|
|
1209
|
+
color: "cyan"
|
|
1210
|
+
}).start();
|
|
748
1211
|
const result = await getStatus();
|
|
749
1212
|
if (!result.success) {
|
|
750
|
-
if (spinner) spinner.fail(
|
|
1213
|
+
if (spinner) spinner.fail(colors.error("Failed to fetch status"));
|
|
751
1214
|
if (options.json) {
|
|
752
1215
|
console.log(JSON.stringify({ error: result.error }));
|
|
753
1216
|
} else {
|
|
754
|
-
console.log(
|
|
755
|
-
|
|
1217
|
+
console.log(`
|
|
1218
|
+
${error(result.error || "Unknown error")}
|
|
1219
|
+
`);
|
|
756
1220
|
}
|
|
757
1221
|
process.exit(1);
|
|
758
1222
|
}
|
|
@@ -761,35 +1225,42 @@ async function status(options) {
|
|
|
761
1225
|
console.log(JSON.stringify(stats, null, 2));
|
|
762
1226
|
return;
|
|
763
1227
|
}
|
|
764
|
-
spinner?.succeed(
|
|
765
|
-
|
|
766
|
-
console.log(
|
|
767
|
-
|
|
1228
|
+
spinner?.succeed(colors.success("Status retrieved"));
|
|
1229
|
+
printCompactHeader("1.2.1");
|
|
1230
|
+
console.log(header("Your CCgather Stats", "\u{1F4CA}"));
|
|
1231
|
+
const levelInfo = getLevelInfo(stats.totalTokens);
|
|
768
1232
|
const medal = getRankMedal(stats.rank);
|
|
769
|
-
console.log(
|
|
770
|
-
${medal} ${
|
|
771
|
-
console.log(
|
|
772
|
-
console.log(
|
|
773
|
-
console.log(
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1233
|
+
console.log();
|
|
1234
|
+
console.log(` ${medal} ${colors.white.bold(`Rank #${stats.rank}`)}`);
|
|
1235
|
+
console.log(` ${colors.dim(`Top ${stats.percentile.toFixed(1)}% of all users`)}`);
|
|
1236
|
+
console.log();
|
|
1237
|
+
console.log(` ${levelInfo.icon} ${levelInfo.color(`Level ${levelInfo.level} \u2022 ${levelInfo.name}`)}`);
|
|
1238
|
+
if (stats.tier) {
|
|
1239
|
+
const badge = getCCplanBadge(stats.tier);
|
|
1240
|
+
if (badge) {
|
|
1241
|
+
console.log(` ${badge}`);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
console.log();
|
|
1245
|
+
const statsLines = [
|
|
1246
|
+
`${colors.muted("Total Tokens")} ${colors.primary(formatNumber(stats.totalTokens))}`,
|
|
1247
|
+
`${colors.muted("Total Spent")} ${colors.success(formatCost(stats.totalSpent))}`,
|
|
1248
|
+
`${colors.muted("Tier")} ${colors.cyan(stats.tier || "Unknown")}`
|
|
1249
|
+
];
|
|
1250
|
+
console.log(createBox(statsLines));
|
|
782
1251
|
if (stats.badges && stats.badges.length > 0) {
|
|
783
|
-
console.log(
|
|
784
|
-
console.log(
|
|
1252
|
+
console.log();
|
|
1253
|
+
console.log(` ${colors.muted("Badges")}`);
|
|
785
1254
|
console.log(` ${stats.badges.join(" ")}`);
|
|
786
1255
|
}
|
|
787
|
-
console.log(
|
|
788
|
-
console.log(
|
|
1256
|
+
console.log();
|
|
1257
|
+
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1258
|
+
console.log(` ${colors.muted("View leaderboard:")} ${link("https://ccgather.dev/leaderboard")}`);
|
|
1259
|
+
console.log();
|
|
789
1260
|
}
|
|
790
1261
|
|
|
791
1262
|
// src/commands/setup-auto.ts
|
|
792
|
-
var
|
|
1263
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
793
1264
|
var import_ora4 = __toESM(require("ora"));
|
|
794
1265
|
var http = __toESM(require("http"));
|
|
795
1266
|
var fs5 = __toESM(require("fs"));
|
|
@@ -813,8 +1284,8 @@ function createCallbackServer() {
|
|
|
813
1284
|
const token = url.searchParams.get("token");
|
|
814
1285
|
const userId = url.searchParams.get("userId");
|
|
815
1286
|
const username = url.searchParams.get("username");
|
|
816
|
-
const
|
|
817
|
-
if (
|
|
1287
|
+
const error2 = url.searchParams.get("error");
|
|
1288
|
+
if (error2) {
|
|
818
1289
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
819
1290
|
res.end(`
|
|
820
1291
|
<html>
|
|
@@ -822,14 +1293,14 @@ function createCallbackServer() {
|
|
|
822
1293
|
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a1a; color: #fff;">
|
|
823
1294
|
<div style="text-align: center;">
|
|
824
1295
|
<h1 style="color: #ef4444;">\u274C Authentication Failed</h1>
|
|
825
|
-
<p>${
|
|
1296
|
+
<p>${error2}</p>
|
|
826
1297
|
<p style="color: #888;">You can close this window.</p>
|
|
827
1298
|
</div>
|
|
828
1299
|
</body>
|
|
829
1300
|
</html>
|
|
830
1301
|
`);
|
|
831
1302
|
server.close();
|
|
832
|
-
reject(new Error(
|
|
1303
|
+
reject(new Error(error2));
|
|
833
1304
|
return;
|
|
834
1305
|
}
|
|
835
1306
|
if (token && userId && username) {
|
|
@@ -1073,20 +1544,20 @@ function saveSyncScript(apiUrl, apiToken) {
|
|
|
1073
1544
|
}
|
|
1074
1545
|
async function setupAuto(options = {}) {
|
|
1075
1546
|
if (options.manual) {
|
|
1076
|
-
console.log(
|
|
1547
|
+
console.log(import_chalk3.default.bold("\n\u{1F527} Disabling Auto-Sync\n"));
|
|
1077
1548
|
const { reset: reset2 } = await Promise.resolve().then(() => (init_reset(), reset_exports));
|
|
1078
1549
|
await reset2();
|
|
1079
|
-
console.log(
|
|
1550
|
+
console.log(import_chalk3.default.green("\u2713 Auto-sync disabled. Use `npx ccgather` to submit manually."));
|
|
1080
1551
|
return;
|
|
1081
1552
|
}
|
|
1082
|
-
console.log(
|
|
1083
|
-
console.log(
|
|
1084
|
-
console.log(
|
|
1553
|
+
console.log(import_chalk3.default.bold("\n\u26A0\uFE0F Auto-Sync Mode (Optional)\n"));
|
|
1554
|
+
console.log(import_chalk3.default.gray("This will install a hook that automatically syncs"));
|
|
1555
|
+
console.log(import_chalk3.default.gray("your usage data when Claude Code sessions end."));
|
|
1085
1556
|
console.log();
|
|
1086
|
-
console.log(
|
|
1557
|
+
console.log(import_chalk3.default.yellow("Note: Manual submission (`npx ccgather`) is recommended for most users."));
|
|
1087
1558
|
console.log();
|
|
1088
|
-
const
|
|
1089
|
-
const { proceed } = await
|
|
1559
|
+
const inquirer4 = await import("inquirer");
|
|
1560
|
+
const { proceed } = await inquirer4.default.prompt([
|
|
1090
1561
|
{
|
|
1091
1562
|
type: "confirm",
|
|
1092
1563
|
name: "proceed",
|
|
@@ -1095,16 +1566,16 @@ async function setupAuto(options = {}) {
|
|
|
1095
1566
|
}
|
|
1096
1567
|
]);
|
|
1097
1568
|
if (!proceed) {
|
|
1098
|
-
console.log(
|
|
1569
|
+
console.log(import_chalk3.default.gray("\nSetup cancelled. Use `npx ccgather` to submit manually."));
|
|
1099
1570
|
return;
|
|
1100
1571
|
}
|
|
1101
1572
|
const config = getConfig();
|
|
1102
1573
|
const apiUrl = getApiUrl();
|
|
1103
|
-
console.log(
|
|
1574
|
+
console.log(import_chalk3.default.bold("\n\u{1F310} CCgather Setup\n"));
|
|
1104
1575
|
const existingToken = config.get("apiToken");
|
|
1105
1576
|
if (existingToken) {
|
|
1106
|
-
const
|
|
1107
|
-
const { reconfigure } = await
|
|
1577
|
+
const inquirer5 = await import("inquirer");
|
|
1578
|
+
const { reconfigure } = await inquirer5.default.prompt([
|
|
1108
1579
|
{
|
|
1109
1580
|
type: "confirm",
|
|
1110
1581
|
name: "reconfigure",
|
|
@@ -1113,11 +1584,11 @@ async function setupAuto(options = {}) {
|
|
|
1113
1584
|
}
|
|
1114
1585
|
]);
|
|
1115
1586
|
if (!reconfigure) {
|
|
1116
|
-
console.log(
|
|
1587
|
+
console.log(import_chalk3.default.gray("Setup cancelled."));
|
|
1117
1588
|
return;
|
|
1118
1589
|
}
|
|
1119
1590
|
}
|
|
1120
|
-
console.log(
|
|
1591
|
+
console.log(import_chalk3.default.gray("Opening browser for GitHub authentication...\n"));
|
|
1121
1592
|
const callbackUrl = `http://localhost:${CALLBACK_PORT}/callback`;
|
|
1122
1593
|
const baseUrl = apiUrl.replace("/api", "");
|
|
1123
1594
|
const authUrl = `${baseUrl}/cli/auth?callback=${encodeURIComponent(callbackUrl)}`;
|
|
@@ -1125,15 +1596,15 @@ async function setupAuto(options = {}) {
|
|
|
1125
1596
|
try {
|
|
1126
1597
|
await openBrowser(authUrl);
|
|
1127
1598
|
} catch {
|
|
1128
|
-
console.log(
|
|
1129
|
-
console.log(
|
|
1130
|
-
console.log(
|
|
1599
|
+
console.log(import_chalk3.default.yellow("Could not open browser automatically."));
|
|
1600
|
+
console.log(import_chalk3.default.gray("Please open this URL manually:"));
|
|
1601
|
+
console.log(import_chalk3.default.cyan(authUrl));
|
|
1131
1602
|
console.log();
|
|
1132
1603
|
}
|
|
1133
1604
|
const spinner = (0, import_ora4.default)("Waiting for authentication...").start();
|
|
1134
1605
|
try {
|
|
1135
1606
|
const authData = await serverPromise;
|
|
1136
|
-
spinner.succeed(
|
|
1607
|
+
spinner.succeed(import_chalk3.default.green("Authentication successful!"));
|
|
1137
1608
|
config.set("apiToken", authData.token);
|
|
1138
1609
|
config.set("userId", authData.userId);
|
|
1139
1610
|
const hookSpinner = (0, import_ora4.default)("Installing Claude Code hook...").start();
|
|
@@ -1141,137 +1612,329 @@ async function setupAuto(options = {}) {
|
|
|
1141
1612
|
saveSyncScript(apiUrl, authData.token);
|
|
1142
1613
|
const hookResult = installStopHook();
|
|
1143
1614
|
if (hookResult.success) {
|
|
1144
|
-
hookSpinner.succeed(
|
|
1615
|
+
hookSpinner.succeed(import_chalk3.default.green("Hook installed successfully!"));
|
|
1145
1616
|
} else {
|
|
1146
|
-
hookSpinner.fail(
|
|
1147
|
-
console.log(
|
|
1617
|
+
hookSpinner.fail(import_chalk3.default.red("Failed to install hook"));
|
|
1618
|
+
console.log(import_chalk3.default.red(hookResult.message));
|
|
1148
1619
|
}
|
|
1149
1620
|
} catch (err) {
|
|
1150
|
-
hookSpinner.fail(
|
|
1151
|
-
console.log(
|
|
1621
|
+
hookSpinner.fail(import_chalk3.default.red("Failed to install hook"));
|
|
1622
|
+
console.log(import_chalk3.default.red(err instanceof Error ? err.message : "Unknown error"));
|
|
1152
1623
|
}
|
|
1153
1624
|
console.log();
|
|
1154
|
-
console.log(
|
|
1625
|
+
console.log(import_chalk3.default.green.bold("\u2705 Setup complete!"));
|
|
1155
1626
|
console.log();
|
|
1156
|
-
console.log(
|
|
1627
|
+
console.log(import_chalk3.default.gray(`Welcome, ${import_chalk3.default.white(authData.username)}!`));
|
|
1157
1628
|
console.log();
|
|
1158
|
-
console.log(
|
|
1159
|
-
console.log(
|
|
1629
|
+
console.log(import_chalk3.default.gray("Your Claude Code usage will now be automatically synced"));
|
|
1630
|
+
console.log(import_chalk3.default.gray("to the leaderboard when each session ends."));
|
|
1160
1631
|
console.log();
|
|
1161
|
-
console.log(
|
|
1162
|
-
console.log(
|
|
1632
|
+
console.log(import_chalk3.default.gray("View your stats:"));
|
|
1633
|
+
console.log(import_chalk3.default.cyan(" npx ccgather status"));
|
|
1163
1634
|
console.log();
|
|
1164
|
-
console.log(
|
|
1165
|
-
console.log(
|
|
1635
|
+
console.log(import_chalk3.default.gray("View the leaderboard:"));
|
|
1636
|
+
console.log(import_chalk3.default.cyan(" https://ccgather.dev/leaderboard"));
|
|
1166
1637
|
console.log();
|
|
1167
1638
|
} catch (err) {
|
|
1168
|
-
spinner.fail(
|
|
1169
|
-
console.log(
|
|
1639
|
+
spinner.fail(import_chalk3.default.red("Authentication failed"));
|
|
1640
|
+
console.log(import_chalk3.default.red(err instanceof Error ? err.message : "Unknown error"));
|
|
1170
1641
|
process.exit(1);
|
|
1171
1642
|
}
|
|
1172
1643
|
}
|
|
1173
1644
|
|
|
1174
1645
|
// src/commands/scan.ts
|
|
1175
|
-
var import_chalk5 = __toESM(require("chalk"));
|
|
1176
1646
|
var import_ora5 = __toESM(require("ora"));
|
|
1177
|
-
function formatNumber3(num) {
|
|
1178
|
-
if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
|
|
1179
|
-
if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`;
|
|
1180
|
-
return num.toLocaleString();
|
|
1181
|
-
}
|
|
1182
1647
|
function displayResults(data) {
|
|
1183
|
-
const orange = import_chalk5.default.hex("#FF6B35");
|
|
1184
|
-
const green = import_chalk5.default.hex("#10B981");
|
|
1185
|
-
const gray = import_chalk5.default.gray;
|
|
1186
|
-
const white = import_chalk5.default.white;
|
|
1187
|
-
const bold = import_chalk5.default.bold;
|
|
1188
|
-
const cyan = import_chalk5.default.cyan;
|
|
1189
1648
|
console.log();
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
);
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
)
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
);
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
gray(" \u2502") + ` Last Used: ${gray((data.stats.lastUsed || "N/A").padEnd(15))}` + gray(" \u2502")
|
|
1222
|
-
);
|
|
1223
|
-
console.log(gray(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1649
|
+
const usageLines = [
|
|
1650
|
+
`${colors.white.bold("\u{1F4CA} Usage Summary")}`,
|
|
1651
|
+
"",
|
|
1652
|
+
`${colors.muted("Total Tokens")} ${colors.primary(formatNumber(data.usage.totalTokens))}`,
|
|
1653
|
+
`${colors.muted("Total Cost")} ${colors.success(formatCost(data.usage.totalCost))}`,
|
|
1654
|
+
`${colors.muted("Input Tokens")} ${colors.white(formatNumber(data.usage.inputTokens))}`,
|
|
1655
|
+
`${colors.muted("Output Tokens")} ${colors.white(formatNumber(data.usage.outputTokens))}`
|
|
1656
|
+
];
|
|
1657
|
+
if (data.usage.cacheReadTokens > 0 || data.usage.cacheWriteTokens > 0) {
|
|
1658
|
+
usageLines.push(`${colors.muted("Cache Read")} ${colors.dim(formatNumber(data.usage.cacheReadTokens))}`);
|
|
1659
|
+
usageLines.push(`${colors.muted("Cache Write")} ${colors.dim(formatNumber(data.usage.cacheWriteTokens))}`);
|
|
1660
|
+
}
|
|
1661
|
+
console.log(createBox(usageLines));
|
|
1662
|
+
console.log();
|
|
1663
|
+
const statsLines = [
|
|
1664
|
+
`${colors.white.bold("\u{1F4C8} Statistics")}`,
|
|
1665
|
+
"",
|
|
1666
|
+
`${colors.muted("Days Tracked")} ${colors.warning(data.stats.daysTracked.toString())}`,
|
|
1667
|
+
`${colors.muted("Sessions")} ${colors.white(data.stats.sessionsCount.toString())}`,
|
|
1668
|
+
`${colors.muted("First Used")} ${colors.dim(data.stats.firstUsed || "N/A")}`,
|
|
1669
|
+
`${colors.muted("Last Used")} ${colors.dim(data.stats.lastUsed || "N/A")}`
|
|
1670
|
+
];
|
|
1671
|
+
console.log(createBox(statsLines));
|
|
1672
|
+
if (data.account?.ccplan) {
|
|
1673
|
+
console.log();
|
|
1674
|
+
const badge = getCCplanBadge(data.account.ccplan);
|
|
1675
|
+
console.log(` ${colors.muted("Account Plan:")} ${badge}`);
|
|
1676
|
+
if (data.account.rateLimitTier) {
|
|
1677
|
+
console.log(` ${colors.muted("Rate Limit:")} ${colors.dim(data.account.rateLimitTier)}`);
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1224
1680
|
if (Object.keys(data.models).length > 0) {
|
|
1225
1681
|
console.log();
|
|
1226
|
-
console.log(
|
|
1227
|
-
|
|
1682
|
+
console.log(` ${colors.white.bold("\u{1F916} Model Breakdown")}`);
|
|
1683
|
+
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1684
|
+
const sortedModels = Object.entries(data.models).sort(([, a], [, b]) => b - a).slice(0, 5);
|
|
1685
|
+
for (const [model, tokens] of sortedModels) {
|
|
1228
1686
|
const shortModel = model.replace("claude-", "").substring(0, 20);
|
|
1229
|
-
console.log(
|
|
1687
|
+
console.log(` ${colors.dim("\u2022")} ${colors.white(shortModel.padEnd(22))} ${colors.primary(formatNumber(tokens))}`);
|
|
1688
|
+
}
|
|
1689
|
+
if (Object.keys(data.models).length > 5) {
|
|
1690
|
+
console.log(` ${colors.dim(`... and ${Object.keys(data.models).length - 5} more models`)}`);
|
|
1230
1691
|
}
|
|
1231
1692
|
}
|
|
1232
1693
|
if (data.projects && Object.keys(data.projects).length > 0) {
|
|
1233
1694
|
console.log();
|
|
1234
|
-
console.log(
|
|
1695
|
+
console.log(` ${colors.white.bold("\u{1F4C1} Top Projects")}`);
|
|
1696
|
+
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1235
1697
|
const sortedProjects = Object.entries(data.projects).sort(([, a], [, b]) => b.tokens - a.tokens).slice(0, 5);
|
|
1236
1698
|
for (const [name, stats] of sortedProjects) {
|
|
1237
|
-
const displayName = name.length >
|
|
1699
|
+
const displayName = name.length > 20 ? name.substring(0, 17) + "..." : name;
|
|
1238
1700
|
console.log(
|
|
1239
|
-
|
|
1701
|
+
` ${colors.cyan(displayName.padEnd(20))} ${colors.primary(formatNumber(stats.tokens).padStart(8))} ${colors.success(formatCost(stats.cost).padStart(10))} ${colors.dim(`(${stats.sessions} sess)`)}`
|
|
1240
1702
|
);
|
|
1241
1703
|
}
|
|
1242
1704
|
if (Object.keys(data.projects).length > 5) {
|
|
1243
|
-
console.log(
|
|
1705
|
+
console.log(` ${colors.dim(`... and ${Object.keys(data.projects).length - 5} more projects`)}`);
|
|
1244
1706
|
}
|
|
1245
1707
|
}
|
|
1246
1708
|
console.log();
|
|
1247
|
-
console.log(
|
|
1709
|
+
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1710
|
+
console.log(` ${colors.muted("Saved to:")} ${colors.dim(getCCGatherJsonPath())}`);
|
|
1248
1711
|
console.log();
|
|
1249
1712
|
}
|
|
1250
1713
|
async function scan() {
|
|
1251
|
-
|
|
1252
|
-
|
|
1714
|
+
printCompactHeader("1.2.1");
|
|
1715
|
+
console.log(header("Scan Claude Code Usage", "\u{1F50D}"));
|
|
1716
|
+
const spinner = (0, import_ora5.default)({
|
|
1717
|
+
text: "Scanning JSONL files...",
|
|
1718
|
+
color: "cyan"
|
|
1719
|
+
}).start();
|
|
1253
1720
|
const data = scanAndSave();
|
|
1254
1721
|
if (!data) {
|
|
1255
|
-
spinner.fail(
|
|
1256
|
-
console.log(
|
|
1257
|
-
console.log(
|
|
1258
|
-
console.log(
|
|
1259
|
-
console.log(
|
|
1722
|
+
spinner.fail(colors.error("No usage data found"));
|
|
1723
|
+
console.log();
|
|
1724
|
+
console.log(` ${error("Possible reasons:")}`);
|
|
1725
|
+
console.log(` ${colors.dim("\u2022")} Claude Code has not been used yet`);
|
|
1726
|
+
console.log(` ${colors.dim("\u2022")} ~/.claude/projects/ directory is empty`);
|
|
1727
|
+
console.log(` ${colors.dim("\u2022")} No permission to read files`);
|
|
1728
|
+
console.log();
|
|
1260
1729
|
process.exit(1);
|
|
1261
1730
|
}
|
|
1262
|
-
spinner.succeed(
|
|
1731
|
+
spinner.succeed(colors.success("Scan complete!"));
|
|
1263
1732
|
displayResults(data);
|
|
1733
|
+
console.log(` ${colors.muted("Next:")} Run ${colors.white("npx ccgather")} to submit your data`);
|
|
1734
|
+
console.log();
|
|
1264
1735
|
}
|
|
1265
1736
|
|
|
1266
1737
|
// src/index.ts
|
|
1738
|
+
init_config();
|
|
1739
|
+
init_api();
|
|
1740
|
+
var VERSION = "1.2.1";
|
|
1267
1741
|
var program = new import_commander.Command();
|
|
1268
|
-
program.name("ccgather").description("Submit your Claude Code usage to the CCgather leaderboard").version(
|
|
1742
|
+
program.name("ccgather").description("Submit your Claude Code usage to the CCgather leaderboard").version(VERSION).option("-y, --yes", "Skip confirmation prompt").option("--auto", "Enable automatic sync on session end").option("--manual", "Disable automatic sync").option("--no-menu", "Skip interactive menu (direct submit)");
|
|
1269
1743
|
program.command("scan").description("Scan Claude Code usage and create ccgather.json").action(scan);
|
|
1270
|
-
program.command("rank").description("View your current rank and stats").action(status);
|
|
1744
|
+
program.command("rank").description("View your current rank and stats").action(() => status({ json: false }));
|
|
1745
|
+
program.command("status").description("View your current rank and stats").option("--json", "Output as JSON").action((opts) => status(opts));
|
|
1746
|
+
program.command("sync").description("Sync usage data to CCgather").action(async () => {
|
|
1747
|
+
const { sync: sync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
1748
|
+
await sync2({});
|
|
1749
|
+
});
|
|
1750
|
+
program.command("auth").description("Authenticate with CCgather").action(async () => {
|
|
1751
|
+
const { auth: auth2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
1752
|
+
await auth2({});
|
|
1753
|
+
});
|
|
1271
1754
|
program.command("reset").description("Remove auto-sync hook and clear config").action(async () => {
|
|
1272
1755
|
const { reset: reset2 } = await Promise.resolve().then(() => (init_reset(), reset_exports));
|
|
1273
1756
|
await reset2();
|
|
1274
1757
|
});
|
|
1758
|
+
async function showInteractiveMenu() {
|
|
1759
|
+
printHeader(VERSION);
|
|
1760
|
+
if (isAuthenticated()) {
|
|
1761
|
+
try {
|
|
1762
|
+
const result = await getStatus();
|
|
1763
|
+
if (result.success && result.data) {
|
|
1764
|
+
const stats = result.data;
|
|
1765
|
+
const levelInfo = getLevelInfo(stats.totalTokens);
|
|
1766
|
+
const welcomeBox = createWelcomeBox({
|
|
1767
|
+
username: getConfig().get("username") || "User",
|
|
1768
|
+
level: levelInfo.level,
|
|
1769
|
+
levelName: levelInfo.name,
|
|
1770
|
+
levelIcon: levelInfo.icon,
|
|
1771
|
+
globalRank: stats.rank,
|
|
1772
|
+
ccplan: stats.tier
|
|
1773
|
+
});
|
|
1774
|
+
console.log(welcomeBox);
|
|
1775
|
+
console.log();
|
|
1776
|
+
}
|
|
1777
|
+
} catch {
|
|
1778
|
+
}
|
|
1779
|
+
} else {
|
|
1780
|
+
console.log(colors.dim(' Not logged in. Run "ccgather auth" to authenticate.\n'));
|
|
1781
|
+
}
|
|
1782
|
+
const { action } = await import_inquirer3.default.prompt([
|
|
1783
|
+
{
|
|
1784
|
+
type: "list",
|
|
1785
|
+
name: "action",
|
|
1786
|
+
message: "What would you like to do?",
|
|
1787
|
+
choices: [
|
|
1788
|
+
{ name: `\u{1F4E4} ${import_chalk6.default.white("Submit usage data")}`, value: "submit" },
|
|
1789
|
+
{ name: `\u{1F4CA} ${import_chalk6.default.white("View my stats")}`, value: "status" },
|
|
1790
|
+
{ name: `\u{1F50D} ${import_chalk6.default.white("Scan local usage")}`, value: "scan" },
|
|
1791
|
+
{ name: `\u2699\uFE0F ${import_chalk6.default.white("Setup auto-sync")}`, value: "setup" },
|
|
1792
|
+
{ name: `\u{1F527} ${import_chalk6.default.white("Settings")}`, value: "settings" },
|
|
1793
|
+
new import_inquirer3.default.Separator(),
|
|
1794
|
+
{ name: `\u2753 ${import_chalk6.default.gray("Help")}`, value: "help" },
|
|
1795
|
+
{ name: `\u{1F6AA} ${import_chalk6.default.gray("Exit")}`, value: "exit" }
|
|
1796
|
+
],
|
|
1797
|
+
loop: false
|
|
1798
|
+
}
|
|
1799
|
+
]);
|
|
1800
|
+
console.log();
|
|
1801
|
+
switch (action) {
|
|
1802
|
+
case "submit":
|
|
1803
|
+
await submit({ yes: false });
|
|
1804
|
+
break;
|
|
1805
|
+
case "status":
|
|
1806
|
+
await status({ json: false });
|
|
1807
|
+
break;
|
|
1808
|
+
case "scan":
|
|
1809
|
+
await scan();
|
|
1810
|
+
break;
|
|
1811
|
+
case "setup":
|
|
1812
|
+
await showSetupMenu();
|
|
1813
|
+
break;
|
|
1814
|
+
case "settings":
|
|
1815
|
+
await showSettingsMenu();
|
|
1816
|
+
break;
|
|
1817
|
+
case "help":
|
|
1818
|
+
showHelp();
|
|
1819
|
+
break;
|
|
1820
|
+
case "exit":
|
|
1821
|
+
console.log(colors.muted(" Goodbye! Happy coding with Claude! \u{1F44B}\n"));
|
|
1822
|
+
process.exit(0);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
async function showSetupMenu() {
|
|
1826
|
+
const { setupAction } = await import_inquirer3.default.prompt([
|
|
1827
|
+
{
|
|
1828
|
+
type: "list",
|
|
1829
|
+
name: "setupAction",
|
|
1830
|
+
message: "Auto-sync settings:",
|
|
1831
|
+
choices: [
|
|
1832
|
+
{ name: `\u2705 ${import_chalk6.default.white("Enable auto-sync")}`, value: "enable" },
|
|
1833
|
+
{ name: `\u274C ${import_chalk6.default.white("Disable auto-sync")}`, value: "disable" },
|
|
1834
|
+
{ name: `\u2B05\uFE0F ${import_chalk6.default.gray("Back")}`, value: "back" }
|
|
1835
|
+
]
|
|
1836
|
+
}
|
|
1837
|
+
]);
|
|
1838
|
+
switch (setupAction) {
|
|
1839
|
+
case "enable":
|
|
1840
|
+
await setupAuto({ auto: true });
|
|
1841
|
+
break;
|
|
1842
|
+
case "disable":
|
|
1843
|
+
await setupAuto({ manual: true });
|
|
1844
|
+
break;
|
|
1845
|
+
case "back":
|
|
1846
|
+
await showInteractiveMenu();
|
|
1847
|
+
break;
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
async function showSettingsMenu() {
|
|
1851
|
+
const config = getConfig();
|
|
1852
|
+
const isAuth = isAuthenticated();
|
|
1853
|
+
const { settingsAction } = await import_inquirer3.default.prompt([
|
|
1854
|
+
{
|
|
1855
|
+
type: "list",
|
|
1856
|
+
name: "settingsAction",
|
|
1857
|
+
message: "Settings:",
|
|
1858
|
+
choices: [
|
|
1859
|
+
{
|
|
1860
|
+
name: isAuth ? `\u{1F510} ${import_chalk6.default.white("Re-authenticate")}` : `\u{1F510} ${import_chalk6.default.white("Login / Authenticate")}`,
|
|
1861
|
+
value: "auth"
|
|
1862
|
+
},
|
|
1863
|
+
{ name: `\u{1F5D1}\uFE0F ${import_chalk6.default.white("Reset all settings")}`, value: "reset" },
|
|
1864
|
+
{ name: `\u{1F4CB} ${import_chalk6.default.white("Show current config")}`, value: "show" },
|
|
1865
|
+
{ name: `\u2B05\uFE0F ${import_chalk6.default.gray("Back")}`, value: "back" }
|
|
1866
|
+
]
|
|
1867
|
+
}
|
|
1868
|
+
]);
|
|
1869
|
+
switch (settingsAction) {
|
|
1870
|
+
case "auth": {
|
|
1871
|
+
const { auth: auth2 } = await Promise.resolve().then(() => (init_auth(), auth_exports));
|
|
1872
|
+
await auth2({});
|
|
1873
|
+
break;
|
|
1874
|
+
}
|
|
1875
|
+
case "reset": {
|
|
1876
|
+
const { confirmReset } = await import_inquirer3.default.prompt([
|
|
1877
|
+
{
|
|
1878
|
+
type: "confirm",
|
|
1879
|
+
name: "confirmReset",
|
|
1880
|
+
message: import_chalk6.default.yellow("Are you sure you want to reset all settings?"),
|
|
1881
|
+
default: false
|
|
1882
|
+
}
|
|
1883
|
+
]);
|
|
1884
|
+
if (confirmReset) {
|
|
1885
|
+
const { reset: reset2 } = await Promise.resolve().then(() => (init_reset(), reset_exports));
|
|
1886
|
+
await reset2();
|
|
1887
|
+
}
|
|
1888
|
+
break;
|
|
1889
|
+
}
|
|
1890
|
+
case "show":
|
|
1891
|
+
console.log();
|
|
1892
|
+
console.log(colors.muted(" Current Configuration:"));
|
|
1893
|
+
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1894
|
+
console.log(` ${colors.muted("Username:")} ${config.get("username") || colors.dim("(not set)")}`);
|
|
1895
|
+
console.log(` ${colors.muted("API Token:")} ${config.get("apiToken") ? colors.success("\u2713 Set") : colors.error("\u2717 Not set")}`);
|
|
1896
|
+
console.log(` ${colors.muted("Auto-sync:")} ${config.get("autoSync") ? colors.success("Enabled") : colors.dim("Disabled")}`);
|
|
1897
|
+
console.log(` ${colors.muted("Last Sync:")} ${config.get("lastSync") || colors.dim("Never")}`);
|
|
1898
|
+
console.log();
|
|
1899
|
+
await promptContinue();
|
|
1900
|
+
await showSettingsMenu();
|
|
1901
|
+
break;
|
|
1902
|
+
case "back":
|
|
1903
|
+
await showInteractiveMenu();
|
|
1904
|
+
break;
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
function showHelp() {
|
|
1908
|
+
console.log(colors.primary.bold(" CCgather CLI Commands"));
|
|
1909
|
+
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1910
|
+
console.log();
|
|
1911
|
+
console.log(` ${colors.white("npx ccgather")} Interactive menu (default)`);
|
|
1912
|
+
console.log(` ${colors.white("npx ccgather scan")} Scan local Claude Code usage`);
|
|
1913
|
+
console.log(` ${colors.white("npx ccgather rank")} View your current rank`);
|
|
1914
|
+
console.log(` ${colors.white("npx ccgather sync")} Sync usage data`);
|
|
1915
|
+
console.log(` ${colors.white("npx ccgather auth")} Authenticate with CCgather`);
|
|
1916
|
+
console.log(` ${colors.white("npx ccgather reset")} Reset all settings`);
|
|
1917
|
+
console.log();
|
|
1918
|
+
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1919
|
+
console.log(` ${colors.muted("Options:")}`);
|
|
1920
|
+
console.log(` ${colors.white("-y, --yes")} Skip confirmation prompts`);
|
|
1921
|
+
console.log(` ${colors.white("--auto")} Enable auto-sync`);
|
|
1922
|
+
console.log(` ${colors.white("--manual")} Disable auto-sync`);
|
|
1923
|
+
console.log(` ${colors.white("--no-menu")} Skip interactive menu`);
|
|
1924
|
+
console.log();
|
|
1925
|
+
console.log(` ${colors.muted("Documentation:")} ${link("https://ccgather.dev/docs")}`);
|
|
1926
|
+
console.log(` ${colors.muted("Leaderboard:")} ${link("https://ccgather.dev/leaderboard")}`);
|
|
1927
|
+
console.log();
|
|
1928
|
+
}
|
|
1929
|
+
async function promptContinue() {
|
|
1930
|
+
await import_inquirer3.default.prompt([
|
|
1931
|
+
{
|
|
1932
|
+
type: "input",
|
|
1933
|
+
name: "continue",
|
|
1934
|
+
message: colors.dim("Press Enter to continue...")
|
|
1935
|
+
}
|
|
1936
|
+
]);
|
|
1937
|
+
}
|
|
1275
1938
|
program.action(async (options) => {
|
|
1276
1939
|
if (options.auto) {
|
|
1277
1940
|
await setupAuto({ auto: true });
|
|
@@ -1281,6 +1944,10 @@ program.action(async (options) => {
|
|
|
1281
1944
|
await setupAuto({ manual: true });
|
|
1282
1945
|
return;
|
|
1283
1946
|
}
|
|
1284
|
-
|
|
1947
|
+
if (options.menu === false) {
|
|
1948
|
+
await submit({ yes: options.yes });
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
await showInteractiveMenu();
|
|
1285
1952
|
});
|
|
1286
1953
|
program.parse();
|