ccgather 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1087 -267
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -70,22 +70,85 @@ 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, {
|
|
76
139
|
reset: () => reset
|
|
77
140
|
});
|
|
78
141
|
function getClaudeSettingsDir() {
|
|
79
|
-
return
|
|
142
|
+
return path4.join(os4.homedir(), ".claude");
|
|
80
143
|
}
|
|
81
144
|
function removeStopHook() {
|
|
82
145
|
const claudeDir = getClaudeSettingsDir();
|
|
83
|
-
const settingsPath =
|
|
84
|
-
if (!
|
|
146
|
+
const settingsPath = path4.join(claudeDir, "settings.json");
|
|
147
|
+
if (!fs4.existsSync(settingsPath)) {
|
|
85
148
|
return { success: true, message: "No settings file found" };
|
|
86
149
|
}
|
|
87
150
|
try {
|
|
88
|
-
const content =
|
|
151
|
+
const content = fs4.readFileSync(settingsPath, "utf-8");
|
|
89
152
|
const settings = JSON.parse(content);
|
|
90
153
|
if (settings.hooks && typeof settings.hooks === "object") {
|
|
91
154
|
const hooks = settings.hooks;
|
|
@@ -102,7 +165,7 @@ function removeStopHook() {
|
|
|
102
165
|
}
|
|
103
166
|
}
|
|
104
167
|
}
|
|
105
|
-
|
|
168
|
+
fs4.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
106
169
|
return { success: true, message: "Hook removed" };
|
|
107
170
|
} catch (err) {
|
|
108
171
|
return {
|
|
@@ -113,16 +176,16 @@ function removeStopHook() {
|
|
|
113
176
|
}
|
|
114
177
|
function removeSyncScript() {
|
|
115
178
|
const claudeDir = getClaudeSettingsDir();
|
|
116
|
-
const scriptPath =
|
|
117
|
-
if (
|
|
118
|
-
|
|
179
|
+
const scriptPath = path4.join(claudeDir, "ccgather-sync.js");
|
|
180
|
+
if (fs4.existsSync(scriptPath)) {
|
|
181
|
+
fs4.unlinkSync(scriptPath);
|
|
119
182
|
}
|
|
120
183
|
}
|
|
121
184
|
async function reset() {
|
|
122
185
|
const config = getConfig();
|
|
123
|
-
console.log(
|
|
186
|
+
console.log(import_chalk2.default.bold("\n\u{1F504} CCgather Reset\n"));
|
|
124
187
|
if (!config.get("apiToken")) {
|
|
125
|
-
console.log(
|
|
188
|
+
console.log(import_chalk2.default.yellow("CCgather is not configured."));
|
|
126
189
|
return;
|
|
127
190
|
}
|
|
128
191
|
const { confirmReset } = await import_inquirer.default.prompt([
|
|
@@ -134,87 +197,404 @@ async function reset() {
|
|
|
134
197
|
}
|
|
135
198
|
]);
|
|
136
199
|
if (!confirmReset) {
|
|
137
|
-
console.log(
|
|
200
|
+
console.log(import_chalk2.default.gray("Reset cancelled."));
|
|
138
201
|
return;
|
|
139
202
|
}
|
|
140
203
|
const hookSpinner = (0, import_ora3.default)("Removing Claude Code hook...").start();
|
|
141
204
|
const hookResult = removeStopHook();
|
|
142
205
|
if (hookResult.success) {
|
|
143
|
-
hookSpinner.succeed(
|
|
206
|
+
hookSpinner.succeed(import_chalk2.default.green("Hook removed"));
|
|
144
207
|
} else {
|
|
145
|
-
hookSpinner.warn(
|
|
208
|
+
hookSpinner.warn(import_chalk2.default.yellow(`Could not remove hook: ${hookResult.message}`));
|
|
146
209
|
}
|
|
147
210
|
const scriptSpinner = (0, import_ora3.default)("Removing sync script...").start();
|
|
148
211
|
try {
|
|
149
212
|
removeSyncScript();
|
|
150
|
-
scriptSpinner.succeed(
|
|
213
|
+
scriptSpinner.succeed(import_chalk2.default.green("Sync script removed"));
|
|
151
214
|
} catch {
|
|
152
|
-
scriptSpinner.warn(
|
|
215
|
+
scriptSpinner.warn(import_chalk2.default.yellow("Could not remove sync script"));
|
|
153
216
|
}
|
|
154
217
|
const { deleteAccount } = await import_inquirer.default.prompt([
|
|
155
218
|
{
|
|
156
219
|
type: "confirm",
|
|
157
220
|
name: "deleteAccount",
|
|
158
|
-
message:
|
|
221
|
+
message: import_chalk2.default.red("Do you also want to delete your account from the leaderboard? (This cannot be undone)"),
|
|
159
222
|
default: false
|
|
160
223
|
}
|
|
161
224
|
]);
|
|
162
225
|
if (deleteAccount) {
|
|
163
|
-
console.log(
|
|
164
|
-
console.log(
|
|
226
|
+
console.log(import_chalk2.default.yellow("\nAccount deletion is not yet implemented."));
|
|
227
|
+
console.log(import_chalk2.default.gray("Please contact support to delete your account."));
|
|
165
228
|
}
|
|
166
229
|
const configSpinner = (0, import_ora3.default)("Resetting local configuration...").start();
|
|
167
230
|
resetConfig();
|
|
168
|
-
configSpinner.succeed(
|
|
231
|
+
configSpinner.succeed(import_chalk2.default.green("Local configuration reset"));
|
|
169
232
|
console.log();
|
|
170
|
-
console.log(
|
|
233
|
+
console.log(import_chalk2.default.green.bold("\u2705 Reset complete!"));
|
|
171
234
|
console.log();
|
|
172
|
-
console.log(
|
|
173
|
-
console.log(
|
|
235
|
+
console.log(import_chalk2.default.gray("Your usage will no longer be tracked."));
|
|
236
|
+
console.log(import_chalk2.default.gray("Run `npx ccgather` to set up again."));
|
|
174
237
|
console.log();
|
|
175
238
|
}
|
|
176
|
-
var
|
|
239
|
+
var import_chalk2, import_ora3, fs4, path4, os4, import_inquirer;
|
|
177
240
|
var init_reset = __esm({
|
|
178
241
|
"src/commands/reset.ts"() {
|
|
179
242
|
"use strict";
|
|
180
|
-
|
|
243
|
+
import_chalk2 = __toESM(require("chalk"));
|
|
181
244
|
import_ora3 = __toESM(require("ora"));
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
245
|
+
fs4 = __toESM(require("fs"));
|
|
246
|
+
path4 = __toESM(require("path"));
|
|
247
|
+
os4 = __toESM(require("os"));
|
|
185
248
|
import_inquirer = __toESM(require("inquirer"));
|
|
186
249
|
init_config();
|
|
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"));
|
|
509
|
+
var fs3 = __toESM(require("fs"));
|
|
510
|
+
var path3 = __toESM(require("path"));
|
|
511
|
+
var os3 = __toESM(require("os"));
|
|
512
|
+
init_config();
|
|
513
|
+
|
|
514
|
+
// src/lib/ccgather-json.ts
|
|
196
515
|
var fs2 = __toESM(require("fs"));
|
|
197
516
|
var path2 = __toESM(require("path"));
|
|
198
517
|
var os2 = __toESM(require("os"));
|
|
199
|
-
init_config();
|
|
200
518
|
|
|
201
|
-
// src/lib/
|
|
519
|
+
// src/lib/credentials.ts
|
|
202
520
|
var fs = __toESM(require("fs"));
|
|
203
521
|
var path = __toESM(require("path"));
|
|
204
522
|
var os = __toESM(require("os"));
|
|
205
|
-
|
|
523
|
+
function getCredentialsPath() {
|
|
524
|
+
return path.join(os.homedir(), ".claude", ".credentials.json");
|
|
525
|
+
}
|
|
526
|
+
function mapSubscriptionToCCPlan(subscriptionType) {
|
|
527
|
+
if (!subscriptionType) {
|
|
528
|
+
return "free";
|
|
529
|
+
}
|
|
530
|
+
const type = subscriptionType.toLowerCase();
|
|
531
|
+
if (type === "max" || type.includes("max")) {
|
|
532
|
+
return "max";
|
|
533
|
+
}
|
|
534
|
+
if (type === "team" || type === "enterprise") {
|
|
535
|
+
return "team";
|
|
536
|
+
}
|
|
537
|
+
if (type === "pro") {
|
|
538
|
+
return "pro";
|
|
539
|
+
}
|
|
540
|
+
return "free";
|
|
541
|
+
}
|
|
542
|
+
function readCredentials() {
|
|
543
|
+
const credentialsPath = getCredentialsPath();
|
|
544
|
+
const defaultData = {
|
|
545
|
+
ccplan: null,
|
|
546
|
+
rateLimitTier: null
|
|
547
|
+
};
|
|
548
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
549
|
+
return defaultData;
|
|
550
|
+
}
|
|
551
|
+
try {
|
|
552
|
+
const content = fs.readFileSync(credentialsPath, "utf-8");
|
|
553
|
+
const credentials = JSON.parse(content);
|
|
554
|
+
const oauthData = credentials.claudeAiOauth;
|
|
555
|
+
if (!oauthData) {
|
|
556
|
+
return defaultData;
|
|
557
|
+
}
|
|
558
|
+
const ccplan = mapSubscriptionToCCPlan(oauthData.subscriptionType);
|
|
559
|
+
const rateLimitTier = oauthData.rateLimitTier || null;
|
|
560
|
+
return {
|
|
561
|
+
ccplan,
|
|
562
|
+
rateLimitTier
|
|
563
|
+
};
|
|
564
|
+
} catch (error2) {
|
|
565
|
+
return defaultData;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
// src/lib/ccgather-json.ts
|
|
570
|
+
var CCGATHER_JSON_VERSION = "1.2.0";
|
|
571
|
+
function extractProjectName(filePath) {
|
|
572
|
+
const parts = filePath.split(/[/\\]/);
|
|
573
|
+
const projectsIndex = parts.findIndex((p) => p === "projects");
|
|
574
|
+
if (projectsIndex >= 0 && parts[projectsIndex + 1]) {
|
|
575
|
+
try {
|
|
576
|
+
const encoded = parts[projectsIndex + 1];
|
|
577
|
+
const decoded = decodeURIComponent(encoded);
|
|
578
|
+
const pathParts = decoded.split(/[/\\]/);
|
|
579
|
+
return pathParts[pathParts.length - 1] || decoded;
|
|
580
|
+
} catch {
|
|
581
|
+
return parts[projectsIndex + 1];
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
return "unknown";
|
|
585
|
+
}
|
|
206
586
|
function getCCGatherJsonPath() {
|
|
207
|
-
return
|
|
587
|
+
return path2.join(os2.homedir(), ".claude", "ccgather.json");
|
|
208
588
|
}
|
|
209
589
|
function getClaudeProjectsDir() {
|
|
210
|
-
return
|
|
590
|
+
return path2.join(os2.homedir(), ".claude", "projects");
|
|
211
591
|
}
|
|
212
592
|
function findJsonlFiles(dir) {
|
|
213
593
|
const files = [];
|
|
214
594
|
try {
|
|
215
|
-
const entries =
|
|
595
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
216
596
|
for (const entry of entries) {
|
|
217
|
-
const fullPath =
|
|
597
|
+
const fullPath = path2.join(dir, entry.name);
|
|
218
598
|
if (entry.isDirectory()) {
|
|
219
599
|
files.push(...findJsonlFiles(fullPath));
|
|
220
600
|
} else if (entry.name.endsWith(".jsonl")) {
|
|
@@ -230,7 +610,7 @@ function estimateCost(model, inputTokens, outputTokens) {
|
|
|
230
610
|
"claude-opus-4": { input: 15, output: 75 },
|
|
231
611
|
"claude-sonnet-4": { input: 3, output: 15 },
|
|
232
612
|
"claude-haiku": { input: 0.25, output: 1.25 },
|
|
233
|
-
|
|
613
|
+
default: { input: 3, output: 15 }
|
|
234
614
|
};
|
|
235
615
|
let modelKey = "default";
|
|
236
616
|
for (const key of Object.keys(pricing)) {
|
|
@@ -246,7 +626,7 @@ function estimateCost(model, inputTokens, outputTokens) {
|
|
|
246
626
|
}
|
|
247
627
|
function scanUsageData() {
|
|
248
628
|
const projectsDir = getClaudeProjectsDir();
|
|
249
|
-
if (!
|
|
629
|
+
if (!fs2.existsSync(projectsDir)) {
|
|
250
630
|
return null;
|
|
251
631
|
}
|
|
252
632
|
let totalInputTokens = 0;
|
|
@@ -257,13 +637,25 @@ function scanUsageData() {
|
|
|
257
637
|
let sessionsCount = 0;
|
|
258
638
|
const dates = /* @__PURE__ */ new Set();
|
|
259
639
|
const models = {};
|
|
640
|
+
const projects = {};
|
|
641
|
+
const dailyData = {};
|
|
260
642
|
let firstTimestamp = null;
|
|
261
643
|
let lastTimestamp = null;
|
|
262
644
|
const jsonlFiles = findJsonlFiles(projectsDir);
|
|
263
645
|
sessionsCount = jsonlFiles.length;
|
|
264
646
|
for (const filePath of jsonlFiles) {
|
|
647
|
+
const projectName = extractProjectName(filePath);
|
|
648
|
+
if (!projects[projectName]) {
|
|
649
|
+
projects[projectName] = {
|
|
650
|
+
tokens: 0,
|
|
651
|
+
cost: 0,
|
|
652
|
+
sessions: 0,
|
|
653
|
+
models: {}
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
projects[projectName].sessions++;
|
|
265
657
|
try {
|
|
266
|
-
const content =
|
|
658
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
267
659
|
const lines = content.split("\n").filter((line) => line.trim());
|
|
268
660
|
for (const line of lines) {
|
|
269
661
|
try {
|
|
@@ -277,12 +669,32 @@ function scanUsageData() {
|
|
|
277
669
|
totalOutputTokens += outputTokens;
|
|
278
670
|
totalCacheRead += usage.cache_read_input_tokens || 0;
|
|
279
671
|
totalCacheWrite += usage.cache_creation_input_tokens || 0;
|
|
280
|
-
|
|
672
|
+
const messageCost = estimateCost(model, inputTokens, outputTokens);
|
|
673
|
+
totalCost += messageCost;
|
|
281
674
|
const totalModelTokens = inputTokens + outputTokens;
|
|
282
675
|
models[model] = (models[model] || 0) + totalModelTokens;
|
|
676
|
+
projects[projectName].tokens += totalModelTokens;
|
|
677
|
+
projects[projectName].cost += messageCost;
|
|
678
|
+
projects[projectName].models[model] = (projects[projectName].models[model] || 0) + totalModelTokens;
|
|
283
679
|
if (event.timestamp) {
|
|
284
680
|
const date = new Date(event.timestamp).toISOString().split("T")[0];
|
|
285
681
|
dates.add(date);
|
|
682
|
+
if (!dailyData[date]) {
|
|
683
|
+
dailyData[date] = {
|
|
684
|
+
tokens: 0,
|
|
685
|
+
cost: 0,
|
|
686
|
+
inputTokens: 0,
|
|
687
|
+
outputTokens: 0,
|
|
688
|
+
sessions: /* @__PURE__ */ new Set(),
|
|
689
|
+
models: {}
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
dailyData[date].tokens += totalModelTokens;
|
|
693
|
+
dailyData[date].cost += messageCost;
|
|
694
|
+
dailyData[date].inputTokens += inputTokens;
|
|
695
|
+
dailyData[date].outputTokens += outputTokens;
|
|
696
|
+
dailyData[date].sessions.add(filePath);
|
|
697
|
+
dailyData[date].models[model] = (dailyData[date].models[model] || 0) + totalModelTokens;
|
|
286
698
|
if (!firstTimestamp || event.timestamp < firstTimestamp) {
|
|
287
699
|
firstTimestamp = event.timestamp;
|
|
288
700
|
}
|
|
@@ -301,6 +713,19 @@ function scanUsageData() {
|
|
|
301
713
|
if (totalTokens === 0) {
|
|
302
714
|
return null;
|
|
303
715
|
}
|
|
716
|
+
for (const projectName of Object.keys(projects)) {
|
|
717
|
+
projects[projectName].cost = Math.round(projects[projectName].cost * 100) / 100;
|
|
718
|
+
}
|
|
719
|
+
const dailyUsage = Object.entries(dailyData).map(([date, data]) => ({
|
|
720
|
+
date,
|
|
721
|
+
tokens: data.tokens,
|
|
722
|
+
cost: Math.round(data.cost * 100) / 100,
|
|
723
|
+
inputTokens: data.inputTokens,
|
|
724
|
+
outputTokens: data.outputTokens,
|
|
725
|
+
sessions: data.sessions.size,
|
|
726
|
+
models: data.models
|
|
727
|
+
})).sort((a, b) => a.date.localeCompare(b.date));
|
|
728
|
+
const credentials = readCredentials();
|
|
304
729
|
return {
|
|
305
730
|
version: CCGATHER_JSON_VERSION,
|
|
306
731
|
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -319,16 +744,22 @@ function scanUsageData() {
|
|
|
319
744
|
firstUsed: firstTimestamp ? new Date(firstTimestamp).toISOString().split("T")[0] : null,
|
|
320
745
|
lastUsed: lastTimestamp ? new Date(lastTimestamp).toISOString().split("T")[0] : null
|
|
321
746
|
},
|
|
322
|
-
models
|
|
747
|
+
models,
|
|
748
|
+
projects,
|
|
749
|
+
dailyUsage,
|
|
750
|
+
account: {
|
|
751
|
+
ccplan: credentials.ccplan,
|
|
752
|
+
rateLimitTier: credentials.rateLimitTier
|
|
753
|
+
}
|
|
323
754
|
};
|
|
324
755
|
}
|
|
325
756
|
function readCCGatherJson() {
|
|
326
757
|
const jsonPath = getCCGatherJsonPath();
|
|
327
|
-
if (!
|
|
758
|
+
if (!fs2.existsSync(jsonPath)) {
|
|
328
759
|
return null;
|
|
329
760
|
}
|
|
330
761
|
try {
|
|
331
|
-
const content =
|
|
762
|
+
const content = fs2.readFileSync(jsonPath, "utf-8");
|
|
332
763
|
return JSON.parse(content);
|
|
333
764
|
} catch {
|
|
334
765
|
return null;
|
|
@@ -336,11 +767,11 @@ function readCCGatherJson() {
|
|
|
336
767
|
}
|
|
337
768
|
function writeCCGatherJson(data) {
|
|
338
769
|
const jsonPath = getCCGatherJsonPath();
|
|
339
|
-
const claudeDir =
|
|
340
|
-
if (!
|
|
341
|
-
|
|
770
|
+
const claudeDir = path2.dirname(jsonPath);
|
|
771
|
+
if (!fs2.existsSync(claudeDir)) {
|
|
772
|
+
fs2.mkdirSync(claudeDir, { recursive: true });
|
|
342
773
|
}
|
|
343
|
-
|
|
774
|
+
fs2.writeFileSync(jsonPath, JSON.stringify(data, null, 2));
|
|
344
775
|
}
|
|
345
776
|
function scanAndSave() {
|
|
346
777
|
const data = scanUsageData();
|
|
@@ -350,6 +781,173 @@ function scanAndSave() {
|
|
|
350
781
|
return data;
|
|
351
782
|
}
|
|
352
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
|
+
|
|
353
951
|
// src/commands/submit.ts
|
|
354
952
|
function ccgatherToUsageData(data) {
|
|
355
953
|
return {
|
|
@@ -359,17 +957,19 @@ function ccgatherToUsageData(data) {
|
|
|
359
957
|
outputTokens: data.usage.outputTokens,
|
|
360
958
|
cacheReadTokens: data.usage.cacheReadTokens,
|
|
361
959
|
cacheWriteTokens: data.usage.cacheWriteTokens,
|
|
362
|
-
daysTracked: data.stats.daysTracked
|
|
960
|
+
daysTracked: data.stats.daysTracked,
|
|
961
|
+
ccplan: data.account?.ccplan || null,
|
|
962
|
+
rateLimitTier: data.account?.rateLimitTier || null
|
|
363
963
|
};
|
|
364
964
|
}
|
|
365
965
|
function findCcJson() {
|
|
366
966
|
const possiblePaths = [
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
967
|
+
path3.join(process.cwd(), "cc.json"),
|
|
968
|
+
path3.join(os3.homedir(), "cc.json"),
|
|
969
|
+
path3.join(os3.homedir(), ".claude", "cc.json")
|
|
370
970
|
];
|
|
371
971
|
for (const p of possiblePaths) {
|
|
372
|
-
if (
|
|
972
|
+
if (fs3.existsSync(p)) {
|
|
373
973
|
return p;
|
|
374
974
|
}
|
|
375
975
|
}
|
|
@@ -377,7 +977,7 @@ function findCcJson() {
|
|
|
377
977
|
}
|
|
378
978
|
function parseCcJson(filePath) {
|
|
379
979
|
try {
|
|
380
|
-
const content =
|
|
980
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
381
981
|
const data = JSON.parse(content);
|
|
382
982
|
return {
|
|
383
983
|
totalTokens: data.totalTokens || data.total_tokens || 0,
|
|
@@ -401,9 +1001,6 @@ function calculateDaysTracked(data) {
|
|
|
401
1001
|
}
|
|
402
1002
|
return 1;
|
|
403
1003
|
}
|
|
404
|
-
function formatNumber(num) {
|
|
405
|
-
return num.toLocaleString();
|
|
406
|
-
}
|
|
407
1004
|
async function detectGitHubUsername() {
|
|
408
1005
|
try {
|
|
409
1006
|
const { execSync } = await import("child_process");
|
|
@@ -439,6 +1036,8 @@ async function submitToServer(username, data) {
|
|
|
439
1036
|
cacheReadTokens: data.cacheReadTokens,
|
|
440
1037
|
cacheWriteTokens: data.cacheWriteTokens,
|
|
441
1038
|
daysTracked: data.daysTracked,
|
|
1039
|
+
ccplan: data.ccplan,
|
|
1040
|
+
rateLimitTier: data.rateLimitTier,
|
|
442
1041
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
443
1042
|
})
|
|
444
1043
|
});
|
|
@@ -447,25 +1046,30 @@ async function submitToServer(username, data) {
|
|
|
447
1046
|
return { success: false, error: errorData.error || `HTTP ${response.status}` };
|
|
448
1047
|
}
|
|
449
1048
|
const result = await response.json();
|
|
450
|
-
return { success: true, profileUrl: result.profileUrl };
|
|
1049
|
+
return { success: true, profileUrl: result.profileUrl, rank: result.rank };
|
|
451
1050
|
} catch (err) {
|
|
452
1051
|
return { success: false, error: err instanceof Error ? err.message : "Unknown error" };
|
|
453
1052
|
}
|
|
454
1053
|
}
|
|
455
1054
|
async function submit(options) {
|
|
456
|
-
|
|
457
|
-
|
|
1055
|
+
printCompactHeader("1.2.0");
|
|
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();
|
|
458
1061
|
let username = await detectGitHubUsername();
|
|
459
1062
|
spinner.stop();
|
|
460
1063
|
if (username) {
|
|
461
|
-
console.log(
|
|
1064
|
+
console.log(`
|
|
1065
|
+
${colors.muted("Detected:")} ${colors.white(username)}`);
|
|
462
1066
|
}
|
|
463
|
-
const
|
|
464
|
-
const { confirmedUsername } = await
|
|
1067
|
+
const inquirer4 = await import("inquirer");
|
|
1068
|
+
const { confirmedUsername } = await inquirer4.default.prompt([
|
|
465
1069
|
{
|
|
466
1070
|
type: "input",
|
|
467
1071
|
name: "confirmedUsername",
|
|
468
|
-
message: "GitHub username:",
|
|
1072
|
+
message: colors.muted("GitHub username:"),
|
|
469
1073
|
default: username || "",
|
|
470
1074
|
validate: (input) => input.trim().length > 0 || "Username is required"
|
|
471
1075
|
}
|
|
@@ -477,18 +1081,21 @@ async function submit(options) {
|
|
|
477
1081
|
if (ccgatherData) {
|
|
478
1082
|
usageData = ccgatherToUsageData(ccgatherData);
|
|
479
1083
|
dataSource = "ccgather.json";
|
|
480
|
-
console.log(
|
|
481
|
-
|
|
482
|
-
`));
|
|
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
|
+
}
|
|
483
1090
|
}
|
|
484
1091
|
if (!usageData) {
|
|
485
1092
|
const ccJsonPath = findCcJson();
|
|
486
1093
|
if (ccJsonPath) {
|
|
487
|
-
const { useCcJson } = await
|
|
1094
|
+
const { useCcJson } = await inquirer4.default.prompt([
|
|
488
1095
|
{
|
|
489
1096
|
type: "confirm",
|
|
490
1097
|
name: "useCcJson",
|
|
491
|
-
message:
|
|
1098
|
+
message: "Found existing cc.json. Use this file?",
|
|
492
1099
|
default: true
|
|
493
1100
|
}
|
|
494
1101
|
]);
|
|
@@ -499,34 +1106,45 @@ async function submit(options) {
|
|
|
499
1106
|
}
|
|
500
1107
|
}
|
|
501
1108
|
if (!usageData) {
|
|
502
|
-
const parseSpinner = (0, import_ora.default)(
|
|
1109
|
+
const parseSpinner = (0, import_ora.default)({
|
|
1110
|
+
text: "Scanning Claude Code usage data...",
|
|
1111
|
+
color: "cyan"
|
|
1112
|
+
}).start();
|
|
503
1113
|
const scannedData = scanAndSave();
|
|
504
1114
|
parseSpinner.stop();
|
|
505
1115
|
if (scannedData) {
|
|
506
1116
|
usageData = ccgatherToUsageData(scannedData);
|
|
507
1117
|
dataSource = "Claude Code logs";
|
|
508
|
-
console.log(
|
|
509
|
-
|
|
510
|
-
`));
|
|
1118
|
+
console.log(`
|
|
1119
|
+
${success("Scanned and saved to ccgather.json")}`);
|
|
1120
|
+
console.log(` ${colors.dim(`Path: ${getCCGatherJsonPath()}`)}`);
|
|
511
1121
|
}
|
|
512
1122
|
}
|
|
513
1123
|
if (!usageData) {
|
|
514
|
-
console.log(
|
|
515
|
-
|
|
516
|
-
console.log(
|
|
1124
|
+
console.log(`
|
|
1125
|
+
${error("No usage data found.")}`);
|
|
1126
|
+
console.log(` ${colors.muted("Make sure you have used Claude Code.")}`);
|
|
1127
|
+
console.log(` ${colors.muted("Run:")} ${colors.white("npx ccgather scan")}
|
|
1128
|
+
`);
|
|
517
1129
|
process.exit(1);
|
|
518
1130
|
}
|
|
519
1131
|
if (dataSource && dataSource !== "Claude Code logs") {
|
|
520
|
-
console.log(
|
|
521
|
-
`));
|
|
1132
|
+
console.log(`
|
|
1133
|
+
${success(`Using ${dataSource}`)}`);
|
|
522
1134
|
}
|
|
523
|
-
console.log(
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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));
|
|
527
1145
|
console.log();
|
|
528
1146
|
if (!options.yes) {
|
|
529
|
-
const { confirmSubmit } = await
|
|
1147
|
+
const { confirmSubmit } = await inquirer4.default.prompt([
|
|
530
1148
|
{
|
|
531
1149
|
type: "confirm",
|
|
532
1150
|
name: "confirmSubmit",
|
|
@@ -535,106 +1153,70 @@ async function submit(options) {
|
|
|
535
1153
|
}
|
|
536
1154
|
]);
|
|
537
1155
|
if (!confirmSubmit) {
|
|
538
|
-
console.log(
|
|
1156
|
+
console.log(`
|
|
1157
|
+
${colors.muted("Submission cancelled.")}
|
|
1158
|
+
`);
|
|
539
1159
|
return;
|
|
540
1160
|
}
|
|
541
1161
|
}
|
|
542
|
-
const submitSpinner = (0, import_ora.default)(
|
|
1162
|
+
const submitSpinner = (0, import_ora.default)({
|
|
1163
|
+
text: "Submitting to CCgather...",
|
|
1164
|
+
color: "cyan"
|
|
1165
|
+
}).start();
|
|
543
1166
|
const result = await submitToServer(username, usageData);
|
|
544
1167
|
if (result.success) {
|
|
545
|
-
submitSpinner.succeed(
|
|
1168
|
+
submitSpinner.succeed(colors.success("Successfully submitted to CCgather!"));
|
|
546
1169
|
console.log();
|
|
547
|
-
|
|
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));
|
|
1179
|
+
console.log();
|
|
1180
|
+
console.log(` ${colors.dim("View leaderboard:")} ${link("https://ccgather.dev/leaderboard")}`);
|
|
548
1181
|
console.log();
|
|
549
|
-
console.log(import_chalk.default.bold("Done! \u{1F389}"));
|
|
550
1182
|
} else {
|
|
551
|
-
submitSpinner.fail(
|
|
552
|
-
console.log(
|
|
1183
|
+
submitSpinner.fail(colors.error("Failed to submit"));
|
|
1184
|
+
console.log(`
|
|
1185
|
+
${error(result.error || "Unknown error")}
|
|
1186
|
+
`);
|
|
553
1187
|
process.exit(1);
|
|
554
1188
|
}
|
|
555
|
-
console.log();
|
|
556
1189
|
}
|
|
557
1190
|
|
|
558
1191
|
// src/commands/status.ts
|
|
559
|
-
var import_chalk2 = __toESM(require("chalk"));
|
|
560
1192
|
var import_ora2 = __toESM(require("ora"));
|
|
561
1193
|
init_config();
|
|
562
|
-
|
|
563
|
-
// src/lib/api.ts
|
|
564
|
-
init_config();
|
|
565
|
-
async function fetchApi(endpoint, options = {}) {
|
|
566
|
-
const config = getConfig();
|
|
567
|
-
const apiToken = config.get("apiToken");
|
|
568
|
-
const apiUrl = getApiUrl();
|
|
569
|
-
if (!apiToken) {
|
|
570
|
-
return { success: false, error: "Not authenticated. Run: npx ccgather auth" };
|
|
571
|
-
}
|
|
572
|
-
try {
|
|
573
|
-
const response = await fetch(`${apiUrl}${endpoint}`, {
|
|
574
|
-
...options,
|
|
575
|
-
headers: {
|
|
576
|
-
"Content-Type": "application/json",
|
|
577
|
-
Authorization: `Bearer ${apiToken}`,
|
|
578
|
-
...options.headers
|
|
579
|
-
}
|
|
580
|
-
});
|
|
581
|
-
const data = await response.json();
|
|
582
|
-
if (!response.ok) {
|
|
583
|
-
return { success: false, error: data.error || `HTTP ${response.status}` };
|
|
584
|
-
}
|
|
585
|
-
return { success: true, data };
|
|
586
|
-
} catch (error) {
|
|
587
|
-
const message = error instanceof Error ? error.message : "Unknown error";
|
|
588
|
-
return { success: false, error: message };
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
async function getStatus() {
|
|
592
|
-
return fetchApi("/cli/status");
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
// src/commands/status.ts
|
|
596
|
-
function formatNumber2(num) {
|
|
597
|
-
if (num >= 1e9) return `${(num / 1e9).toFixed(2)}B`;
|
|
598
|
-
if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
|
|
599
|
-
if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`;
|
|
600
|
-
return num.toString();
|
|
601
|
-
}
|
|
602
|
-
function getTierEmoji(tier) {
|
|
603
|
-
const emojis = {
|
|
604
|
-
free: "\u{1F193}",
|
|
605
|
-
pro: "\u2B50",
|
|
606
|
-
team: "\u{1F465}",
|
|
607
|
-
enterprise: "\u{1F3E2}"
|
|
608
|
-
};
|
|
609
|
-
return emojis[tier.toLowerCase()] || "\u{1F3AF}";
|
|
610
|
-
}
|
|
611
|
-
function getRankMedal(rank) {
|
|
612
|
-
if (rank === 1) return "\u{1F947}";
|
|
613
|
-
if (rank === 2) return "\u{1F948}";
|
|
614
|
-
if (rank === 3) return "\u{1F949}";
|
|
615
|
-
if (rank <= 10) return "\u{1F3C5}";
|
|
616
|
-
if (rank <= 100) return "\u{1F396}\uFE0F";
|
|
617
|
-
return "\u{1F4CA}";
|
|
618
|
-
}
|
|
1194
|
+
init_api();
|
|
619
1195
|
async function status(options) {
|
|
620
1196
|
if (!isAuthenticated()) {
|
|
621
1197
|
if (options.json) {
|
|
622
1198
|
console.log(JSON.stringify({ error: "Not authenticated" }));
|
|
623
1199
|
} else {
|
|
624
|
-
console.log(
|
|
625
|
-
|
|
1200
|
+
console.log(`
|
|
1201
|
+
${error("Not authenticated.")}`);
|
|
1202
|
+
console.log(` ${colors.muted("Run:")} ${colors.white("npx ccgather auth")}
|
|
1203
|
+
`);
|
|
626
1204
|
}
|
|
627
1205
|
process.exit(1);
|
|
628
1206
|
}
|
|
629
|
-
const spinner = options.json ? null : (0, import_ora2.default)(
|
|
1207
|
+
const spinner = options.json ? null : (0, import_ora2.default)({
|
|
1208
|
+
text: "Fetching your stats...",
|
|
1209
|
+
color: "cyan"
|
|
1210
|
+
}).start();
|
|
630
1211
|
const result = await getStatus();
|
|
631
1212
|
if (!result.success) {
|
|
632
|
-
if (spinner) spinner.fail(
|
|
1213
|
+
if (spinner) spinner.fail(colors.error("Failed to fetch status"));
|
|
633
1214
|
if (options.json) {
|
|
634
1215
|
console.log(JSON.stringify({ error: result.error }));
|
|
635
1216
|
} else {
|
|
636
|
-
console.log(
|
|
637
|
-
|
|
1217
|
+
console.log(`
|
|
1218
|
+
${error(result.error || "Unknown error")}
|
|
1219
|
+
`);
|
|
638
1220
|
}
|
|
639
1221
|
process.exit(1);
|
|
640
1222
|
}
|
|
@@ -643,45 +1225,52 @@ async function status(options) {
|
|
|
643
1225
|
console.log(JSON.stringify(stats, null, 2));
|
|
644
1226
|
return;
|
|
645
1227
|
}
|
|
646
|
-
spinner?.succeed(
|
|
647
|
-
|
|
648
|
-
console.log(
|
|
649
|
-
|
|
1228
|
+
spinner?.succeed(colors.success("Status retrieved"));
|
|
1229
|
+
printCompactHeader("1.2.0");
|
|
1230
|
+
console.log(header("Your CCgather Stats", "\u{1F4CA}"));
|
|
1231
|
+
const levelInfo = getLevelInfo(stats.totalTokens);
|
|
650
1232
|
const medal = getRankMedal(stats.rank);
|
|
651
|
-
console.log(
|
|
652
|
-
${medal} ${
|
|
653
|
-
console.log(
|
|
654
|
-
console.log(
|
|
655
|
-
console.log(
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
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));
|
|
664
1251
|
if (stats.badges && stats.badges.length > 0) {
|
|
665
|
-
console.log(
|
|
666
|
-
console.log(
|
|
1252
|
+
console.log();
|
|
1253
|
+
console.log(` ${colors.muted("Badges")}`);
|
|
667
1254
|
console.log(` ${stats.badges.join(" ")}`);
|
|
668
1255
|
}
|
|
669
|
-
console.log(
|
|
670
|
-
console.log(
|
|
1256
|
+
console.log();
|
|
1257
|
+
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1258
|
+
console.log(` ${colors.muted("View leaderboard:")} ${link("https://ccgather.dev/leaderboard")}`);
|
|
1259
|
+
console.log();
|
|
671
1260
|
}
|
|
672
1261
|
|
|
673
1262
|
// src/commands/setup-auto.ts
|
|
674
|
-
var
|
|
1263
|
+
var import_chalk3 = __toESM(require("chalk"));
|
|
675
1264
|
var import_ora4 = __toESM(require("ora"));
|
|
676
1265
|
var http = __toESM(require("http"));
|
|
677
|
-
var
|
|
678
|
-
var
|
|
679
|
-
var
|
|
1266
|
+
var fs5 = __toESM(require("fs"));
|
|
1267
|
+
var path5 = __toESM(require("path"));
|
|
1268
|
+
var os5 = __toESM(require("os"));
|
|
680
1269
|
init_config();
|
|
681
1270
|
init_config();
|
|
682
1271
|
var CALLBACK_PORT = 9876;
|
|
683
1272
|
function getClaudeSettingsDir2() {
|
|
684
|
-
return
|
|
1273
|
+
return path5.join(os5.homedir(), ".claude");
|
|
685
1274
|
}
|
|
686
1275
|
async function openBrowser(url) {
|
|
687
1276
|
const { default: open } = await import("open");
|
|
@@ -695,8 +1284,8 @@ function createCallbackServer() {
|
|
|
695
1284
|
const token = url.searchParams.get("token");
|
|
696
1285
|
const userId = url.searchParams.get("userId");
|
|
697
1286
|
const username = url.searchParams.get("username");
|
|
698
|
-
const
|
|
699
|
-
if (
|
|
1287
|
+
const error2 = url.searchParams.get("error");
|
|
1288
|
+
if (error2) {
|
|
700
1289
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
701
1290
|
res.end(`
|
|
702
1291
|
<html>
|
|
@@ -704,14 +1293,14 @@ function createCallbackServer() {
|
|
|
704
1293
|
<body style="font-family: system-ui; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background: #1a1a1a; color: #fff;">
|
|
705
1294
|
<div style="text-align: center;">
|
|
706
1295
|
<h1 style="color: #ef4444;">\u274C Authentication Failed</h1>
|
|
707
|
-
<p>${
|
|
1296
|
+
<p>${error2}</p>
|
|
708
1297
|
<p style="color: #888;">You can close this window.</p>
|
|
709
1298
|
</div>
|
|
710
1299
|
</body>
|
|
711
1300
|
</html>
|
|
712
1301
|
`);
|
|
713
1302
|
server.close();
|
|
714
|
-
reject(new Error(
|
|
1303
|
+
reject(new Error(error2));
|
|
715
1304
|
return;
|
|
716
1305
|
}
|
|
717
1306
|
if (token && userId && username) {
|
|
@@ -911,14 +1500,14 @@ if (usageData) {
|
|
|
911
1500
|
}
|
|
912
1501
|
function installStopHook() {
|
|
913
1502
|
const claudeDir = getClaudeSettingsDir2();
|
|
914
|
-
const settingsPath =
|
|
915
|
-
if (!
|
|
916
|
-
|
|
1503
|
+
const settingsPath = path5.join(claudeDir, "settings.json");
|
|
1504
|
+
if (!fs5.existsSync(claudeDir)) {
|
|
1505
|
+
fs5.mkdirSync(claudeDir, { recursive: true });
|
|
917
1506
|
}
|
|
918
1507
|
let settings = {};
|
|
919
1508
|
try {
|
|
920
|
-
if (
|
|
921
|
-
const content =
|
|
1509
|
+
if (fs5.existsSync(settingsPath)) {
|
|
1510
|
+
const content = fs5.readFileSync(settingsPath, "utf-8");
|
|
922
1511
|
settings = JSON.parse(content);
|
|
923
1512
|
}
|
|
924
1513
|
} catch {
|
|
@@ -927,7 +1516,7 @@ function installStopHook() {
|
|
|
927
1516
|
settings.hooks = {};
|
|
928
1517
|
}
|
|
929
1518
|
const hooks = settings.hooks;
|
|
930
|
-
const syncScriptPath =
|
|
1519
|
+
const syncScriptPath = path5.join(claudeDir, "ccgather-sync.js");
|
|
931
1520
|
const hookCommand = `node "${syncScriptPath}"`;
|
|
932
1521
|
if (!hooks.Stop || !Array.isArray(hooks.Stop)) {
|
|
933
1522
|
hooks.Stop = [];
|
|
@@ -941,34 +1530,34 @@ function installStopHook() {
|
|
|
941
1530
|
background: true
|
|
942
1531
|
});
|
|
943
1532
|
}
|
|
944
|
-
|
|
1533
|
+
fs5.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
945
1534
|
return { success: true, message: "Stop hook installed" };
|
|
946
1535
|
}
|
|
947
1536
|
function saveSyncScript(apiUrl, apiToken) {
|
|
948
1537
|
const claudeDir = getClaudeSettingsDir2();
|
|
949
|
-
const scriptPath =
|
|
1538
|
+
const scriptPath = path5.join(claudeDir, "ccgather-sync.js");
|
|
950
1539
|
const scriptContent = generateSyncScript(apiUrl, apiToken);
|
|
951
|
-
|
|
952
|
-
if (
|
|
953
|
-
|
|
1540
|
+
fs5.writeFileSync(scriptPath, scriptContent);
|
|
1541
|
+
if (os5.platform() !== "win32") {
|
|
1542
|
+
fs5.chmodSync(scriptPath, "755");
|
|
954
1543
|
}
|
|
955
1544
|
}
|
|
956
1545
|
async function setupAuto(options = {}) {
|
|
957
1546
|
if (options.manual) {
|
|
958
|
-
console.log(
|
|
1547
|
+
console.log(import_chalk3.default.bold("\n\u{1F527} Disabling Auto-Sync\n"));
|
|
959
1548
|
const { reset: reset2 } = await Promise.resolve().then(() => (init_reset(), reset_exports));
|
|
960
1549
|
await reset2();
|
|
961
|
-
console.log(
|
|
1550
|
+
console.log(import_chalk3.default.green("\u2713 Auto-sync disabled. Use `npx ccgather` to submit manually."));
|
|
962
1551
|
return;
|
|
963
1552
|
}
|
|
964
|
-
console.log(
|
|
965
|
-
console.log(
|
|
966
|
-
console.log(
|
|
1553
|
+
console.log(import_chalk3.default.bold("\n\u26A0\uFE0F Auto-Sync Mode (Optional)\n"));
|
|
1554
|
+
console.log(import_chalk3.default.gray("This will install a hook that automatically syncs"));
|
|
1555
|
+
console.log(import_chalk3.default.gray("your usage data when Claude Code sessions end."));
|
|
967
1556
|
console.log();
|
|
968
|
-
console.log(
|
|
1557
|
+
console.log(import_chalk3.default.yellow("Note: Manual submission (`npx ccgather`) is recommended for most users."));
|
|
969
1558
|
console.log();
|
|
970
|
-
const
|
|
971
|
-
const { proceed } = await
|
|
1559
|
+
const inquirer4 = await import("inquirer");
|
|
1560
|
+
const { proceed } = await inquirer4.default.prompt([
|
|
972
1561
|
{
|
|
973
1562
|
type: "confirm",
|
|
974
1563
|
name: "proceed",
|
|
@@ -977,16 +1566,16 @@ async function setupAuto(options = {}) {
|
|
|
977
1566
|
}
|
|
978
1567
|
]);
|
|
979
1568
|
if (!proceed) {
|
|
980
|
-
console.log(
|
|
1569
|
+
console.log(import_chalk3.default.gray("\nSetup cancelled. Use `npx ccgather` to submit manually."));
|
|
981
1570
|
return;
|
|
982
1571
|
}
|
|
983
1572
|
const config = getConfig();
|
|
984
1573
|
const apiUrl = getApiUrl();
|
|
985
|
-
console.log(
|
|
1574
|
+
console.log(import_chalk3.default.bold("\n\u{1F310} CCgather Setup\n"));
|
|
986
1575
|
const existingToken = config.get("apiToken");
|
|
987
1576
|
if (existingToken) {
|
|
988
|
-
const
|
|
989
|
-
const { reconfigure } = await
|
|
1577
|
+
const inquirer5 = await import("inquirer");
|
|
1578
|
+
const { reconfigure } = await inquirer5.default.prompt([
|
|
990
1579
|
{
|
|
991
1580
|
type: "confirm",
|
|
992
1581
|
name: "reconfigure",
|
|
@@ -995,11 +1584,11 @@ async function setupAuto(options = {}) {
|
|
|
995
1584
|
}
|
|
996
1585
|
]);
|
|
997
1586
|
if (!reconfigure) {
|
|
998
|
-
console.log(
|
|
1587
|
+
console.log(import_chalk3.default.gray("Setup cancelled."));
|
|
999
1588
|
return;
|
|
1000
1589
|
}
|
|
1001
1590
|
}
|
|
1002
|
-
console.log(
|
|
1591
|
+
console.log(import_chalk3.default.gray("Opening browser for GitHub authentication...\n"));
|
|
1003
1592
|
const callbackUrl = `http://localhost:${CALLBACK_PORT}/callback`;
|
|
1004
1593
|
const baseUrl = apiUrl.replace("/api", "");
|
|
1005
1594
|
const authUrl = `${baseUrl}/cli/auth?callback=${encodeURIComponent(callbackUrl)}`;
|
|
@@ -1007,15 +1596,15 @@ async function setupAuto(options = {}) {
|
|
|
1007
1596
|
try {
|
|
1008
1597
|
await openBrowser(authUrl);
|
|
1009
1598
|
} catch {
|
|
1010
|
-
console.log(
|
|
1011
|
-
console.log(
|
|
1012
|
-
console.log(
|
|
1599
|
+
console.log(import_chalk3.default.yellow("Could not open browser automatically."));
|
|
1600
|
+
console.log(import_chalk3.default.gray("Please open this URL manually:"));
|
|
1601
|
+
console.log(import_chalk3.default.cyan(authUrl));
|
|
1013
1602
|
console.log();
|
|
1014
1603
|
}
|
|
1015
1604
|
const spinner = (0, import_ora4.default)("Waiting for authentication...").start();
|
|
1016
1605
|
try {
|
|
1017
1606
|
const authData = await serverPromise;
|
|
1018
|
-
spinner.succeed(
|
|
1607
|
+
spinner.succeed(import_chalk3.default.green("Authentication successful!"));
|
|
1019
1608
|
config.set("apiToken", authData.token);
|
|
1020
1609
|
config.set("userId", authData.userId);
|
|
1021
1610
|
const hookSpinner = (0, import_ora4.default)("Installing Claude Code hook...").start();
|
|
@@ -1023,102 +1612,329 @@ async function setupAuto(options = {}) {
|
|
|
1023
1612
|
saveSyncScript(apiUrl, authData.token);
|
|
1024
1613
|
const hookResult = installStopHook();
|
|
1025
1614
|
if (hookResult.success) {
|
|
1026
|
-
hookSpinner.succeed(
|
|
1615
|
+
hookSpinner.succeed(import_chalk3.default.green("Hook installed successfully!"));
|
|
1027
1616
|
} else {
|
|
1028
|
-
hookSpinner.fail(
|
|
1029
|
-
console.log(
|
|
1617
|
+
hookSpinner.fail(import_chalk3.default.red("Failed to install hook"));
|
|
1618
|
+
console.log(import_chalk3.default.red(hookResult.message));
|
|
1030
1619
|
}
|
|
1031
1620
|
} catch (err) {
|
|
1032
|
-
hookSpinner.fail(
|
|
1033
|
-
console.log(
|
|
1621
|
+
hookSpinner.fail(import_chalk3.default.red("Failed to install hook"));
|
|
1622
|
+
console.log(import_chalk3.default.red(err instanceof Error ? err.message : "Unknown error"));
|
|
1034
1623
|
}
|
|
1035
1624
|
console.log();
|
|
1036
|
-
console.log(
|
|
1625
|
+
console.log(import_chalk3.default.green.bold("\u2705 Setup complete!"));
|
|
1037
1626
|
console.log();
|
|
1038
|
-
console.log(
|
|
1627
|
+
console.log(import_chalk3.default.gray(`Welcome, ${import_chalk3.default.white(authData.username)}!`));
|
|
1039
1628
|
console.log();
|
|
1040
|
-
console.log(
|
|
1041
|
-
console.log(
|
|
1629
|
+
console.log(import_chalk3.default.gray("Your Claude Code usage will now be automatically synced"));
|
|
1630
|
+
console.log(import_chalk3.default.gray("to the leaderboard when each session ends."));
|
|
1042
1631
|
console.log();
|
|
1043
|
-
console.log(
|
|
1044
|
-
console.log(
|
|
1632
|
+
console.log(import_chalk3.default.gray("View your stats:"));
|
|
1633
|
+
console.log(import_chalk3.default.cyan(" npx ccgather status"));
|
|
1045
1634
|
console.log();
|
|
1046
|
-
console.log(
|
|
1047
|
-
console.log(
|
|
1635
|
+
console.log(import_chalk3.default.gray("View the leaderboard:"));
|
|
1636
|
+
console.log(import_chalk3.default.cyan(" https://ccgather.dev/leaderboard"));
|
|
1048
1637
|
console.log();
|
|
1049
1638
|
} catch (err) {
|
|
1050
|
-
spinner.fail(
|
|
1051
|
-
console.log(
|
|
1639
|
+
spinner.fail(import_chalk3.default.red("Authentication failed"));
|
|
1640
|
+
console.log(import_chalk3.default.red(err instanceof Error ? err.message : "Unknown error"));
|
|
1052
1641
|
process.exit(1);
|
|
1053
1642
|
}
|
|
1054
1643
|
}
|
|
1055
1644
|
|
|
1056
1645
|
// src/commands/scan.ts
|
|
1057
|
-
var import_chalk5 = __toESM(require("chalk"));
|
|
1058
1646
|
var import_ora5 = __toESM(require("ora"));
|
|
1059
|
-
function formatNumber3(num) {
|
|
1060
|
-
if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
|
|
1061
|
-
if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`;
|
|
1062
|
-
return num.toLocaleString();
|
|
1063
|
-
}
|
|
1064
1647
|
function displayResults(data) {
|
|
1065
|
-
const orange = import_chalk5.default.hex("#FF6B35");
|
|
1066
|
-
const green = import_chalk5.default.hex("#10B981");
|
|
1067
|
-
const gray = import_chalk5.default.gray;
|
|
1068
|
-
const white = import_chalk5.default.white;
|
|
1069
|
-
const bold = import_chalk5.default.bold;
|
|
1070
1648
|
console.log();
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
console.log(
|
|
1084
|
-
console.log(
|
|
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
|
+
}
|
|
1085
1680
|
if (Object.keys(data.models).length > 0) {
|
|
1086
1681
|
console.log();
|
|
1087
|
-
console.log(
|
|
1088
|
-
|
|
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) {
|
|
1089
1686
|
const shortModel = model.replace("claude-", "").substring(0, 20);
|
|
1090
|
-
console.log(
|
|
1687
|
+
console.log(` ${colors.dim("\u2022")} ${colors.white(shortModel.padEnd(22))} ${colors.primary(formatNumber(tokens))}`);
|
|
1688
|
+
}
|
|
1689
|
+
if (Object.keys(data.models).length > 5) {
|
|
1690
|
+
console.log(` ${colors.dim(`... and ${Object.keys(data.models).length - 5} more models`)}`);
|
|
1691
|
+
}
|
|
1692
|
+
}
|
|
1693
|
+
if (data.projects && Object.keys(data.projects).length > 0) {
|
|
1694
|
+
console.log();
|
|
1695
|
+
console.log(` ${colors.white.bold("\u{1F4C1} Top Projects")}`);
|
|
1696
|
+
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1697
|
+
const sortedProjects = Object.entries(data.projects).sort(([, a], [, b]) => b.tokens - a.tokens).slice(0, 5);
|
|
1698
|
+
for (const [name, stats] of sortedProjects) {
|
|
1699
|
+
const displayName = name.length > 20 ? name.substring(0, 17) + "..." : name;
|
|
1700
|
+
console.log(
|
|
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)`)}`
|
|
1702
|
+
);
|
|
1703
|
+
}
|
|
1704
|
+
if (Object.keys(data.projects).length > 5) {
|
|
1705
|
+
console.log(` ${colors.dim(`... and ${Object.keys(data.projects).length - 5} more projects`)}`);
|
|
1091
1706
|
}
|
|
1092
1707
|
}
|
|
1093
1708
|
console.log();
|
|
1094
|
-
console.log(
|
|
1709
|
+
console.log(colors.dim(" \u2500".repeat(25)));
|
|
1710
|
+
console.log(` ${colors.muted("Saved to:")} ${colors.dim(getCCGatherJsonPath())}`);
|
|
1095
1711
|
console.log();
|
|
1096
1712
|
}
|
|
1097
1713
|
async function scan() {
|
|
1098
|
-
|
|
1099
|
-
|
|
1714
|
+
printCompactHeader("1.2.0");
|
|
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();
|
|
1100
1720
|
const data = scanAndSave();
|
|
1101
1721
|
if (!data) {
|
|
1102
|
-
spinner.fail(
|
|
1103
|
-
console.log(
|
|
1104
|
-
console.log(
|
|
1105
|
-
console.log(
|
|
1106
|
-
console.log(
|
|
1722
|
+
spinner.fail(colors.error("No usage data found"));
|
|
1723
|
+
console.log();
|
|
1724
|
+
console.log(` ${error("Possible reasons:")}`);
|
|
1725
|
+
console.log(` ${colors.dim("\u2022")} Claude Code has not been used yet`);
|
|
1726
|
+
console.log(` ${colors.dim("\u2022")} ~/.claude/projects/ directory is empty`);
|
|
1727
|
+
console.log(` ${colors.dim("\u2022")} No permission to read files`);
|
|
1728
|
+
console.log();
|
|
1107
1729
|
process.exit(1);
|
|
1108
1730
|
}
|
|
1109
|
-
spinner.succeed(
|
|
1731
|
+
spinner.succeed(colors.success("Scan complete!"));
|
|
1110
1732
|
displayResults(data);
|
|
1733
|
+
console.log(` ${colors.muted("Next:")} Run ${colors.white("npx ccgather")} to submit your data`);
|
|
1734
|
+
console.log();
|
|
1111
1735
|
}
|
|
1112
1736
|
|
|
1113
1737
|
// src/index.ts
|
|
1738
|
+
init_config();
|
|
1739
|
+
init_api();
|
|
1740
|
+
var VERSION = "1.2.0";
|
|
1114
1741
|
var program = new import_commander.Command();
|
|
1115
|
-
program.name("ccgather").description("Submit your Claude Code usage to the CCgather leaderboard").version(
|
|
1742
|
+
program.name("ccgather").description("Submit your Claude Code usage to the CCgather leaderboard").version(VERSION).option("-y, --yes", "Skip confirmation prompt").option("--auto", "Enable automatic sync on session end").option("--manual", "Disable automatic sync").option("--no-menu", "Skip interactive menu (direct submit)");
|
|
1116
1743
|
program.command("scan").description("Scan Claude Code usage and create ccgather.json").action(scan);
|
|
1117
|
-
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
|
+
});
|
|
1118
1754
|
program.command("reset").description("Remove auto-sync hook and clear config").action(async () => {
|
|
1119
1755
|
const { reset: reset2 } = await Promise.resolve().then(() => (init_reset(), reset_exports));
|
|
1120
1756
|
await reset2();
|
|
1121
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
|
+
}
|
|
1122
1938
|
program.action(async (options) => {
|
|
1123
1939
|
if (options.auto) {
|
|
1124
1940
|
await setupAuto({ auto: true });
|
|
@@ -1128,6 +1944,10 @@ program.action(async (options) => {
|
|
|
1128
1944
|
await setupAuto({ manual: true });
|
|
1129
1945
|
return;
|
|
1130
1946
|
}
|
|
1131
|
-
|
|
1947
|
+
if (options.menu === false) {
|
|
1948
|
+
await submit({ yes: options.yes });
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
await showInteractiveMenu();
|
|
1132
1952
|
});
|
|
1133
1953
|
program.parse();
|