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.
Files changed (2) hide show
  1. package/dist/index.js +445 -1417
  2. 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
- return Math.round((inputCost + outputCost) * 100) / 100;
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
- for (const filePath of jsonlFiles) {
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 += usage.cache_read_input_tokens || 0;
241
- totalCacheWrite += usage.cache_creation_input_tokens || 0;
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
- var fs2, path2, os2, CCGATHER_JSON_VERSION;
354
- var init_ccgather_json = __esm({
355
- "src/lib/ccgather-json.ts"() {
356
- "use strict";
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 printHeader(version) {
462
- console.log(LOGO);
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 printCompactHeader(version) {
470
- console.log();
471
- console.log(LOGO_COMPACT);
472
- console.log(colors.dim(` v${version}`));
473
- console.log();
474
- }
475
- function link(url) {
476
- return colors.cyan.underline(url);
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. Please run 'ccgather auth' first." };
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, rank: result.rank };
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
- let dataSource = "";
644
- const ccgatherData = readCCGatherJson();
645
- if (ccgatherData) {
646
- usageData = ccgatherToUsageData(ccgatherData);
647
- dataSource = "ccgather.json";
802
+ const totalFiles = getSessionFileCount();
803
+ if (totalFiles > 0) {
648
804
  console.log(`
649
- ${success(`Found ${dataSource}`)}`);
650
- console.log(
651
- ` ${colors.dim(`Last scanned: ${new Date(ccgatherData.lastScanned).toLocaleString()}`)}`
652
- );
653
- if (usageData.ccplan) {
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("Scanned and saved to ccgather.json")}`);
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
- if (!options.yes) {
716
- const inquirer4 = await import("inquirer");
717
- const { confirmSubmit } = await inquirer4.default.prompt([
718
- {
719
- type: "confirm",
720
- name: "confirmSubmit",
721
- message: "Submit to CCgather leaderboard?",
722
- default: true
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
- return;
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 to CCgather!"));
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("Profile:")} ${link(result.profileUrl || `https://ccgather.com/u/${username}`)}`
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 running:")} ${colors.white("npx ccgather auth")}`);
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
- var import_ora, fs3, path3, os3;
765
- var init_submit = __esm({
766
- "src/commands/submit.ts"() {
767
- "use strict";
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
- if (options.json) {
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 = options.json ? null : (0, import_ora2.default)({
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
- if (spinner) spinner.fail(colors.error("Failed to fetch status"));
1297
- if (options.json) {
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
- if (options.json) {
1308
- console.log(JSON.stringify(stats, null, 2));
1309
- return;
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(`Rank #${stats.rank}`)}`);
1318
- console.log(` ${colors.dim(`Top ${stats.percentile.toFixed(1)}% of all users`)}`);
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} \u2022 ${levelInfo.name}`)}`
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("Tier")} ${colors.cyan(stats.tier || "Unknown")}`
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("https://ccgather.com/leaderboard")}`);
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
- init_api();
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: `${import_chalk6.default.yellow("Update")} available ${import_chalk6.default.dim(VERSION)} \u2192 ${import_chalk6.default.yellow(notifier.update?.latest || "")}
1865
- ${import_chalk6.default.yellow("Run")} ${import_chalk6.default.cyan("npx ccgather@latest")} to update`,
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).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)");
1875
- program.command("scan").description("Scan Claude Code usage and create ccgather.json").option("-a, --all", "Scan all-time usage (no date limit)").option("-d, --days <number>", "Number of days to scan (default: 30)", parseInt).action((opts) => scan(opts));
1876
- program.command("rank").description("View your current rank and stats").action(() => status({ json: false }));
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 You can authenticate later by running: npx ccgather auth\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 result = await getStatus();
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
- const welcomeBox = createWelcomeBox({
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} ${import_chalk6.default.white("Submit usage data")}`, value: "submit" },
1938
- { name: `\u{1F4CA} ${import_chalk6.default.white("View my stats")}`, value: "status" },
1939
- { name: `\u{1F50D} ${import_chalk6.default.white("Scan local usage")}`, value: "scan" },
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 submit({ yes: false });
1953
- break;
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 "setup":
1961
- await showSetupMenu();
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
- name: isAuth ? `\u{1F510} ${import_chalk6.default.white("Re-authenticate")}` : `\u{1F510} ${import_chalk6.default.white("Login / Authenticate")}`,
2010
- value: "auth"
2011
- },
2012
- { name: `\u{1F5D1}\uFE0F ${import_chalk6.default.white("Reset all settings")}`, value: "reset" },
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 "reset": {
2025
- const { confirmReset } = await import_inquirer3.default.prompt([
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 showInteractiveMenu();
1106
+ await showMenuOnly();
2061
1107
  break;
2062
1108
  }
2063
1109
  }
2064
- function showHelp() {
2065
- console.log(colors.primary.bold(" CCgather CLI Commands"));
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: "input",
2098
- name: "continue",
2099
- message: colors.dim("Press Enter to continue...")
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
- program.action(async (options) => {
2104
- if (options.auto) {
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
- if (options.menu === false) {
2113
- await submit({ yes: options.yes });
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
- await showInteractiveMenu();
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();