ccgather 1.3.1 → 1.3.3
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 +1111 -1058
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -70,1197 +70,1230 @@ var init_config = __esm({
|
|
|
70
70
|
}
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
// src/lib/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
if (!
|
|
79
|
-
return
|
|
73
|
+
// src/lib/credentials.ts
|
|
74
|
+
function getCredentialsPath() {
|
|
75
|
+
return path.join(os.homedir(), ".claude", ".credentials.json");
|
|
76
|
+
}
|
|
77
|
+
function mapSubscriptionToCCPlan(subscriptionType) {
|
|
78
|
+
if (!subscriptionType) {
|
|
79
|
+
return "free";
|
|
80
80
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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 };
|
|
81
|
+
const type = subscriptionType.toLowerCase();
|
|
82
|
+
if (type === "max" || type.includes("max")) {
|
|
83
|
+
return "max";
|
|
98
84
|
}
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
var init_api = __esm({
|
|
110
|
-
"src/lib/api.ts"() {
|
|
111
|
-
"use strict";
|
|
112
|
-
init_config();
|
|
85
|
+
if (type === "pro") {
|
|
86
|
+
return "pro";
|
|
113
87
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
__export(reset_exports, {
|
|
119
|
-
reset: () => reset
|
|
120
|
-
});
|
|
121
|
-
function getClaudeSettingsDir() {
|
|
122
|
-
return path4.join(os4.homedir(), ".claude");
|
|
88
|
+
if (type === "free") {
|
|
89
|
+
return "free";
|
|
90
|
+
}
|
|
91
|
+
return type;
|
|
123
92
|
}
|
|
124
|
-
function
|
|
125
|
-
const
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
93
|
+
function readCredentials() {
|
|
94
|
+
const credentialsPath = getCredentialsPath();
|
|
95
|
+
const defaultData = {
|
|
96
|
+
ccplan: null,
|
|
97
|
+
rateLimitTier: null
|
|
98
|
+
};
|
|
99
|
+
if (!fs.existsSync(credentialsPath)) {
|
|
100
|
+
return defaultData;
|
|
129
101
|
}
|
|
130
102
|
try {
|
|
131
|
-
const content =
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
hooks.Stop = hooks.Stop.filter((hook) => {
|
|
137
|
-
if (typeof hook === "object" && hook !== null) {
|
|
138
|
-
const h = hook;
|
|
139
|
-
return typeof h.command !== "string" || !h.command.includes("ccgather");
|
|
140
|
-
}
|
|
141
|
-
return true;
|
|
142
|
-
});
|
|
143
|
-
if (hooks.Stop.length === 0) {
|
|
144
|
-
delete hooks.Stop;
|
|
145
|
-
}
|
|
146
|
-
}
|
|
103
|
+
const content = fs.readFileSync(credentialsPath, "utf-8");
|
|
104
|
+
const credentials = JSON.parse(content);
|
|
105
|
+
const oauthData = credentials.claudeAiOauth;
|
|
106
|
+
if (!oauthData) {
|
|
107
|
+
return defaultData;
|
|
147
108
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
} catch (err) {
|
|
109
|
+
const ccplan = mapSubscriptionToCCPlan(oauthData.subscriptionType);
|
|
110
|
+
const rateLimitTier = oauthData.rateLimitTier || null;
|
|
151
111
|
return {
|
|
152
|
-
|
|
153
|
-
|
|
112
|
+
ccplan,
|
|
113
|
+
rateLimitTier
|
|
154
114
|
};
|
|
115
|
+
} catch (error2) {
|
|
116
|
+
return defaultData;
|
|
155
117
|
}
|
|
156
118
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
async function reset() {
|
|
165
|
-
const config = getConfig();
|
|
166
|
-
console.log(import_chalk2.default.bold("\n\u{1F504} CCgather Reset\n"));
|
|
167
|
-
if (!config.get("apiToken")) {
|
|
168
|
-
console.log(import_chalk2.default.yellow("CCgather is not configured."));
|
|
169
|
-
return;
|
|
119
|
+
var fs, path, os;
|
|
120
|
+
var init_credentials = __esm({
|
|
121
|
+
"src/lib/credentials.ts"() {
|
|
122
|
+
"use strict";
|
|
123
|
+
fs = __toESM(require("fs"));
|
|
124
|
+
path = __toESM(require("path"));
|
|
125
|
+
os = __toESM(require("os"));
|
|
170
126
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// src/lib/ccgather-json.ts
|
|
130
|
+
function extractProjectName(filePath) {
|
|
131
|
+
const parts = filePath.split(/[/\\]/);
|
|
132
|
+
const projectsIndex = parts.findIndex((p) => p === "projects");
|
|
133
|
+
if (projectsIndex >= 0 && parts[projectsIndex + 1]) {
|
|
134
|
+
try {
|
|
135
|
+
const encoded = parts[projectsIndex + 1];
|
|
136
|
+
const decoded = decodeURIComponent(encoded);
|
|
137
|
+
const pathParts = decoded.split(/[/\\]/);
|
|
138
|
+
return pathParts[pathParts.length - 1] || decoded;
|
|
139
|
+
} catch {
|
|
140
|
+
return parts[projectsIndex + 1];
|
|
177
141
|
}
|
|
178
|
-
]);
|
|
179
|
-
if (!confirmReset) {
|
|
180
|
-
console.log(import_chalk2.default.gray("Reset cancelled."));
|
|
181
|
-
return;
|
|
182
|
-
}
|
|
183
|
-
const hookSpinner = (0, import_ora3.default)("Removing Claude Code hook...").start();
|
|
184
|
-
const hookResult = removeStopHook();
|
|
185
|
-
if (hookResult.success) {
|
|
186
|
-
hookSpinner.succeed(import_chalk2.default.green("Hook removed"));
|
|
187
|
-
} else {
|
|
188
|
-
hookSpinner.warn(import_chalk2.default.yellow(`Could not remove hook: ${hookResult.message}`));
|
|
189
142
|
}
|
|
190
|
-
|
|
143
|
+
return "unknown";
|
|
144
|
+
}
|
|
145
|
+
function getCCGatherJsonPath() {
|
|
146
|
+
return path2.join(os2.homedir(), ".claude", "ccgather.json");
|
|
147
|
+
}
|
|
148
|
+
function getClaudeProjectsDir() {
|
|
149
|
+
return path2.join(os2.homedir(), ".claude", "projects");
|
|
150
|
+
}
|
|
151
|
+
function findJsonlFiles(dir) {
|
|
152
|
+
const files = [];
|
|
191
153
|
try {
|
|
192
|
-
|
|
193
|
-
|
|
154
|
+
const entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
155
|
+
for (const entry of entries) {
|
|
156
|
+
const fullPath = path2.join(dir, entry.name);
|
|
157
|
+
if (entry.isDirectory()) {
|
|
158
|
+
files.push(...findJsonlFiles(fullPath));
|
|
159
|
+
} else if (entry.name.endsWith(".jsonl")) {
|
|
160
|
+
files.push(fullPath);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
194
163
|
} catch {
|
|
195
|
-
scriptSpinner.warn(import_chalk2.default.yellow("Could not remove sync script"));
|
|
196
164
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
165
|
+
return files;
|
|
166
|
+
}
|
|
167
|
+
function estimateCost(model, inputTokens, outputTokens) {
|
|
168
|
+
const pricing = {
|
|
169
|
+
"claude-opus-4": { input: 15, output: 75 },
|
|
170
|
+
"claude-sonnet-4": { input: 3, output: 15 },
|
|
171
|
+
"claude-haiku": { input: 0.25, output: 1.25 },
|
|
172
|
+
default: { input: 3, output: 15 }
|
|
173
|
+
};
|
|
174
|
+
let modelKey = "default";
|
|
175
|
+
for (const key of Object.keys(pricing)) {
|
|
176
|
+
if (model.includes(key.replace("claude-", ""))) {
|
|
177
|
+
modelKey = key;
|
|
178
|
+
break;
|
|
203
179
|
}
|
|
204
|
-
]);
|
|
205
|
-
if (deleteAccount) {
|
|
206
|
-
console.log(import_chalk2.default.yellow("\nAccount deletion is not yet implemented."));
|
|
207
|
-
console.log(import_chalk2.default.gray("Please contact support to delete your account."));
|
|
208
180
|
}
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
console.log(import_chalk2.default.green.bold("\u2705 Reset complete!"));
|
|
214
|
-
console.log();
|
|
215
|
-
console.log(import_chalk2.default.gray("Your usage will no longer be tracked."));
|
|
216
|
-
console.log(import_chalk2.default.gray("Run `npx ccgather` to set up again."));
|
|
217
|
-
console.log();
|
|
181
|
+
const price = pricing[modelKey];
|
|
182
|
+
const inputCost = inputTokens / 1e6 * price.input;
|
|
183
|
+
const outputCost = outputTokens / 1e6 * price.output;
|
|
184
|
+
return Math.round((inputCost + outputCost) * 100) / 100;
|
|
218
185
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
import_chalk2 = __toESM(require("chalk"));
|
|
224
|
-
import_ora3 = __toESM(require("ora"));
|
|
225
|
-
fs4 = __toESM(require("fs"));
|
|
226
|
-
path4 = __toESM(require("path"));
|
|
227
|
-
os4 = __toESM(require("os"));
|
|
228
|
-
import_inquirer = __toESM(require("inquirer"));
|
|
229
|
-
init_config();
|
|
186
|
+
function scanUsageData(options = {}) {
|
|
187
|
+
const projectsDir = getClaudeProjectsDir();
|
|
188
|
+
if (!fs2.existsSync(projectsDir)) {
|
|
189
|
+
return null;
|
|
230
190
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
} else if (platform3 === "darwin") {
|
|
239
|
-
return path6.join(os6.homedir(), "Library", "Application Support", "claude-code");
|
|
240
|
-
} else {
|
|
241
|
-
return path6.join(os6.homedir(), ".config", "claude-code");
|
|
191
|
+
const days = options.days ?? 30;
|
|
192
|
+
let cutoffDate = null;
|
|
193
|
+
if (days > 0) {
|
|
194
|
+
const cutoff = /* @__PURE__ */ new Date();
|
|
195
|
+
cutoff.setDate(cutoff.getDate() - days);
|
|
196
|
+
cutoff.setHours(0, 0, 0, 0);
|
|
197
|
+
cutoffDate = cutoff.toISOString();
|
|
242
198
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
256
|
-
|
|
199
|
+
let totalInputTokens = 0;
|
|
200
|
+
let totalOutputTokens = 0;
|
|
201
|
+
let totalCacheRead = 0;
|
|
202
|
+
let totalCacheWrite = 0;
|
|
203
|
+
let totalCost = 0;
|
|
204
|
+
let sessionsCount = 0;
|
|
205
|
+
const dates = /* @__PURE__ */ new Set();
|
|
206
|
+
const models = {};
|
|
207
|
+
const projects = {};
|
|
208
|
+
const dailyData = {};
|
|
209
|
+
let firstTimestamp = null;
|
|
210
|
+
let lastTimestamp = null;
|
|
211
|
+
const jsonlFiles = findJsonlFiles(projectsDir);
|
|
212
|
+
sessionsCount = jsonlFiles.length;
|
|
213
|
+
for (const filePath of jsonlFiles) {
|
|
214
|
+
const projectName = extractProjectName(filePath);
|
|
215
|
+
if (!projects[projectName]) {
|
|
216
|
+
projects[projectName] = {
|
|
217
|
+
tokens: 0,
|
|
218
|
+
cost: 0,
|
|
219
|
+
sessions: 0,
|
|
220
|
+
models: {}
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
projects[projectName].sessions++;
|
|
257
224
|
try {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
225
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
226
|
+
const lines = content.split("\n").filter((line) => line.trim());
|
|
227
|
+
for (const line of lines) {
|
|
228
|
+
try {
|
|
229
|
+
const event = JSON.parse(line);
|
|
230
|
+
if (event.type === "assistant" && event.message?.usage) {
|
|
231
|
+
if (cutoffDate && event.timestamp && event.timestamp < cutoffDate) {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
const usage = event.message.usage;
|
|
235
|
+
const model = event.message.model || "unknown";
|
|
236
|
+
const inputTokens = usage.input_tokens || 0;
|
|
237
|
+
const outputTokens = usage.output_tokens || 0;
|
|
238
|
+
totalInputTokens += inputTokens;
|
|
239
|
+
totalOutputTokens += outputTokens;
|
|
240
|
+
totalCacheRead += usage.cache_read_input_tokens || 0;
|
|
241
|
+
totalCacheWrite += usage.cache_creation_input_tokens || 0;
|
|
242
|
+
const messageCost = estimateCost(model, inputTokens, outputTokens);
|
|
243
|
+
totalCost += messageCost;
|
|
244
|
+
const totalModelTokens = inputTokens + outputTokens;
|
|
245
|
+
models[model] = (models[model] || 0) + totalModelTokens;
|
|
246
|
+
projects[projectName].tokens += totalModelTokens;
|
|
247
|
+
projects[projectName].cost += messageCost;
|
|
248
|
+
projects[projectName].models[model] = (projects[projectName].models[model] || 0) + totalModelTokens;
|
|
249
|
+
if (event.timestamp) {
|
|
250
|
+
const date = new Date(event.timestamp).toISOString().split("T")[0];
|
|
251
|
+
dates.add(date);
|
|
252
|
+
if (!dailyData[date]) {
|
|
253
|
+
dailyData[date] = {
|
|
254
|
+
tokens: 0,
|
|
255
|
+
cost: 0,
|
|
256
|
+
inputTokens: 0,
|
|
257
|
+
outputTokens: 0,
|
|
258
|
+
sessions: /* @__PURE__ */ new Set(),
|
|
259
|
+
models: {}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
dailyData[date].tokens += totalModelTokens;
|
|
263
|
+
dailyData[date].cost += messageCost;
|
|
264
|
+
dailyData[date].inputTokens += inputTokens;
|
|
265
|
+
dailyData[date].outputTokens += outputTokens;
|
|
266
|
+
dailyData[date].sessions.add(filePath);
|
|
267
|
+
dailyData[date].models[model] = (dailyData[date].models[model] || 0) + totalModelTokens;
|
|
268
|
+
if (!firstTimestamp || event.timestamp < firstTimestamp) {
|
|
269
|
+
firstTimestamp = event.timestamp;
|
|
270
|
+
}
|
|
271
|
+
if (!lastTimestamp || event.timestamp > lastTimestamp) {
|
|
272
|
+
lastTimestamp = event.timestamp;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
277
275
|
}
|
|
278
|
-
|
|
279
|
-
totalTokens,
|
|
280
|
-
totalSpent,
|
|
281
|
-
modelBreakdown: breakdown,
|
|
282
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
283
|
-
};
|
|
276
|
+
} catch {
|
|
284
277
|
}
|
|
285
278
|
}
|
|
286
|
-
} catch
|
|
279
|
+
} catch {
|
|
287
280
|
}
|
|
288
281
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
282
|
+
const totalTokens = totalInputTokens + totalOutputTokens;
|
|
283
|
+
if (totalTokens === 0) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
for (const projectName of Object.keys(projects)) {
|
|
287
|
+
projects[projectName].cost = Math.round(projects[projectName].cost * 100) / 100;
|
|
288
|
+
}
|
|
289
|
+
const dailyUsage = Object.entries(dailyData).map(([date, data]) => ({
|
|
290
|
+
date,
|
|
291
|
+
tokens: data.tokens,
|
|
292
|
+
cost: Math.round(data.cost * 100) / 100,
|
|
293
|
+
inputTokens: data.inputTokens,
|
|
294
|
+
outputTokens: data.outputTokens,
|
|
295
|
+
sessions: data.sessions.size,
|
|
296
|
+
models: data.models
|
|
297
|
+
})).sort((a, b) => a.date.localeCompare(b.date));
|
|
298
|
+
const credentials = readCredentials();
|
|
296
299
|
return {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
300
|
+
version: CCGATHER_JSON_VERSION,
|
|
301
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
302
|
+
lastScanned: (/* @__PURE__ */ new Date()).toISOString(),
|
|
303
|
+
usage: {
|
|
304
|
+
totalTokens,
|
|
305
|
+
totalCost: Math.round(totalCost * 100) / 100,
|
|
306
|
+
inputTokens: totalInputTokens,
|
|
307
|
+
outputTokens: totalOutputTokens,
|
|
308
|
+
cacheReadTokens: totalCacheRead,
|
|
309
|
+
cacheWriteTokens: totalCacheWrite
|
|
303
310
|
},
|
|
304
|
-
|
|
311
|
+
stats: {
|
|
312
|
+
daysTracked: dates.size,
|
|
313
|
+
sessionsCount,
|
|
314
|
+
firstUsed: firstTimestamp ? new Date(firstTimestamp).toISOString().split("T")[0] : null,
|
|
315
|
+
lastUsed: lastTimestamp ? new Date(lastTimestamp).toISOString().split("T")[0] : null
|
|
316
|
+
},
|
|
317
|
+
models,
|
|
318
|
+
projects,
|
|
319
|
+
dailyUsage,
|
|
320
|
+
account: {
|
|
321
|
+
ccplan: credentials.ccplan,
|
|
322
|
+
rateLimitTier: credentials.rateLimitTier
|
|
323
|
+
}
|
|
305
324
|
};
|
|
306
325
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
326
|
+
function readCCGatherJson() {
|
|
327
|
+
const jsonPath = getCCGatherJsonPath();
|
|
328
|
+
if (!fs2.existsSync(jsonPath)) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
try {
|
|
332
|
+
const content = fs2.readFileSync(jsonPath, "utf-8");
|
|
333
|
+
return JSON.parse(content);
|
|
334
|
+
} catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
function writeCCGatherJson(data) {
|
|
339
|
+
const jsonPath = getCCGatherJsonPath();
|
|
340
|
+
const claudeDir = path2.dirname(jsonPath);
|
|
341
|
+
if (!fs2.existsSync(claudeDir)) {
|
|
342
|
+
fs2.mkdirSync(claudeDir, { recursive: true });
|
|
343
|
+
}
|
|
344
|
+
fs2.writeFileSync(jsonPath, JSON.stringify(data, null, 2));
|
|
345
|
+
}
|
|
346
|
+
function scanAndSave(options = {}) {
|
|
347
|
+
const data = scanUsageData(options);
|
|
348
|
+
if (data) {
|
|
349
|
+
writeCCGatherJson(data);
|
|
350
|
+
}
|
|
351
|
+
return data;
|
|
352
|
+
}
|
|
353
|
+
var fs2, path2, os2, CCGATHER_JSON_VERSION;
|
|
354
|
+
var init_ccgather_json = __esm({
|
|
355
|
+
"src/lib/ccgather-json.ts"() {
|
|
310
356
|
"use strict";
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
357
|
+
fs2 = __toESM(require("fs"));
|
|
358
|
+
path2 = __toESM(require("path"));
|
|
359
|
+
os2 = __toESM(require("os"));
|
|
360
|
+
init_credentials();
|
|
361
|
+
CCGATHER_JSON_VERSION = "1.2.0";
|
|
314
362
|
}
|
|
315
363
|
});
|
|
316
364
|
|
|
317
|
-
// src/
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
365
|
+
// src/lib/ui.ts
|
|
366
|
+
function getVersionLine(version) {
|
|
367
|
+
return colors.dim(` v${version} \u2022 ccgather.com`);
|
|
368
|
+
}
|
|
369
|
+
function createBox(lines, width = 47) {
|
|
370
|
+
const paddedLines = lines.map((line) => {
|
|
371
|
+
const visibleLength = stripAnsi(line).length;
|
|
372
|
+
const padding = width - 2 - visibleLength;
|
|
373
|
+
return `${box.vertical} ${line}${" ".repeat(Math.max(0, padding))} ${box.vertical}`;
|
|
374
|
+
});
|
|
375
|
+
const top = colors.dim(` ${box.topLeft}${box.horizontal.repeat(width)}${box.topRight}`);
|
|
376
|
+
const bottom = colors.dim(` ${box.bottomLeft}${box.horizontal.repeat(width)}${box.bottomRight}`);
|
|
377
|
+
return [top, ...paddedLines.map((l) => colors.dim(" ") + l), bottom].join("\n");
|
|
378
|
+
}
|
|
379
|
+
function stripAnsi(str) {
|
|
380
|
+
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
381
|
+
}
|
|
382
|
+
function header(title, icon = "") {
|
|
383
|
+
const iconPart = icon ? `${icon} ` : "";
|
|
384
|
+
return `
|
|
385
|
+
${colors.primary("\u2501".repeat(50))}
|
|
386
|
+
${iconPart}${colors.white.bold(title)}
|
|
387
|
+
${colors.primary("\u2501".repeat(50))}`;
|
|
388
|
+
}
|
|
389
|
+
function formatNumber(num) {
|
|
390
|
+
if (num >= 1e9) return `${(num / 1e9).toFixed(2)}B`;
|
|
323
391
|
if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
|
|
324
392
|
if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`;
|
|
325
|
-
return num.
|
|
393
|
+
return num.toLocaleString();
|
|
326
394
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
if (
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
395
|
+
function formatCost(cost) {
|
|
396
|
+
return `$${cost.toLocaleString(void 0, { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
|
|
397
|
+
}
|
|
398
|
+
function getRankMedal(rank) {
|
|
399
|
+
if (rank === 1) return "\u{1F947}";
|
|
400
|
+
if (rank === 2) return "\u{1F948}";
|
|
401
|
+
if (rank === 3) return "\u{1F949}";
|
|
402
|
+
if (rank <= 10) return "\u{1F3C5}";
|
|
403
|
+
if (rank <= 100) return "\u{1F396}\uFE0F";
|
|
404
|
+
return "\u{1F4CA}";
|
|
405
|
+
}
|
|
406
|
+
function getCCplanBadge(ccplan) {
|
|
407
|
+
if (!ccplan) return "";
|
|
408
|
+
const badges = {
|
|
409
|
+
max: `${colors.max("\u{1F680} MAX")}`,
|
|
410
|
+
pro: `${colors.pro("\u26A1 PRO")}`,
|
|
411
|
+
team: `${colors.team("\u{1F465} TEAM")}`,
|
|
412
|
+
free: `${colors.free("\u26AA FREE")}`
|
|
413
|
+
};
|
|
414
|
+
return badges[ccplan.toLowerCase()] || "";
|
|
415
|
+
}
|
|
416
|
+
function getLevelInfo(tokens) {
|
|
417
|
+
const levels = [
|
|
418
|
+
{ min: 0, level: 1, name: "Novice", icon: "\u{1F331}", color: colors.dim },
|
|
419
|
+
{ min: 1e5, level: 2, name: "Apprentice", icon: "\u{1F4DA}", color: colors.muted },
|
|
420
|
+
{ min: 5e5, level: 3, name: "Journeyman", icon: "\u26A1", color: colors.cyan },
|
|
421
|
+
{ min: 1e6, level: 4, name: "Expert", icon: "\u{1F48E}", color: colors.pro },
|
|
422
|
+
{ min: 5e6, level: 5, name: "Master", icon: "\u{1F525}", color: colors.warning },
|
|
423
|
+
{ min: 1e7, level: 6, name: "Grandmaster", icon: "\u{1F451}", color: colors.max },
|
|
424
|
+
{ min: 5e7, level: 7, name: "Legend", icon: "\u{1F31F}", color: colors.primary },
|
|
425
|
+
{ min: 1e8, level: 8, name: "Mythic", icon: "\u{1F3C6}", color: colors.secondary }
|
|
426
|
+
];
|
|
427
|
+
for (let i = levels.length - 1; i >= 0; i--) {
|
|
428
|
+
if (tokens >= levels[i].min) {
|
|
429
|
+
return levels[i];
|
|
430
|
+
}
|
|
360
431
|
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
432
|
+
return levels[0];
|
|
433
|
+
}
|
|
434
|
+
function createWelcomeBox(user) {
|
|
435
|
+
const levelInfo = user.level && user.levelName && user.levelIcon ? `${user.levelIcon} Level ${user.level} \u2022 ${user.levelName}` : "";
|
|
436
|
+
const ccplanBadge = user.ccplan ? getCCplanBadge(user.ccplan) : "";
|
|
437
|
+
const lines = [`\u{1F44B} ${colors.white.bold(`Welcome back, ${user.username}!`)}`];
|
|
438
|
+
if (levelInfo || ccplanBadge) {
|
|
439
|
+
lines.push(`${levelInfo}${ccplanBadge ? ` ${ccplanBadge}` : ""}`);
|
|
368
440
|
}
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
totalTokens: usageData.totalTokens,
|
|
372
|
-
totalSpent: usageData.totalSpent,
|
|
373
|
-
modelBreakdown: usageData.modelBreakdown,
|
|
374
|
-
timestamp: usageData.lastUpdated
|
|
375
|
-
});
|
|
376
|
-
if (!result.success) {
|
|
377
|
-
spinner.fail(import_chalk4.default.red("Sync failed"));
|
|
378
|
-
console.log(import_chalk4.default.red(`Error: ${result.error}
|
|
379
|
-
`));
|
|
380
|
-
process.exit(1);
|
|
441
|
+
if (user.globalRank) {
|
|
442
|
+
lines.push(`\u{1F30D} Global Rank: ${colors.primary(`#${user.globalRank}`)}`);
|
|
381
443
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
console.log(import_chalk4.default.bold("\u{1F4CA} Your Stats"));
|
|
386
|
-
console.log(import_chalk4.default.gray("\u2500".repeat(40)));
|
|
387
|
-
console.log(` ${import_chalk4.default.gray("Tokens:")} ${import_chalk4.default.white(formatNumber2(usageData.totalTokens))}`);
|
|
388
|
-
console.log(` ${import_chalk4.default.gray("Spent:")} ${import_chalk4.default.green("$" + usageData.totalSpent.toFixed(2))}`);
|
|
389
|
-
console.log(` ${import_chalk4.default.gray("Rank:")} ${import_chalk4.default.yellow("#" + result.data?.rank)}`);
|
|
390
|
-
if (options.verbose) {
|
|
391
|
-
console.log("\n" + import_chalk4.default.gray("Model Breakdown:"));
|
|
392
|
-
for (const [model, tokens] of Object.entries(usageData.modelBreakdown)) {
|
|
393
|
-
const shortModel = model.replace("claude-", "").replace(/-\d+$/, "");
|
|
394
|
-
console.log(` ${import_chalk4.default.gray(shortModel + ":")} ${formatNumber2(tokens)}`);
|
|
395
|
-
}
|
|
444
|
+
if (user.countryRank && user.countryCode) {
|
|
445
|
+
const flag = countryCodeToFlag(user.countryCode);
|
|
446
|
+
lines.push(`${flag} Country Rank: ${colors.primary(`#${user.countryRank}`)}`);
|
|
396
447
|
}
|
|
397
|
-
|
|
398
|
-
console.log(import_chalk4.default.gray("\nView full leaderboard: https://ccgather.com/leaderboard\n"));
|
|
448
|
+
return createBox(lines);
|
|
399
449
|
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
"
|
|
450
|
+
function countryCodeToFlag(countryCode) {
|
|
451
|
+
if (!countryCode || countryCode.length !== 2) return "\u{1F310}";
|
|
452
|
+
const codePoints = countryCode.toUpperCase().split("").map((char) => 127462 + char.charCodeAt(0) - 65);
|
|
453
|
+
return String.fromCodePoint(...codePoints);
|
|
454
|
+
}
|
|
455
|
+
function success(message) {
|
|
456
|
+
return `${colors.success("\u2713")} ${message}`;
|
|
457
|
+
}
|
|
458
|
+
function error(message) {
|
|
459
|
+
return `${colors.error("\u2717")} ${message}`;
|
|
460
|
+
}
|
|
461
|
+
function printHeader(version) {
|
|
462
|
+
console.log(LOGO);
|
|
463
|
+
console.log(TAGLINE);
|
|
464
|
+
console.log(SLOGAN);
|
|
465
|
+
console.log();
|
|
466
|
+
console.log(getVersionLine(version));
|
|
467
|
+
console.log();
|
|
468
|
+
}
|
|
469
|
+
function printCompactHeader(version) {
|
|
470
|
+
console.log();
|
|
471
|
+
console.log(LOGO_COMPACT);
|
|
472
|
+
console.log(colors.dim(` v${version}`));
|
|
473
|
+
console.log();
|
|
474
|
+
}
|
|
475
|
+
function link(url) {
|
|
476
|
+
return colors.cyan.underline(url);
|
|
477
|
+
}
|
|
478
|
+
var import_chalk, colors, LOGO, LOGO_COMPACT, TAGLINE, SLOGAN, box;
|
|
479
|
+
var init_ui = __esm({
|
|
480
|
+
"src/lib/ui.ts"() {
|
|
403
481
|
"use strict";
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
482
|
+
import_chalk = __toESM(require("chalk"));
|
|
483
|
+
colors = {
|
|
484
|
+
primary: import_chalk.default.hex("#DA7756"),
|
|
485
|
+
// Claude coral
|
|
486
|
+
secondary: import_chalk.default.hex("#F7931E"),
|
|
487
|
+
// Orange accent
|
|
488
|
+
success: import_chalk.default.hex("#22C55E"),
|
|
489
|
+
// Green
|
|
490
|
+
warning: import_chalk.default.hex("#F59E0B"),
|
|
491
|
+
// Amber
|
|
492
|
+
error: import_chalk.default.hex("#EF4444"),
|
|
493
|
+
// Red
|
|
494
|
+
muted: import_chalk.default.hex("#71717A"),
|
|
495
|
+
// Gray
|
|
496
|
+
dim: import_chalk.default.hex("#52525B"),
|
|
497
|
+
// Dark gray
|
|
498
|
+
white: import_chalk.default.white,
|
|
499
|
+
cyan: import_chalk.default.cyan,
|
|
500
|
+
// CCplan colors
|
|
501
|
+
max: import_chalk.default.hex("#F59E0B"),
|
|
502
|
+
// Gold
|
|
503
|
+
pro: import_chalk.default.hex("#3B82F6"),
|
|
504
|
+
// Blue
|
|
505
|
+
team: import_chalk.default.hex("#8B5CF6"),
|
|
506
|
+
// Purple
|
|
507
|
+
free: import_chalk.default.hex("#6B7280")
|
|
508
|
+
// Gray
|
|
509
|
+
};
|
|
510
|
+
LOGO = `
|
|
511
|
+
${colors.primary("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557")} ${colors.secondary("\u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557 \u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}
|
|
512
|
+
${colors.primary("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D")}${colors.secondary("\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}
|
|
513
|
+
${colors.primary("\u2588\u2588\u2551 \u2588\u2588\u2551 ")}${colors.secondary("\u2588\u2588\u2551 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D")}
|
|
514
|
+
${colors.primary("\u2588\u2588\u2551 \u2588\u2588\u2551 ")}${colors.secondary("\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u255D \u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557")}
|
|
515
|
+
${colors.primary("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557")}${colors.secondary("\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551")}
|
|
516
|
+
${colors.primary("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D")} ${colors.secondary("\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D")}
|
|
517
|
+
`;
|
|
518
|
+
LOGO_COMPACT = `
|
|
519
|
+
${colors.primary("CC")}${colors.secondary("gather")} ${colors.muted("- Where Claude Code Developers Gather")}
|
|
520
|
+
`;
|
|
521
|
+
TAGLINE = colors.muted(" Where Claude Code Developers Gather");
|
|
522
|
+
SLOGAN = colors.dim(" Gather. Compete. Rise.");
|
|
523
|
+
box = {
|
|
524
|
+
topLeft: "\u250C",
|
|
525
|
+
topRight: "\u2510",
|
|
526
|
+
bottomLeft: "\u2514",
|
|
527
|
+
bottomRight: "\u2518",
|
|
528
|
+
horizontal: "\u2500",
|
|
529
|
+
vertical: "\u2502",
|
|
530
|
+
leftT: "\u251C",
|
|
531
|
+
rightT: "\u2524"
|
|
532
|
+
};
|
|
409
533
|
}
|
|
410
534
|
});
|
|
411
535
|
|
|
412
|
-
// src/commands/
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
536
|
+
// src/commands/submit.ts
|
|
537
|
+
function ccgatherToUsageData(data) {
|
|
538
|
+
return {
|
|
539
|
+
totalTokens: data.usage.totalTokens,
|
|
540
|
+
totalCost: data.usage.totalCost,
|
|
541
|
+
inputTokens: data.usage.inputTokens,
|
|
542
|
+
outputTokens: data.usage.outputTokens,
|
|
543
|
+
cacheReadTokens: data.usage.cacheReadTokens,
|
|
544
|
+
cacheWriteTokens: data.usage.cacheWriteTokens,
|
|
545
|
+
daysTracked: data.stats.daysTracked,
|
|
546
|
+
ccplan: data.account?.ccplan || null,
|
|
547
|
+
rateLimitTier: data.account?.rateLimitTier || null
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
function findCcJson() {
|
|
551
|
+
const possiblePaths = [
|
|
552
|
+
path3.join(process.cwd(), "cc.json"),
|
|
553
|
+
path3.join(os3.homedir(), "cc.json"),
|
|
554
|
+
path3.join(os3.homedir(), ".claude", "cc.json")
|
|
555
|
+
];
|
|
556
|
+
for (const p of possiblePaths) {
|
|
557
|
+
if (fs3.existsSync(p)) {
|
|
558
|
+
return p;
|
|
433
559
|
}
|
|
434
560
|
}
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
function parseCcJson(filePath) {
|
|
564
|
+
try {
|
|
565
|
+
const content = fs3.readFileSync(filePath, "utf-8");
|
|
566
|
+
const data = JSON.parse(content);
|
|
567
|
+
return {
|
|
568
|
+
totalTokens: data.totalTokens || data.total_tokens || 0,
|
|
569
|
+
totalCost: data.totalCost || data.total_cost || data.costUSD || 0,
|
|
570
|
+
inputTokens: data.inputTokens || data.input_tokens || 0,
|
|
571
|
+
outputTokens: data.outputTokens || data.output_tokens || 0,
|
|
572
|
+
cacheReadTokens: data.cacheReadTokens || data.cache_read_tokens || 0,
|
|
573
|
+
cacheWriteTokens: data.cacheWriteTokens || data.cache_write_tokens || 0,
|
|
574
|
+
daysTracked: data.daysTracked || data.days_tracked || calculateDaysTracked(data)
|
|
575
|
+
};
|
|
576
|
+
} catch {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function calculateDaysTracked(data) {
|
|
581
|
+
if (data.dailyStats && Array.isArray(data.dailyStats)) {
|
|
582
|
+
return data.dailyStats.length;
|
|
438
583
|
}
|
|
584
|
+
if (data.daily && typeof data.daily === "object") {
|
|
585
|
+
return Object.keys(data.daily).length;
|
|
586
|
+
}
|
|
587
|
+
return 1;
|
|
588
|
+
}
|
|
589
|
+
async function submitToServer(data) {
|
|
439
590
|
const apiUrl = getApiUrl();
|
|
440
|
-
const
|
|
591
|
+
const config = getConfig();
|
|
592
|
+
const apiToken = config.get("apiToken");
|
|
593
|
+
if (!apiToken) {
|
|
594
|
+
return { success: false, error: "Not authenticated. Please run 'ccgather auth' first." };
|
|
595
|
+
}
|
|
441
596
|
try {
|
|
442
|
-
const response = await fetch(`${apiUrl}/cli/
|
|
443
|
-
method: "POST"
|
|
597
|
+
const response = await fetch(`${apiUrl}/cli/submit`, {
|
|
598
|
+
method: "POST",
|
|
599
|
+
headers: {
|
|
600
|
+
"Content-Type": "application/json",
|
|
601
|
+
Authorization: `Bearer ${apiToken}`
|
|
602
|
+
},
|
|
603
|
+
body: JSON.stringify({
|
|
604
|
+
totalTokens: data.totalTokens,
|
|
605
|
+
totalSpent: data.totalCost,
|
|
606
|
+
inputTokens: data.inputTokens,
|
|
607
|
+
outputTokens: data.outputTokens,
|
|
608
|
+
cacheReadTokens: data.cacheReadTokens,
|
|
609
|
+
cacheWriteTokens: data.cacheWriteTokens,
|
|
610
|
+
daysTracked: data.daysTracked,
|
|
611
|
+
ccplan: data.ccplan,
|
|
612
|
+
rateLimitTier: data.rateLimitTier,
|
|
613
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
614
|
+
})
|
|
444
615
|
});
|
|
445
616
|
if (!response.ok) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
process.exit(1);
|
|
449
|
-
}
|
|
450
|
-
const deviceData = await response.json();
|
|
451
|
-
spinner.stop();
|
|
452
|
-
console.log(import_chalk5.default.gray(" Opening browser for authentication...\n"));
|
|
453
|
-
console.log(import_chalk5.default.gray(" If browser doesn't open, visit:"));
|
|
454
|
-
console.log(` \u{1F517} ${import_chalk5.default.cyan.underline(deviceData.verification_uri_complete)}`);
|
|
455
|
-
console.log();
|
|
456
|
-
try {
|
|
457
|
-
await (0, import_open.default)(deviceData.verification_uri_complete);
|
|
458
|
-
} catch {
|
|
459
|
-
console.log(import_chalk5.default.yellow(" Could not open browser automatically."));
|
|
460
|
-
console.log(import_chalk5.default.yellow(" Please open the URL above manually."));
|
|
617
|
+
const errorData = await response.json().catch(() => ({}));
|
|
618
|
+
return { success: false, error: errorData.error || `HTTP ${response.status}` };
|
|
461
619
|
}
|
|
462
|
-
const
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
while (Date.now() < expiresAt) {
|
|
467
|
-
await sleep(pollInterval);
|
|
468
|
-
try {
|
|
469
|
-
const pollResponse = await fetch(
|
|
470
|
-
`${apiUrl}/cli/auth/device/poll?device_code=${deviceData.device_code}`
|
|
471
|
-
);
|
|
472
|
-
const pollData = await pollResponse.json();
|
|
473
|
-
if (pollData.status === "authorized" && pollData.token) {
|
|
474
|
-
pollSpinner.succeed(import_chalk5.default.green("Authentication successful!"));
|
|
475
|
-
config.set("apiToken", pollData.token);
|
|
476
|
-
config.set("userId", pollData.userId);
|
|
477
|
-
config.set("username", pollData.username);
|
|
478
|
-
console.log(import_chalk5.default.gray(`
|
|
479
|
-
Welcome, ${import_chalk5.default.white(pollData.username)}!`));
|
|
480
|
-
console.log(import_chalk5.default.gray("\nYou can now submit your usage data:"));
|
|
481
|
-
console.log(import_chalk5.default.cyan(" npx ccgather submit\n"));
|
|
482
|
-
return;
|
|
483
|
-
}
|
|
484
|
-
if (pollData.status === "expired" || pollData.status === "used") {
|
|
485
|
-
pollSpinner.fail(import_chalk5.default.red("Authentication expired or already used"));
|
|
486
|
-
console.log(import_chalk5.default.gray('\nPlease run "ccgather auth" to try again.\n'));
|
|
487
|
-
process.exit(1);
|
|
488
|
-
}
|
|
489
|
-
const remaining = Math.ceil((expiresAt - Date.now()) / 1e3);
|
|
490
|
-
pollSpinner.text = `Waiting for authorization... (${remaining}s remaining)`;
|
|
491
|
-
} catch {
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
pollSpinner.fail(import_chalk5.default.red("Authentication timed out"));
|
|
495
|
-
console.log(import_chalk5.default.gray('\nPlease run "ccgather auth" to try again.\n'));
|
|
496
|
-
process.exit(1);
|
|
497
|
-
} catch (error2) {
|
|
498
|
-
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
499
|
-
console.log(import_chalk5.default.red(`
|
|
500
|
-
Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`));
|
|
501
|
-
process.exit(1);
|
|
620
|
+
const result = await response.json();
|
|
621
|
+
return { success: true, profileUrl: result.profileUrl, rank: result.rank };
|
|
622
|
+
} catch (err) {
|
|
623
|
+
return { success: false, error: err instanceof Error ? err.message : "Unknown error" };
|
|
502
624
|
}
|
|
503
625
|
}
|
|
504
|
-
async function
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
"Content-Type": "application/json",
|
|
513
|
-
Authorization: `Bearer ${token}`
|
|
514
|
-
}
|
|
515
|
-
});
|
|
516
|
-
if (!response.ok) {
|
|
517
|
-
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
518
|
-
const errorData = await response.json().catch(() => ({}));
|
|
519
|
-
console.log(import_chalk5.default.red(`Error: ${errorData.error || "Invalid token"}`));
|
|
520
|
-
console.log(import_chalk5.default.gray("\nMake sure your token is correct and try again."));
|
|
521
|
-
process.exit(1);
|
|
522
|
-
}
|
|
523
|
-
const data = await response.json();
|
|
524
|
-
config.set("apiToken", token);
|
|
525
|
-
config.set("userId", data.userId);
|
|
526
|
-
config.set("username", data.username);
|
|
527
|
-
spinner.succeed(import_chalk5.default.green("Authentication successful!"));
|
|
528
|
-
console.log(import_chalk5.default.gray(`
|
|
529
|
-
Welcome, ${import_chalk5.default.white(data.username)}!`));
|
|
530
|
-
console.log(import_chalk5.default.gray("\nNext step: Submit your usage data:"));
|
|
531
|
-
console.log(import_chalk5.default.cyan(" npx ccgather submit\n"));
|
|
532
|
-
} catch (error2) {
|
|
533
|
-
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
534
|
-
console.log(import_chalk5.default.red(`
|
|
535
|
-
Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`));
|
|
626
|
+
async function submit(options) {
|
|
627
|
+
printCompactHeader("1.2.1");
|
|
628
|
+
console.log(header("Submit Usage Data", "\u{1F4E4}"));
|
|
629
|
+
if (!isAuthenticated()) {
|
|
630
|
+
console.log(`
|
|
631
|
+
${error("Not authenticated.")}`);
|
|
632
|
+
console.log(` ${colors.muted("Please run:")} ${colors.white("npx ccgather auth")}
|
|
633
|
+
`);
|
|
536
634
|
process.exit(1);
|
|
537
635
|
}
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
var init_auth = __esm({
|
|
544
|
-
"src/commands/auth.ts"() {
|
|
545
|
-
"use strict";
|
|
546
|
-
import_chalk5 = __toESM(require("chalk"));
|
|
547
|
-
import_ora7 = __toESM(require("ora"));
|
|
548
|
-
import_inquirer2 = __toESM(require("inquirer"));
|
|
549
|
-
import_open = __toESM(require("open"));
|
|
550
|
-
init_config();
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
// src/index.ts
|
|
555
|
-
var import_commander = require("commander");
|
|
556
|
-
var import_inquirer3 = __toESM(require("inquirer"));
|
|
557
|
-
var import_chalk6 = __toESM(require("chalk"));
|
|
558
|
-
|
|
559
|
-
// src/commands/submit.ts
|
|
560
|
-
var import_ora = __toESM(require("ora"));
|
|
561
|
-
var fs3 = __toESM(require("fs"));
|
|
562
|
-
var path3 = __toESM(require("path"));
|
|
563
|
-
var os3 = __toESM(require("os"));
|
|
564
|
-
init_config();
|
|
565
|
-
|
|
566
|
-
// src/lib/ccgather-json.ts
|
|
567
|
-
var fs2 = __toESM(require("fs"));
|
|
568
|
-
var path2 = __toESM(require("path"));
|
|
569
|
-
var os2 = __toESM(require("os"));
|
|
570
|
-
|
|
571
|
-
// src/lib/credentials.ts
|
|
572
|
-
var fs = __toESM(require("fs"));
|
|
573
|
-
var path = __toESM(require("path"));
|
|
574
|
-
var os = __toESM(require("os"));
|
|
575
|
-
function getCredentialsPath() {
|
|
576
|
-
return path.join(os.homedir(), ".claude", ".credentials.json");
|
|
577
|
-
}
|
|
578
|
-
function mapSubscriptionToCCPlan(subscriptionType) {
|
|
579
|
-
if (!subscriptionType) {
|
|
580
|
-
return "free";
|
|
581
|
-
}
|
|
582
|
-
const type = subscriptionType.toLowerCase();
|
|
583
|
-
if (type === "max" || type.includes("max")) {
|
|
584
|
-
return "max";
|
|
585
|
-
}
|
|
586
|
-
if (type === "pro") {
|
|
587
|
-
return "pro";
|
|
588
|
-
}
|
|
589
|
-
if (type === "free") {
|
|
590
|
-
return "free";
|
|
591
|
-
}
|
|
592
|
-
return type;
|
|
593
|
-
}
|
|
594
|
-
function readCredentials() {
|
|
595
|
-
const credentialsPath = getCredentialsPath();
|
|
596
|
-
const defaultData = {
|
|
597
|
-
ccplan: null,
|
|
598
|
-
rateLimitTier: null
|
|
599
|
-
};
|
|
600
|
-
if (!fs.existsSync(credentialsPath)) {
|
|
601
|
-
return defaultData;
|
|
602
|
-
}
|
|
603
|
-
try {
|
|
604
|
-
const content = fs.readFileSync(credentialsPath, "utf-8");
|
|
605
|
-
const credentials = JSON.parse(content);
|
|
606
|
-
const oauthData = credentials.claudeAiOauth;
|
|
607
|
-
if (!oauthData) {
|
|
608
|
-
return defaultData;
|
|
609
|
-
}
|
|
610
|
-
const ccplan = mapSubscriptionToCCPlan(oauthData.subscriptionType);
|
|
611
|
-
const rateLimitTier = oauthData.rateLimitTier || null;
|
|
612
|
-
return {
|
|
613
|
-
ccplan,
|
|
614
|
-
rateLimitTier
|
|
615
|
-
};
|
|
616
|
-
} catch (error2) {
|
|
617
|
-
return defaultData;
|
|
636
|
+
const config = getConfig();
|
|
637
|
+
const username = config.get("username");
|
|
638
|
+
if (username) {
|
|
639
|
+
console.log(`
|
|
640
|
+
${colors.muted("Logged in as:")} ${colors.white(username)}`);
|
|
618
641
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
} catch {
|
|
633
|
-
return parts[projectsIndex + 1];
|
|
642
|
+
let usageData = null;
|
|
643
|
+
let dataSource = "";
|
|
644
|
+
const ccgatherData = readCCGatherJson();
|
|
645
|
+
if (ccgatherData) {
|
|
646
|
+
usageData = ccgatherToUsageData(ccgatherData);
|
|
647
|
+
dataSource = "ccgather.json";
|
|
648
|
+
console.log(`
|
|
649
|
+
${success(`Found ${dataSource}`)}`);
|
|
650
|
+
console.log(
|
|
651
|
+
` ${colors.dim(`Last scanned: ${new Date(ccgatherData.lastScanned).toLocaleString()}`)}`
|
|
652
|
+
);
|
|
653
|
+
if (usageData.ccplan) {
|
|
654
|
+
console.log(` ${colors.dim("CCplan:")} ${colors.primary(usageData.ccplan.toUpperCase())}`);
|
|
634
655
|
}
|
|
635
656
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
files.push(...findJsonlFiles(fullPath));
|
|
652
|
-
} else if (entry.name.endsWith(".jsonl")) {
|
|
653
|
-
files.push(fullPath);
|
|
657
|
+
if (!usageData) {
|
|
658
|
+
const ccJsonPath = findCcJson();
|
|
659
|
+
if (ccJsonPath) {
|
|
660
|
+
const inquirer4 = await import("inquirer");
|
|
661
|
+
const { useCcJson } = await inquirer4.default.prompt([
|
|
662
|
+
{
|
|
663
|
+
type: "confirm",
|
|
664
|
+
name: "useCcJson",
|
|
665
|
+
message: "Found existing cc.json. Use this file?",
|
|
666
|
+
default: true
|
|
667
|
+
}
|
|
668
|
+
]);
|
|
669
|
+
if (useCcJson) {
|
|
670
|
+
usageData = parseCcJson(ccJsonPath);
|
|
671
|
+
dataSource = "cc.json";
|
|
654
672
|
}
|
|
655
673
|
}
|
|
656
|
-
} catch {
|
|
657
674
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
break;
|
|
675
|
+
if (!usageData) {
|
|
676
|
+
const parseSpinner = (0, import_ora.default)({
|
|
677
|
+
text: "Scanning Claude Code usage data...",
|
|
678
|
+
color: "cyan"
|
|
679
|
+
}).start();
|
|
680
|
+
const scannedData = scanAndSave();
|
|
681
|
+
parseSpinner.stop();
|
|
682
|
+
if (scannedData) {
|
|
683
|
+
usageData = ccgatherToUsageData(scannedData);
|
|
684
|
+
dataSource = "Claude Code logs";
|
|
685
|
+
console.log(`
|
|
686
|
+
${success("Scanned and saved to ccgather.json")}`);
|
|
687
|
+
console.log(` ${colors.dim(`Path: ${getCCGatherJsonPath()}`)}`);
|
|
672
688
|
}
|
|
673
689
|
}
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
if (!fs2.existsSync(projectsDir)) {
|
|
682
|
-
return null;
|
|
690
|
+
if (!usageData) {
|
|
691
|
+
console.log(`
|
|
692
|
+
${error("No usage data found.")}`);
|
|
693
|
+
console.log(` ${colors.muted("Make sure you have used Claude Code.")}`);
|
|
694
|
+
console.log(` ${colors.muted("Run:")} ${colors.white("npx ccgather scan")}
|
|
695
|
+
`);
|
|
696
|
+
process.exit(1);
|
|
683
697
|
}
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
const cutoff = /* @__PURE__ */ new Date();
|
|
688
|
-
cutoff.setDate(cutoff.getDate() - days);
|
|
689
|
-
cutoff.setHours(0, 0, 0, 0);
|
|
690
|
-
cutoffDate = cutoff.toISOString();
|
|
698
|
+
if (dataSource && dataSource !== "Claude Code logs") {
|
|
699
|
+
console.log(`
|
|
700
|
+
${success(`Using ${dataSource}`)}`);
|
|
691
701
|
}
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const projectName = extractProjectName(filePath);
|
|
708
|
-
if (!projects[projectName]) {
|
|
709
|
-
projects[projectName] = {
|
|
710
|
-
tokens: 0,
|
|
711
|
-
cost: 0,
|
|
712
|
-
sessions: 0,
|
|
713
|
-
models: {}
|
|
714
|
-
};
|
|
715
|
-
}
|
|
716
|
-
projects[projectName].sessions++;
|
|
717
|
-
try {
|
|
718
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
719
|
-
const lines = content.split("\n").filter((line) => line.trim());
|
|
720
|
-
for (const line of lines) {
|
|
721
|
-
try {
|
|
722
|
-
const event = JSON.parse(line);
|
|
723
|
-
if (event.type === "assistant" && event.message?.usage) {
|
|
724
|
-
if (cutoffDate && event.timestamp && event.timestamp < cutoffDate) {
|
|
725
|
-
continue;
|
|
726
|
-
}
|
|
727
|
-
const usage = event.message.usage;
|
|
728
|
-
const model = event.message.model || "unknown";
|
|
729
|
-
const inputTokens = usage.input_tokens || 0;
|
|
730
|
-
const outputTokens = usage.output_tokens || 0;
|
|
731
|
-
totalInputTokens += inputTokens;
|
|
732
|
-
totalOutputTokens += outputTokens;
|
|
733
|
-
totalCacheRead += usage.cache_read_input_tokens || 0;
|
|
734
|
-
totalCacheWrite += usage.cache_creation_input_tokens || 0;
|
|
735
|
-
const messageCost = estimateCost(model, inputTokens, outputTokens);
|
|
736
|
-
totalCost += messageCost;
|
|
737
|
-
const totalModelTokens = inputTokens + outputTokens;
|
|
738
|
-
models[model] = (models[model] || 0) + totalModelTokens;
|
|
739
|
-
projects[projectName].tokens += totalModelTokens;
|
|
740
|
-
projects[projectName].cost += messageCost;
|
|
741
|
-
projects[projectName].models[model] = (projects[projectName].models[model] || 0) + totalModelTokens;
|
|
742
|
-
if (event.timestamp) {
|
|
743
|
-
const date = new Date(event.timestamp).toISOString().split("T")[0];
|
|
744
|
-
dates.add(date);
|
|
745
|
-
if (!dailyData[date]) {
|
|
746
|
-
dailyData[date] = {
|
|
747
|
-
tokens: 0,
|
|
748
|
-
cost: 0,
|
|
749
|
-
inputTokens: 0,
|
|
750
|
-
outputTokens: 0,
|
|
751
|
-
sessions: /* @__PURE__ */ new Set(),
|
|
752
|
-
models: {}
|
|
753
|
-
};
|
|
754
|
-
}
|
|
755
|
-
dailyData[date].tokens += totalModelTokens;
|
|
756
|
-
dailyData[date].cost += messageCost;
|
|
757
|
-
dailyData[date].inputTokens += inputTokens;
|
|
758
|
-
dailyData[date].outputTokens += outputTokens;
|
|
759
|
-
dailyData[date].sessions.add(filePath);
|
|
760
|
-
dailyData[date].models[model] = (dailyData[date].models[model] || 0) + totalModelTokens;
|
|
761
|
-
if (!firstTimestamp || event.timestamp < firstTimestamp) {
|
|
762
|
-
firstTimestamp = event.timestamp;
|
|
763
|
-
}
|
|
764
|
-
if (!lastTimestamp || event.timestamp > lastTimestamp) {
|
|
765
|
-
lastTimestamp = event.timestamp;
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
} catch {
|
|
770
|
-
}
|
|
702
|
+
if (!usageData.ccplan) {
|
|
703
|
+
const inquirer4 = await import("inquirer");
|
|
704
|
+
const { selectedCCplan } = await inquirer4.default.prompt([
|
|
705
|
+
{
|
|
706
|
+
type: "list",
|
|
707
|
+
name: "selectedCCplan",
|
|
708
|
+
message: colors.muted("Select your Claude plan:"),
|
|
709
|
+
choices: [
|
|
710
|
+
{ name: "\u{1F680} Max", value: "max" },
|
|
711
|
+
{ name: "\u26A1 Pro", value: "pro" },
|
|
712
|
+
{ name: "\u26AA Free", value: "free" },
|
|
713
|
+
{ name: "\u{1F465} Team / Enterprise", value: "team" },
|
|
714
|
+
{ name: "\u23ED\uFE0F Skip", value: null }
|
|
715
|
+
],
|
|
716
|
+
default: "free"
|
|
771
717
|
}
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
}
|
|
775
|
-
const totalTokens = totalInputTokens + totalOutputTokens;
|
|
776
|
-
if (totalTokens === 0) {
|
|
777
|
-
return null;
|
|
718
|
+
]);
|
|
719
|
+
usageData.ccplan = selectedCCplan;
|
|
778
720
|
}
|
|
779
|
-
|
|
780
|
-
|
|
721
|
+
console.log();
|
|
722
|
+
const summaryLines = [
|
|
723
|
+
`${colors.muted("Total Cost")} ${colors.success(formatCost(usageData.totalCost))}`,
|
|
724
|
+
`${colors.muted("Total Tokens")} ${colors.primary(formatNumber(usageData.totalTokens))}`,
|
|
725
|
+
`${colors.muted("Days Tracked")} ${colors.warning(usageData.daysTracked.toString())}`
|
|
726
|
+
];
|
|
727
|
+
if (usageData.ccplan) {
|
|
728
|
+
summaryLines.push(
|
|
729
|
+
`${colors.muted("CCplan")} ${colors.cyan(usageData.ccplan.toUpperCase())}`
|
|
730
|
+
);
|
|
781
731
|
}
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
inputTokens: totalInputTokens,
|
|
800
|
-
outputTokens: totalOutputTokens,
|
|
801
|
-
cacheReadTokens: totalCacheRead,
|
|
802
|
-
cacheWriteTokens: totalCacheWrite
|
|
803
|
-
},
|
|
804
|
-
stats: {
|
|
805
|
-
daysTracked: dates.size,
|
|
806
|
-
sessionsCount,
|
|
807
|
-
firstUsed: firstTimestamp ? new Date(firstTimestamp).toISOString().split("T")[0] : null,
|
|
808
|
-
lastUsed: lastTimestamp ? new Date(lastTimestamp).toISOString().split("T")[0] : null
|
|
809
|
-
},
|
|
810
|
-
models,
|
|
811
|
-
projects,
|
|
812
|
-
dailyUsage,
|
|
813
|
-
account: {
|
|
814
|
-
ccplan: credentials.ccplan,
|
|
815
|
-
rateLimitTier: credentials.rateLimitTier
|
|
732
|
+
console.log(createBox(summaryLines));
|
|
733
|
+
console.log();
|
|
734
|
+
if (!options.yes) {
|
|
735
|
+
const inquirer4 = await import("inquirer");
|
|
736
|
+
const { confirmSubmit } = await inquirer4.default.prompt([
|
|
737
|
+
{
|
|
738
|
+
type: "confirm",
|
|
739
|
+
name: "confirmSubmit",
|
|
740
|
+
message: "Submit to CCgather leaderboard?",
|
|
741
|
+
default: true
|
|
742
|
+
}
|
|
743
|
+
]);
|
|
744
|
+
if (!confirmSubmit) {
|
|
745
|
+
console.log(`
|
|
746
|
+
${colors.muted("Submission cancelled.")}
|
|
747
|
+
`);
|
|
748
|
+
return;
|
|
816
749
|
}
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
function readCCGatherJson() {
|
|
820
|
-
const jsonPath = getCCGatherJsonPath();
|
|
821
|
-
if (!fs2.existsSync(jsonPath)) {
|
|
822
|
-
return null;
|
|
823
|
-
}
|
|
824
|
-
try {
|
|
825
|
-
const content = fs2.readFileSync(jsonPath, "utf-8");
|
|
826
|
-
return JSON.parse(content);
|
|
827
|
-
} catch {
|
|
828
|
-
return null;
|
|
829
750
|
}
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
751
|
+
const submitSpinner = (0, import_ora.default)({
|
|
752
|
+
text: "Submitting to CCgather...",
|
|
753
|
+
color: "cyan"
|
|
754
|
+
}).start();
|
|
755
|
+
const result = await submitToServer(usageData);
|
|
756
|
+
if (result.success) {
|
|
757
|
+
submitSpinner.succeed(colors.success("Successfully submitted to CCgather!"));
|
|
758
|
+
console.log();
|
|
759
|
+
const successLines = [
|
|
760
|
+
`${colors.success("\u2713")} ${colors.white.bold("Submission Complete!")}`,
|
|
761
|
+
"",
|
|
762
|
+
`${colors.muted("Profile:")} ${link(result.profileUrl || `https://ccgather.com/u/${username}`)}`
|
|
763
|
+
];
|
|
764
|
+
if (result.rank) {
|
|
765
|
+
successLines.push(`${colors.muted("Rank:")} ${colors.warning(`#${result.rank}`)}`);
|
|
766
|
+
}
|
|
767
|
+
console.log(createBox(successLines));
|
|
768
|
+
console.log();
|
|
769
|
+
console.log(` ${colors.dim("View leaderboard:")} ${link("https://ccgather.com/leaderboard")}`);
|
|
770
|
+
console.log();
|
|
771
|
+
} else {
|
|
772
|
+
submitSpinner.fail(colors.error("Failed to submit"));
|
|
773
|
+
console.log(`
|
|
774
|
+
${error(result.error || "Unknown error")}`);
|
|
775
|
+
if (result.error?.includes("auth") || result.error?.includes("token")) {
|
|
776
|
+
console.log(`
|
|
777
|
+
${colors.muted("Try running:")} ${colors.white("npx ccgather auth")}`);
|
|
778
|
+
}
|
|
779
|
+
console.log();
|
|
780
|
+
process.exit(1);
|
|
836
781
|
}
|
|
837
|
-
fs2.writeFileSync(jsonPath, JSON.stringify(data, null, 2));
|
|
838
782
|
}
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
783
|
+
var import_ora, fs3, path3, os3;
|
|
784
|
+
var init_submit = __esm({
|
|
785
|
+
"src/commands/submit.ts"() {
|
|
786
|
+
"use strict";
|
|
787
|
+
import_ora = __toESM(require("ora"));
|
|
788
|
+
fs3 = __toESM(require("fs"));
|
|
789
|
+
path3 = __toESM(require("path"));
|
|
790
|
+
os3 = __toESM(require("os"));
|
|
791
|
+
init_config();
|
|
792
|
+
init_ccgather_json();
|
|
793
|
+
init_ui();
|
|
843
794
|
}
|
|
844
|
-
|
|
845
|
-
}
|
|
795
|
+
});
|
|
846
796
|
|
|
847
|
-
// src/lib/
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
free: import_chalk.default.hex("#6B7280")
|
|
874
|
-
// Gray
|
|
875
|
-
};
|
|
876
|
-
var LOGO = `
|
|
877
|
-
${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")}
|
|
878
|
-
${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")}
|
|
879
|
-
${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")}
|
|
880
|
-
${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")}
|
|
881
|
-
${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")}
|
|
882
|
-
${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")}
|
|
883
|
-
`;
|
|
884
|
-
var LOGO_COMPACT = `
|
|
885
|
-
${colors.primary("CC")}${colors.secondary("gather")} ${colors.muted("- Where Claude Code Developers Gather")}
|
|
886
|
-
`;
|
|
887
|
-
var TAGLINE = colors.muted(" Where Claude Code Developers Gather");
|
|
888
|
-
var SLOGAN = colors.dim(" Gather. Compete. Rise.");
|
|
889
|
-
function getVersionLine(version) {
|
|
890
|
-
return colors.dim(` v${version} \u2022 ccgather.com`);
|
|
797
|
+
// src/lib/api.ts
|
|
798
|
+
async function fetchApi(endpoint, options = {}) {
|
|
799
|
+
const config = getConfig();
|
|
800
|
+
const apiToken = config.get("apiToken");
|
|
801
|
+
const apiUrl = getApiUrl();
|
|
802
|
+
if (!apiToken) {
|
|
803
|
+
return { success: false, error: "Not authenticated. Run: npx ccgather auth" };
|
|
804
|
+
}
|
|
805
|
+
try {
|
|
806
|
+
const response = await fetch(`${apiUrl}${endpoint}`, {
|
|
807
|
+
...options,
|
|
808
|
+
headers: {
|
|
809
|
+
"Content-Type": "application/json",
|
|
810
|
+
Authorization: `Bearer ${apiToken}`,
|
|
811
|
+
...options.headers
|
|
812
|
+
}
|
|
813
|
+
});
|
|
814
|
+
const data = await response.json();
|
|
815
|
+
if (!response.ok) {
|
|
816
|
+
return { success: false, error: data.error || `HTTP ${response.status}` };
|
|
817
|
+
}
|
|
818
|
+
return { success: true, data };
|
|
819
|
+
} catch (error2) {
|
|
820
|
+
const message = error2 instanceof Error ? error2.message : "Unknown error";
|
|
821
|
+
return { success: false, error: message };
|
|
822
|
+
}
|
|
891
823
|
}
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
bottomRight: "\u2518",
|
|
897
|
-
horizontal: "\u2500",
|
|
898
|
-
vertical: "\u2502",
|
|
899
|
-
leftT: "\u251C",
|
|
900
|
-
rightT: "\u2524"
|
|
901
|
-
};
|
|
902
|
-
function createBox(lines, width = 47) {
|
|
903
|
-
const paddedLines = lines.map((line) => {
|
|
904
|
-
const visibleLength = stripAnsi(line).length;
|
|
905
|
-
const padding = width - 2 - visibleLength;
|
|
906
|
-
return `${box.vertical} ${line}${" ".repeat(Math.max(0, padding))} ${box.vertical}`;
|
|
824
|
+
async function syncUsage(payload) {
|
|
825
|
+
return fetchApi("/cli/sync", {
|
|
826
|
+
method: "POST",
|
|
827
|
+
body: JSON.stringify(payload)
|
|
907
828
|
});
|
|
908
|
-
const top = colors.dim(` ${box.topLeft}${box.horizontal.repeat(width)}${box.topRight}`);
|
|
909
|
-
const bottom = colors.dim(` ${box.bottomLeft}${box.horizontal.repeat(width)}${box.bottomRight}`);
|
|
910
|
-
return [top, ...paddedLines.map((l) => colors.dim(" ") + l), bottom].join("\n");
|
|
911
|
-
}
|
|
912
|
-
function stripAnsi(str) {
|
|
913
|
-
return str.replace(/\x1B\[[0-9;]*[a-zA-Z]/g, "");
|
|
914
829
|
}
|
|
915
|
-
function
|
|
916
|
-
|
|
917
|
-
return `
|
|
918
|
-
${colors.primary("\u2501".repeat(50))}
|
|
919
|
-
${iconPart}${colors.white.bold(title)}
|
|
920
|
-
${colors.primary("\u2501".repeat(50))}`;
|
|
921
|
-
}
|
|
922
|
-
function formatNumber(num) {
|
|
923
|
-
if (num >= 1e9) return `${(num / 1e9).toFixed(2)}B`;
|
|
924
|
-
if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
|
|
925
|
-
if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`;
|
|
926
|
-
return num.toLocaleString();
|
|
830
|
+
async function getStatus() {
|
|
831
|
+
return fetchApi("/cli/status");
|
|
927
832
|
}
|
|
928
|
-
|
|
929
|
-
|
|
833
|
+
var init_api = __esm({
|
|
834
|
+
"src/lib/api.ts"() {
|
|
835
|
+
"use strict";
|
|
836
|
+
init_config();
|
|
837
|
+
}
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
// src/commands/reset.ts
|
|
841
|
+
var reset_exports = {};
|
|
842
|
+
__export(reset_exports, {
|
|
843
|
+
reset: () => reset
|
|
844
|
+
});
|
|
845
|
+
function getClaudeSettingsDir() {
|
|
846
|
+
return path4.join(os4.homedir(), ".claude");
|
|
930
847
|
}
|
|
931
|
-
function
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
if (
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
848
|
+
function removeStopHook() {
|
|
849
|
+
const claudeDir = getClaudeSettingsDir();
|
|
850
|
+
const settingsPath = path4.join(claudeDir, "settings.json");
|
|
851
|
+
if (!fs4.existsSync(settingsPath)) {
|
|
852
|
+
return { success: true, message: "No settings file found" };
|
|
853
|
+
}
|
|
854
|
+
try {
|
|
855
|
+
const content = fs4.readFileSync(settingsPath, "utf-8");
|
|
856
|
+
const settings = JSON.parse(content);
|
|
857
|
+
if (settings.hooks && typeof settings.hooks === "object") {
|
|
858
|
+
const hooks = settings.hooks;
|
|
859
|
+
if (hooks.Stop && Array.isArray(hooks.Stop)) {
|
|
860
|
+
hooks.Stop = hooks.Stop.filter((hook) => {
|
|
861
|
+
if (typeof hook === "object" && hook !== null) {
|
|
862
|
+
const h = hook;
|
|
863
|
+
return typeof h.command !== "string" || !h.command.includes("ccgather");
|
|
864
|
+
}
|
|
865
|
+
return true;
|
|
866
|
+
});
|
|
867
|
+
if (hooks.Stop.length === 0) {
|
|
868
|
+
delete hooks.Stop;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
fs4.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
873
|
+
return { success: true, message: "Hook removed" };
|
|
874
|
+
} catch (err) {
|
|
875
|
+
return {
|
|
876
|
+
success: false,
|
|
877
|
+
message: err instanceof Error ? err.message : "Unknown error"
|
|
878
|
+
};
|
|
879
|
+
}
|
|
938
880
|
}
|
|
939
|
-
function
|
|
940
|
-
|
|
941
|
-
const
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
free: `${colors.free("\u26AA FREE")}`
|
|
946
|
-
};
|
|
947
|
-
return badges[ccplan.toLowerCase()] || "";
|
|
881
|
+
function removeSyncScript() {
|
|
882
|
+
const claudeDir = getClaudeSettingsDir();
|
|
883
|
+
const scriptPath = path4.join(claudeDir, "ccgather-sync.js");
|
|
884
|
+
if (fs4.existsSync(scriptPath)) {
|
|
885
|
+
fs4.unlinkSync(scriptPath);
|
|
886
|
+
}
|
|
948
887
|
}
|
|
949
|
-
function
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
{
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
return levels[i];
|
|
888
|
+
async function reset() {
|
|
889
|
+
const config = getConfig();
|
|
890
|
+
console.log(import_chalk2.default.bold("\n\u{1F504} CCgather Reset\n"));
|
|
891
|
+
if (!config.get("apiToken")) {
|
|
892
|
+
console.log(import_chalk2.default.yellow("CCgather is not configured."));
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
const { confirmReset } = await import_inquirer.default.prompt([
|
|
896
|
+
{
|
|
897
|
+
type: "confirm",
|
|
898
|
+
name: "confirmReset",
|
|
899
|
+
message: "This will remove the CCgather hook and local configuration. Continue?",
|
|
900
|
+
default: false
|
|
963
901
|
}
|
|
902
|
+
]);
|
|
903
|
+
if (!confirmReset) {
|
|
904
|
+
console.log(import_chalk2.default.gray("Reset cancelled."));
|
|
905
|
+
return;
|
|
964
906
|
}
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
if (levelInfo || ccplanBadge) {
|
|
972
|
-
lines.push(`${levelInfo}${ccplanBadge ? ` ${ccplanBadge}` : ""}`);
|
|
907
|
+
const hookSpinner = (0, import_ora3.default)("Removing Claude Code hook...").start();
|
|
908
|
+
const hookResult = removeStopHook();
|
|
909
|
+
if (hookResult.success) {
|
|
910
|
+
hookSpinner.succeed(import_chalk2.default.green("Hook removed"));
|
|
911
|
+
} else {
|
|
912
|
+
hookSpinner.warn(import_chalk2.default.yellow(`Could not remove hook: ${hookResult.message}`));
|
|
973
913
|
}
|
|
974
|
-
|
|
975
|
-
|
|
914
|
+
const scriptSpinner = (0, import_ora3.default)("Removing sync script...").start();
|
|
915
|
+
try {
|
|
916
|
+
removeSyncScript();
|
|
917
|
+
scriptSpinner.succeed(import_chalk2.default.green("Sync script removed"));
|
|
918
|
+
} catch {
|
|
919
|
+
scriptSpinner.warn(import_chalk2.default.yellow("Could not remove sync script"));
|
|
976
920
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
921
|
+
const { deleteAccount } = await import_inquirer.default.prompt([
|
|
922
|
+
{
|
|
923
|
+
type: "confirm",
|
|
924
|
+
name: "deleteAccount",
|
|
925
|
+
message: import_chalk2.default.red("Do you also want to delete your account from the leaderboard? (This cannot be undone)"),
|
|
926
|
+
default: false
|
|
927
|
+
}
|
|
928
|
+
]);
|
|
929
|
+
if (deleteAccount) {
|
|
930
|
+
console.log(import_chalk2.default.yellow("\nAccount deletion is not yet implemented."));
|
|
931
|
+
console.log(import_chalk2.default.gray("Please contact support to delete your account."));
|
|
980
932
|
}
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
if (!countryCode || countryCode.length !== 2) return "\u{1F310}";
|
|
985
|
-
const codePoints = countryCode.toUpperCase().split("").map((char) => 127462 + char.charCodeAt(0) - 65);
|
|
986
|
-
return String.fromCodePoint(...codePoints);
|
|
987
|
-
}
|
|
988
|
-
function success(message) {
|
|
989
|
-
return `${colors.success("\u2713")} ${message}`;
|
|
990
|
-
}
|
|
991
|
-
function error(message) {
|
|
992
|
-
return `${colors.error("\u2717")} ${message}`;
|
|
993
|
-
}
|
|
994
|
-
function printHeader(version) {
|
|
995
|
-
console.log(LOGO);
|
|
996
|
-
console.log(TAGLINE);
|
|
997
|
-
console.log(SLOGAN);
|
|
998
|
-
console.log();
|
|
999
|
-
console.log(getVersionLine(version));
|
|
1000
|
-
console.log();
|
|
1001
|
-
}
|
|
1002
|
-
function printCompactHeader(version) {
|
|
933
|
+
const configSpinner = (0, import_ora3.default)("Resetting local configuration...").start();
|
|
934
|
+
resetConfig();
|
|
935
|
+
configSpinner.succeed(import_chalk2.default.green("Local configuration reset"));
|
|
1003
936
|
console.log();
|
|
1004
|
-
console.log(
|
|
1005
|
-
console.log(
|
|
937
|
+
console.log(import_chalk2.default.green.bold("\u2705 Reset complete!"));
|
|
938
|
+
console.log();
|
|
939
|
+
console.log(import_chalk2.default.gray("Your usage will no longer be tracked."));
|
|
940
|
+
console.log(import_chalk2.default.gray("Run `npx ccgather` to set up again."));
|
|
1006
941
|
console.log();
|
|
1007
942
|
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
943
|
+
var import_chalk2, import_ora3, fs4, path4, os4, import_inquirer;
|
|
944
|
+
var init_reset = __esm({
|
|
945
|
+
"src/commands/reset.ts"() {
|
|
946
|
+
"use strict";
|
|
947
|
+
import_chalk2 = __toESM(require("chalk"));
|
|
948
|
+
import_ora3 = __toESM(require("ora"));
|
|
949
|
+
fs4 = __toESM(require("fs"));
|
|
950
|
+
path4 = __toESM(require("path"));
|
|
951
|
+
os4 = __toESM(require("os"));
|
|
952
|
+
import_inquirer = __toESM(require("inquirer"));
|
|
953
|
+
init_config();
|
|
954
|
+
}
|
|
955
|
+
});
|
|
1011
956
|
|
|
1012
|
-
// src/
|
|
1013
|
-
function
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
ccplan: data.account?.ccplan || null,
|
|
1023
|
-
rateLimitTier: data.account?.rateLimitTier || null
|
|
1024
|
-
};
|
|
957
|
+
// src/lib/claude.ts
|
|
958
|
+
function getClaudeConfigDir() {
|
|
959
|
+
const platform3 = os6.platform();
|
|
960
|
+
if (platform3 === "win32") {
|
|
961
|
+
return path6.join(os6.homedir(), "AppData", "Roaming", "claude-code");
|
|
962
|
+
} else if (platform3 === "darwin") {
|
|
963
|
+
return path6.join(os6.homedir(), "Library", "Application Support", "claude-code");
|
|
964
|
+
} else {
|
|
965
|
+
return path6.join(os6.homedir(), ".config", "claude-code");
|
|
966
|
+
}
|
|
1025
967
|
}
|
|
1026
|
-
function
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
968
|
+
function getUsageFilePaths() {
|
|
969
|
+
const configDir = getClaudeConfigDir();
|
|
970
|
+
return [
|
|
971
|
+
path6.join(configDir, "usage.json"),
|
|
972
|
+
path6.join(configDir, "stats.json"),
|
|
973
|
+
path6.join(configDir, "data", "usage.json"),
|
|
974
|
+
path6.join(os6.homedir(), ".claude", "usage.json"),
|
|
975
|
+
path6.join(os6.homedir(), ".claude-code", "usage.json")
|
|
1031
976
|
];
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
977
|
+
}
|
|
978
|
+
function readClaudeUsage() {
|
|
979
|
+
const possiblePaths = getUsageFilePaths();
|
|
980
|
+
for (const filePath of possiblePaths) {
|
|
981
|
+
try {
|
|
982
|
+
if (fs6.existsSync(filePath)) {
|
|
983
|
+
const content = fs6.readFileSync(filePath, "utf-8");
|
|
984
|
+
const data = JSON.parse(content);
|
|
985
|
+
if (data.usage) {
|
|
986
|
+
return {
|
|
987
|
+
totalTokens: data.usage.total_tokens || 0,
|
|
988
|
+
totalSpent: data.usage.total_cost || 0,
|
|
989
|
+
modelBreakdown: data.usage.by_model || {},
|
|
990
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
if (data.sessions && data.sessions.length > 0) {
|
|
994
|
+
const breakdown = {};
|
|
995
|
+
let totalTokens = 0;
|
|
996
|
+
let totalSpent = 0;
|
|
997
|
+
for (const session of data.sessions) {
|
|
998
|
+
totalTokens += session.tokens_used || 0;
|
|
999
|
+
totalSpent += session.cost || 0;
|
|
1000
|
+
breakdown[session.model] = (breakdown[session.model] || 0) + session.tokens_used;
|
|
1001
|
+
}
|
|
1002
|
+
return {
|
|
1003
|
+
totalTokens,
|
|
1004
|
+
totalSpent,
|
|
1005
|
+
modelBreakdown: breakdown,
|
|
1006
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
} catch (error2) {
|
|
1035
1011
|
}
|
|
1036
1012
|
}
|
|
1037
1013
|
return null;
|
|
1038
1014
|
}
|
|
1039
|
-
function
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
const data = JSON.parse(content);
|
|
1043
|
-
return {
|
|
1044
|
-
totalTokens: data.totalTokens || data.total_tokens || 0,
|
|
1045
|
-
totalCost: data.totalCost || data.total_cost || data.costUSD || 0,
|
|
1046
|
-
inputTokens: data.inputTokens || data.input_tokens || 0,
|
|
1047
|
-
outputTokens: data.outputTokens || data.output_tokens || 0,
|
|
1048
|
-
cacheReadTokens: data.cacheReadTokens || data.cache_read_tokens || 0,
|
|
1049
|
-
cacheWriteTokens: data.cacheWriteTokens || data.cache_write_tokens || 0,
|
|
1050
|
-
daysTracked: data.daysTracked || data.days_tracked || calculateDaysTracked(data)
|
|
1051
|
-
};
|
|
1052
|
-
} catch {
|
|
1053
|
-
return null;
|
|
1054
|
-
}
|
|
1015
|
+
function isClaudeCodeInstalled() {
|
|
1016
|
+
const configDir = getClaudeConfigDir();
|
|
1017
|
+
return fs6.existsSync(configDir);
|
|
1055
1018
|
}
|
|
1056
|
-
function
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1019
|
+
function getMockUsageData() {
|
|
1020
|
+
return {
|
|
1021
|
+
totalTokens: Math.floor(Math.random() * 1e6) + 1e4,
|
|
1022
|
+
totalSpent: Math.random() * 50 + 5,
|
|
1023
|
+
modelBreakdown: {
|
|
1024
|
+
"claude-3-5-sonnet-20241022": Math.floor(Math.random() * 5e5),
|
|
1025
|
+
"claude-3-opus-20240229": Math.floor(Math.random() * 1e5),
|
|
1026
|
+
"claude-3-haiku-20240307": Math.floor(Math.random() * 2e5)
|
|
1027
|
+
},
|
|
1028
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
|
|
1029
|
+
};
|
|
1064
1030
|
}
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
try {
|
|
1073
|
-
const response = await fetch(`${apiUrl}/cli/submit`, {
|
|
1074
|
-
method: "POST",
|
|
1075
|
-
headers: {
|
|
1076
|
-
"Content-Type": "application/json",
|
|
1077
|
-
Authorization: `Bearer ${apiToken}`
|
|
1078
|
-
},
|
|
1079
|
-
body: JSON.stringify({
|
|
1080
|
-
totalTokens: data.totalTokens,
|
|
1081
|
-
totalSpent: data.totalCost,
|
|
1082
|
-
inputTokens: data.inputTokens,
|
|
1083
|
-
outputTokens: data.outputTokens,
|
|
1084
|
-
cacheReadTokens: data.cacheReadTokens,
|
|
1085
|
-
cacheWriteTokens: data.cacheWriteTokens,
|
|
1086
|
-
daysTracked: data.daysTracked,
|
|
1087
|
-
ccplan: data.ccplan,
|
|
1088
|
-
rateLimitTier: data.rateLimitTier,
|
|
1089
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1090
|
-
})
|
|
1091
|
-
});
|
|
1092
|
-
if (!response.ok) {
|
|
1093
|
-
const errorData = await response.json().catch(() => ({}));
|
|
1094
|
-
return { success: false, error: errorData.error || `HTTP ${response.status}` };
|
|
1095
|
-
}
|
|
1096
|
-
const result = await response.json();
|
|
1097
|
-
return { success: true, profileUrl: result.profileUrl, rank: result.rank };
|
|
1098
|
-
} catch (err) {
|
|
1099
|
-
return { success: false, error: err instanceof Error ? err.message : "Unknown error" };
|
|
1031
|
+
var fs6, path6, os6;
|
|
1032
|
+
var init_claude = __esm({
|
|
1033
|
+
"src/lib/claude.ts"() {
|
|
1034
|
+
"use strict";
|
|
1035
|
+
fs6 = __toESM(require("fs"));
|
|
1036
|
+
path6 = __toESM(require("path"));
|
|
1037
|
+
os6 = __toESM(require("os"));
|
|
1100
1038
|
}
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
// src/commands/sync.ts
|
|
1042
|
+
var sync_exports = {};
|
|
1043
|
+
__export(sync_exports, {
|
|
1044
|
+
sync: () => sync
|
|
1045
|
+
});
|
|
1046
|
+
function formatNumber2(num) {
|
|
1047
|
+
if (num >= 1e6) return `${(num / 1e6).toFixed(2)}M`;
|
|
1048
|
+
if (num >= 1e3) return `${(num / 1e3).toFixed(2)}K`;
|
|
1049
|
+
return num.toString();
|
|
1101
1050
|
}
|
|
1102
|
-
async function
|
|
1103
|
-
|
|
1104
|
-
console.log(
|
|
1051
|
+
async function sync(options) {
|
|
1052
|
+
const config = getConfig();
|
|
1053
|
+
console.log(import_chalk4.default.bold("\n\u{1F504} CCgather Sync\n"));
|
|
1105
1054
|
if (!isAuthenticated()) {
|
|
1106
|
-
console.log(
|
|
1107
|
-
|
|
1108
|
-
console.log(` ${colors.muted("Please run:")} ${colors.white("npx ccgather auth")}
|
|
1109
|
-
`);
|
|
1055
|
+
console.log(import_chalk4.default.red("Not authenticated."));
|
|
1056
|
+
console.log(import_chalk4.default.gray("Run: npx ccgather auth\n"));
|
|
1110
1057
|
process.exit(1);
|
|
1111
1058
|
}
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
if (username) {
|
|
1115
|
-
console.log(`
|
|
1116
|
-
${colors.muted("Logged in as:")} ${colors.white(username)}`);
|
|
1117
|
-
}
|
|
1118
|
-
let usageData = null;
|
|
1119
|
-
let dataSource = "";
|
|
1120
|
-
const ccgatherData = readCCGatherJson();
|
|
1121
|
-
if (ccgatherData) {
|
|
1122
|
-
usageData = ccgatherToUsageData(ccgatherData);
|
|
1123
|
-
dataSource = "ccgather.json";
|
|
1124
|
-
console.log(`
|
|
1125
|
-
${success(`Found ${dataSource}`)}`);
|
|
1059
|
+
if (!isClaudeCodeInstalled()) {
|
|
1060
|
+
console.log(import_chalk4.default.yellow("\u26A0\uFE0F Claude Code installation not detected."));
|
|
1126
1061
|
console.log(
|
|
1127
|
-
|
|
1062
|
+
import_chalk4.default.gray("Make sure Claude Code is installed and has been used at least once.\n")
|
|
1128
1063
|
);
|
|
1129
|
-
if (
|
|
1130
|
-
console.log(
|
|
1064
|
+
if (process.env.CCGATHER_DEMO === "true") {
|
|
1065
|
+
console.log(import_chalk4.default.gray("Demo mode: Using mock data..."));
|
|
1066
|
+
} else {
|
|
1067
|
+
process.exit(1);
|
|
1131
1068
|
}
|
|
1132
1069
|
}
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
}
|
|
1144
|
-
]);
|
|
1145
|
-
if (useCcJson) {
|
|
1146
|
-
usageData = parseCcJson(ccJsonPath);
|
|
1147
|
-
dataSource = "cc.json";
|
|
1148
|
-
}
|
|
1070
|
+
const lastSync = config.get("lastSync");
|
|
1071
|
+
if (lastSync && !options.force) {
|
|
1072
|
+
const lastSyncDate = new Date(lastSync);
|
|
1073
|
+
const minInterval = 5 * 60 * 1e3;
|
|
1074
|
+
const timeSinceSync = Date.now() - lastSyncDate.getTime();
|
|
1075
|
+
if (timeSinceSync < minInterval) {
|
|
1076
|
+
const remaining = Math.ceil((minInterval - timeSinceSync) / 1e3 / 60);
|
|
1077
|
+
console.log(import_chalk4.default.yellow(`\u23F3 Please wait ${remaining} minutes before syncing again.`));
|
|
1078
|
+
console.log(import_chalk4.default.gray("Use --force to override.\n"));
|
|
1079
|
+
process.exit(0);
|
|
1149
1080
|
}
|
|
1150
1081
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
}).start();
|
|
1156
|
-
const scannedData = scanAndSave();
|
|
1157
|
-
parseSpinner.stop();
|
|
1158
|
-
if (scannedData) {
|
|
1159
|
-
usageData = ccgatherToUsageData(scannedData);
|
|
1160
|
-
dataSource = "Claude Code logs";
|
|
1161
|
-
console.log(`
|
|
1162
|
-
${success("Scanned and saved to ccgather.json")}`);
|
|
1163
|
-
console.log(` ${colors.dim(`Path: ${getCCGatherJsonPath()}`)}`);
|
|
1164
|
-
}
|
|
1082
|
+
const spinner = (0, import_ora6.default)("Reading Claude Code usage data...").start();
|
|
1083
|
+
let usageData = readClaudeUsage();
|
|
1084
|
+
if (!usageData && process.env.CCGATHER_DEMO === "true") {
|
|
1085
|
+
usageData = getMockUsageData();
|
|
1165
1086
|
}
|
|
1166
1087
|
if (!usageData) {
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
console.log(
|
|
1170
|
-
console.log(
|
|
1171
|
-
|
|
1088
|
+
spinner.fail(import_chalk4.default.red("Failed to read usage data"));
|
|
1089
|
+
console.log(import_chalk4.default.gray("\nPossible reasons:"));
|
|
1090
|
+
console.log(import_chalk4.default.gray(" - Claude Code has not been used yet"));
|
|
1091
|
+
console.log(import_chalk4.default.gray(" - Usage data file is missing or corrupted"));
|
|
1092
|
+
console.log(import_chalk4.default.gray(" - Insufficient permissions to read the file\n"));
|
|
1172
1093
|
process.exit(1);
|
|
1173
1094
|
}
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1095
|
+
spinner.text = "Syncing to CCgather...";
|
|
1096
|
+
const result = await syncUsage({
|
|
1097
|
+
totalTokens: usageData.totalTokens,
|
|
1098
|
+
totalSpent: usageData.totalSpent,
|
|
1099
|
+
modelBreakdown: usageData.modelBreakdown,
|
|
1100
|
+
timestamp: usageData.lastUpdated
|
|
1101
|
+
});
|
|
1102
|
+
if (!result.success) {
|
|
1103
|
+
spinner.fail(import_chalk4.default.red("Sync failed"));
|
|
1104
|
+
console.log(import_chalk4.default.red(`Error: ${result.error}
|
|
1105
|
+
`));
|
|
1106
|
+
process.exit(1);
|
|
1177
1107
|
}
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
]);
|
|
1195
|
-
usageData.ccplan = selectedCCplan;
|
|
1108
|
+
config.set("lastSync", (/* @__PURE__ */ new Date()).toISOString());
|
|
1109
|
+
spinner.succeed(import_chalk4.default.green("Sync complete!"));
|
|
1110
|
+
console.log("\n" + import_chalk4.default.gray("\u2500".repeat(40)));
|
|
1111
|
+
console.log(import_chalk4.default.bold("\u{1F4CA} Your Stats"));
|
|
1112
|
+
console.log(import_chalk4.default.gray("\u2500".repeat(40)));
|
|
1113
|
+
console.log(` ${import_chalk4.default.gray("Tokens:")} ${import_chalk4.default.white(formatNumber2(usageData.totalTokens))}`);
|
|
1114
|
+
console.log(
|
|
1115
|
+
` ${import_chalk4.default.gray("Spent:")} ${import_chalk4.default.green("$" + usageData.totalSpent.toFixed(2))}`
|
|
1116
|
+
);
|
|
1117
|
+
console.log(` ${import_chalk4.default.gray("Rank:")} ${import_chalk4.default.yellow("#" + result.data?.rank)}`);
|
|
1118
|
+
if (options.verbose) {
|
|
1119
|
+
console.log("\n" + import_chalk4.default.gray("Model Breakdown:"));
|
|
1120
|
+
for (const [model, tokens] of Object.entries(usageData.modelBreakdown)) {
|
|
1121
|
+
const shortModel = model.replace("claude-", "").replace(/-\d+$/, "");
|
|
1122
|
+
console.log(` ${import_chalk4.default.gray(shortModel + ":")} ${formatNumber2(tokens)}`);
|
|
1123
|
+
}
|
|
1196
1124
|
}
|
|
1197
|
-
console.log();
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
);
|
|
1125
|
+
console.log(import_chalk4.default.gray("\u2500".repeat(40)));
|
|
1126
|
+
console.log(import_chalk4.default.gray("\nView full leaderboard: https://ccgather.com/leaderboard\n"));
|
|
1127
|
+
}
|
|
1128
|
+
var import_chalk4, import_ora6;
|
|
1129
|
+
var init_sync = __esm({
|
|
1130
|
+
"src/commands/sync.ts"() {
|
|
1131
|
+
"use strict";
|
|
1132
|
+
import_chalk4 = __toESM(require("chalk"));
|
|
1133
|
+
import_ora6 = __toESM(require("ora"));
|
|
1134
|
+
init_config();
|
|
1135
|
+
init_api();
|
|
1136
|
+
init_claude();
|
|
1207
1137
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
// src/commands/auth.ts
|
|
1141
|
+
var auth_exports = {};
|
|
1142
|
+
__export(auth_exports, {
|
|
1143
|
+
auth: () => auth
|
|
1144
|
+
});
|
|
1145
|
+
async function auth(options) {
|
|
1146
|
+
const config = getConfig();
|
|
1147
|
+
console.log(import_chalk5.default.bold("\n\u{1F510} CCgather Authentication\n"));
|
|
1148
|
+
const existingToken = config.get("apiToken");
|
|
1149
|
+
if (existingToken) {
|
|
1150
|
+
const { overwrite } = await import_inquirer2.default.prompt([
|
|
1213
1151
|
{
|
|
1214
1152
|
type: "confirm",
|
|
1215
|
-
name: "
|
|
1216
|
-
message: "
|
|
1217
|
-
default:
|
|
1153
|
+
name: "overwrite",
|
|
1154
|
+
message: "You are already authenticated. Do you want to re-authenticate?",
|
|
1155
|
+
default: false
|
|
1218
1156
|
}
|
|
1219
1157
|
]);
|
|
1220
|
-
if (!
|
|
1221
|
-
console.log(
|
|
1222
|
-
${colors.muted("Submission cancelled.")}
|
|
1223
|
-
`);
|
|
1158
|
+
if (!overwrite) {
|
|
1159
|
+
console.log(import_chalk5.default.gray("Authentication cancelled."));
|
|
1224
1160
|
return;
|
|
1225
1161
|
}
|
|
1226
1162
|
}
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
}
|
|
1231
|
-
const
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
successLines.push(`${colors.muted("Rank:")} ${colors.warning(`#${result.rank}`)}`);
|
|
1163
|
+
if (options.token) {
|
|
1164
|
+
await authenticateWithToken(options.token);
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const apiUrl = getApiUrl();
|
|
1168
|
+
const spinner = (0, import_ora7.default)("Initializing authentication...").start();
|
|
1169
|
+
try {
|
|
1170
|
+
const response = await fetch(`${apiUrl}/cli/auth/device`, {
|
|
1171
|
+
method: "POST"
|
|
1172
|
+
});
|
|
1173
|
+
if (!response.ok) {
|
|
1174
|
+
spinner.fail(import_chalk5.default.red("Failed to initialize authentication"));
|
|
1175
|
+
console.log(import_chalk5.default.red("\nPlease check your internet connection and try again."));
|
|
1176
|
+
process.exit(1);
|
|
1242
1177
|
}
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
console.log(
|
|
1178
|
+
const deviceData = await response.json();
|
|
1179
|
+
spinner.stop();
|
|
1180
|
+
console.log(import_chalk5.default.gray(" Opening browser for authentication...\n"));
|
|
1181
|
+
console.log(import_chalk5.default.gray(" If browser doesn't open, visit:"));
|
|
1182
|
+
console.log(` \u{1F517} ${import_chalk5.default.cyan.underline(deviceData.verification_uri_complete)}`);
|
|
1246
1183
|
console.log();
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
console.log(`
|
|
1253
|
-
${colors.muted("Try running:")} ${colors.white("npx ccgather auth")}`);
|
|
1184
|
+
try {
|
|
1185
|
+
await (0, import_open.default)(deviceData.verification_uri_complete);
|
|
1186
|
+
} catch {
|
|
1187
|
+
console.log(import_chalk5.default.yellow(" Could not open browser automatically."));
|
|
1188
|
+
console.log(import_chalk5.default.yellow(" Please open the URL above manually."));
|
|
1254
1189
|
}
|
|
1255
|
-
|
|
1190
|
+
const pollSpinner = (0, import_ora7.default)("Waiting for authorization...").start();
|
|
1191
|
+
const startTime = Date.now();
|
|
1192
|
+
const expiresAt = startTime + deviceData.expires_in * 1e3;
|
|
1193
|
+
const pollInterval = Math.max(deviceData.interval * 1e3, 5e3);
|
|
1194
|
+
while (Date.now() < expiresAt) {
|
|
1195
|
+
await sleep(pollInterval);
|
|
1196
|
+
try {
|
|
1197
|
+
const pollResponse = await fetch(
|
|
1198
|
+
`${apiUrl}/cli/auth/device/poll?device_code=${deviceData.device_code}`
|
|
1199
|
+
);
|
|
1200
|
+
const pollData = await pollResponse.json();
|
|
1201
|
+
if (pollData.status === "authorized" && pollData.token) {
|
|
1202
|
+
pollSpinner.succeed(import_chalk5.default.green("Authentication successful!"));
|
|
1203
|
+
config.set("apiToken", pollData.token);
|
|
1204
|
+
config.set("userId", pollData.userId);
|
|
1205
|
+
config.set("username", pollData.username);
|
|
1206
|
+
console.log(import_chalk5.default.gray(`
|
|
1207
|
+
Welcome, ${import_chalk5.default.white(pollData.username)}!
|
|
1208
|
+
`));
|
|
1209
|
+
console.log(import_chalk5.default.bold("\u{1F4CA} Submitting your usage data...\n"));
|
|
1210
|
+
await submit({ yes: true });
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
if (pollData.status === "expired" || pollData.status === "used") {
|
|
1214
|
+
pollSpinner.fail(import_chalk5.default.red("Authentication expired or already used"));
|
|
1215
|
+
console.log(import_chalk5.default.gray('\nPlease run "ccgather auth" to try again.\n'));
|
|
1216
|
+
process.exit(1);
|
|
1217
|
+
}
|
|
1218
|
+
const remaining = Math.ceil((expiresAt - Date.now()) / 1e3);
|
|
1219
|
+
pollSpinner.text = `Waiting for authorization... (${remaining}s remaining)`;
|
|
1220
|
+
} catch {
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
pollSpinner.fail(import_chalk5.default.red("Authentication timed out"));
|
|
1224
|
+
console.log(import_chalk5.default.gray('\nPlease run "ccgather auth" to try again.\n'));
|
|
1225
|
+
process.exit(1);
|
|
1226
|
+
} catch (error2) {
|
|
1227
|
+
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
1228
|
+
console.log(import_chalk5.default.red(`
|
|
1229
|
+
Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`));
|
|
1230
|
+
process.exit(1);
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
async function authenticateWithToken(token) {
|
|
1234
|
+
const config = getConfig();
|
|
1235
|
+
const apiUrl = getApiUrl();
|
|
1236
|
+
const spinner = (0, import_ora7.default)("Verifying token...").start();
|
|
1237
|
+
try {
|
|
1238
|
+
const response = await fetch(`${apiUrl}/cli/verify`, {
|
|
1239
|
+
method: "POST",
|
|
1240
|
+
headers: {
|
|
1241
|
+
"Content-Type": "application/json",
|
|
1242
|
+
Authorization: `Bearer ${token}`
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
if (!response.ok) {
|
|
1246
|
+
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
1247
|
+
const errorData = await response.json().catch(() => ({}));
|
|
1248
|
+
console.log(import_chalk5.default.red(`Error: ${errorData.error || "Invalid token"}`));
|
|
1249
|
+
console.log(import_chalk5.default.gray("\nMake sure your token is correct and try again."));
|
|
1250
|
+
process.exit(1);
|
|
1251
|
+
}
|
|
1252
|
+
const data = await response.json();
|
|
1253
|
+
config.set("apiToken", token);
|
|
1254
|
+
config.set("userId", data.userId);
|
|
1255
|
+
config.set("username", data.username);
|
|
1256
|
+
spinner.succeed(import_chalk5.default.green("Authentication successful!"));
|
|
1257
|
+
console.log(import_chalk5.default.gray(`
|
|
1258
|
+
Welcome, ${import_chalk5.default.white(data.username)}!
|
|
1259
|
+
`));
|
|
1260
|
+
console.log(import_chalk5.default.bold("\u{1F4CA} Submitting your usage data...\n"));
|
|
1261
|
+
await submit({ yes: true });
|
|
1262
|
+
} catch (error2) {
|
|
1263
|
+
spinner.fail(import_chalk5.default.red("Authentication failed"));
|
|
1264
|
+
console.log(import_chalk5.default.red(`
|
|
1265
|
+
Error: ${error2 instanceof Error ? error2.message : "Unknown error"}`));
|
|
1256
1266
|
process.exit(1);
|
|
1257
1267
|
}
|
|
1258
1268
|
}
|
|
1269
|
+
function sleep(ms) {
|
|
1270
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1271
|
+
}
|
|
1272
|
+
var import_chalk5, import_ora7, import_inquirer2, import_open;
|
|
1273
|
+
var init_auth = __esm({
|
|
1274
|
+
"src/commands/auth.ts"() {
|
|
1275
|
+
"use strict";
|
|
1276
|
+
import_chalk5 = __toESM(require("chalk"));
|
|
1277
|
+
import_ora7 = __toESM(require("ora"));
|
|
1278
|
+
import_inquirer2 = __toESM(require("inquirer"));
|
|
1279
|
+
import_open = __toESM(require("open"));
|
|
1280
|
+
init_config();
|
|
1281
|
+
init_submit();
|
|
1282
|
+
}
|
|
1283
|
+
});
|
|
1284
|
+
|
|
1285
|
+
// src/index.ts
|
|
1286
|
+
var import_commander = require("commander");
|
|
1287
|
+
var import_inquirer3 = __toESM(require("inquirer"));
|
|
1288
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
1289
|
+
var import_update_notifier = __toESM(require("update-notifier"));
|
|
1290
|
+
init_submit();
|
|
1259
1291
|
|
|
1260
1292
|
// src/commands/status.ts
|
|
1261
1293
|
var import_ora2 = __toESM(require("ora"));
|
|
1262
1294
|
init_config();
|
|
1263
1295
|
init_api();
|
|
1296
|
+
init_ui();
|
|
1264
1297
|
async function status(options) {
|
|
1265
1298
|
if (!isAuthenticated()) {
|
|
1266
1299
|
if (options.json) {
|
|
@@ -1404,10 +1437,13 @@ function createCallbackServer() {
|
|
|
1404
1437
|
server.on("error", (err) => {
|
|
1405
1438
|
reject(new Error(`Failed to start callback server: ${err.message}`));
|
|
1406
1439
|
});
|
|
1407
|
-
setTimeout(
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1440
|
+
setTimeout(
|
|
1441
|
+
() => {
|
|
1442
|
+
server.close();
|
|
1443
|
+
reject(new Error("Authentication timed out"));
|
|
1444
|
+
},
|
|
1445
|
+
5 * 60 * 1e3
|
|
1446
|
+
);
|
|
1411
1447
|
});
|
|
1412
1448
|
}
|
|
1413
1449
|
function generateSyncScript(apiUrl, apiToken) {
|
|
@@ -1625,7 +1661,9 @@ async function setupAuto(options = {}) {
|
|
|
1625
1661
|
console.log(import_chalk3.default.gray("This will install a hook that automatically syncs"));
|
|
1626
1662
|
console.log(import_chalk3.default.gray("your usage data when Claude Code sessions end."));
|
|
1627
1663
|
console.log();
|
|
1628
|
-
console.log(
|
|
1664
|
+
console.log(
|
|
1665
|
+
import_chalk3.default.yellow("Note: Manual submission (`npx ccgather`) is recommended for most users.")
|
|
1666
|
+
);
|
|
1629
1667
|
console.log();
|
|
1630
1668
|
const inquirer4 = await import("inquirer");
|
|
1631
1669
|
const { proceed } = await inquirer4.default.prompt([
|
|
@@ -1715,6 +1753,8 @@ async function setupAuto(options = {}) {
|
|
|
1715
1753
|
|
|
1716
1754
|
// src/commands/scan.ts
|
|
1717
1755
|
var import_ora5 = __toESM(require("ora"));
|
|
1756
|
+
init_ccgather_json();
|
|
1757
|
+
init_ui();
|
|
1718
1758
|
function displayResults(data) {
|
|
1719
1759
|
console.log();
|
|
1720
1760
|
const usageLines = [
|
|
@@ -1833,9 +1873,22 @@ async function scan(options = {}) {
|
|
|
1833
1873
|
}
|
|
1834
1874
|
|
|
1835
1875
|
// src/index.ts
|
|
1876
|
+
init_ui();
|
|
1836
1877
|
init_config();
|
|
1837
1878
|
init_api();
|
|
1838
|
-
var VERSION = "1.3.
|
|
1879
|
+
var VERSION = "1.3.3";
|
|
1880
|
+
var pkg = { name: "ccgather", version: VERSION };
|
|
1881
|
+
var notifier = (0, import_update_notifier.default)({ pkg, updateCheckInterval: 1e3 * 60 * 60 });
|
|
1882
|
+
notifier.notify({
|
|
1883
|
+
message: `${import_chalk6.default.yellow("Update")} available ${import_chalk6.default.dim(VERSION)} \u2192 ${import_chalk6.default.yellow(notifier.update?.latest || "")}
|
|
1884
|
+
${import_chalk6.default.yellow("Run")} ${import_chalk6.default.cyan("npx ccgather@latest")} to update`,
|
|
1885
|
+
boxenOptions: {
|
|
1886
|
+
padding: 1,
|
|
1887
|
+
margin: 1,
|
|
1888
|
+
borderStyle: "round",
|
|
1889
|
+
borderColor: "yellow"
|
|
1890
|
+
}
|
|
1891
|
+
});
|
|
1839
1892
|
var program = new import_commander.Command();
|
|
1840
1893
|
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)");
|
|
1841
1894
|
program.command("scan").description("Scan Claude Code usage and create ccgather.json").option("-a, --all", "Scan all-time usage (no date limit)").option("-d, --days <number>", "Number of days to scan (default: 30)", parseInt).action((opts) => scan(opts));
|