ccgather 1.1.0 → 1.2.1

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