@tokscale/cli 1.0.24 → 1.1.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/cli.js +127 -41
- package/dist/cli.js.map +1 -1
- package/dist/cursor.d.ts +38 -3
- package/dist/cursor.d.ts.map +1 -1
- package/dist/cursor.js +520 -46
- package/dist/cursor.js.map +1 -1
- package/dist/submit.d.ts.map +1 -1
- package/dist/submit.js +33 -5
- package/dist/submit.js.map +1 -1
- package/dist/tui/hooks/useData.d.ts.map +1 -1
- package/dist/tui/hooks/useData.js +9 -4
- package/dist/tui/hooks/useData.js.map +1 -1
- package/dist/wrapped.d.ts.map +1 -1
- package/dist/wrapped.js +8 -4
- package/dist/wrapped.js.map +1 -1
- package/package.json +2 -5
- package/src/cli.ts +143 -43
- package/src/cursor.ts +537 -51
- package/src/submit.ts +37 -5
- package/src/tui/hooks/useData.ts +10 -4
- package/src/wrapped.ts +9 -4
package/src/submit.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { exec } from "node:child_process";
|
|
|
10
10
|
import { promisify } from "node:util";
|
|
11
11
|
import { loadCredentials, getApiBaseUrl, loadStarCache, saveStarCache } from "./credentials.js";
|
|
12
12
|
import { parseLocalSourcesAsync, finalizeReportAndGraphAsync, type ParsedMessages } from "./native.js";
|
|
13
|
-
import { syncCursorCache,
|
|
13
|
+
import { syncCursorCache, isCursorLoggedIn, hasCursorUsageCache } from "./cursor.js";
|
|
14
14
|
import type { TokenContributionData } from "./graph-types.js";
|
|
15
15
|
import { formatCurrency } from "./table.js";
|
|
16
16
|
|
|
@@ -179,6 +179,9 @@ async function handleStarPrompt(username: string): Promise<void> {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
export async function submit(options: SubmitOptions = {}): Promise<void> {
|
|
182
|
+
const submitStart = performance.now();
|
|
183
|
+
const timing: Record<string, number> = {};
|
|
184
|
+
|
|
182
185
|
const credentials = loadCredentials();
|
|
183
186
|
if (!credentials) {
|
|
184
187
|
console.log(pc.yellow("\n Not logged in."));
|
|
@@ -186,7 +189,9 @@ export async function submit(options: SubmitOptions = {}): Promise<void> {
|
|
|
186
189
|
process.exit(1);
|
|
187
190
|
}
|
|
188
191
|
|
|
192
|
+
let t0 = performance.now();
|
|
189
193
|
await handleStarPrompt(credentials.username);
|
|
194
|
+
timing["star-prompt"] = performance.now() - t0;
|
|
190
195
|
|
|
191
196
|
console.log(pc.cyan("\n Tokscale - Submit Usage Data\n"));
|
|
192
197
|
|
|
@@ -214,6 +219,7 @@ export async function submit(options: SubmitOptions = {}): Promise<void> {
|
|
|
214
219
|
try {
|
|
215
220
|
// Two-phase processing (same as TUI) for consistency:
|
|
216
221
|
// Phase 1: Parse local sources + sync cursor in parallel
|
|
222
|
+
t0 = performance.now();
|
|
217
223
|
const [localMessages, cursorSync] = await Promise.all([
|
|
218
224
|
parseLocalSourcesAsync({
|
|
219
225
|
sources: localSources,
|
|
@@ -221,20 +227,28 @@ export async function submit(options: SubmitOptions = {}): Promise<void> {
|
|
|
221
227
|
until: options.until,
|
|
222
228
|
year: options.year,
|
|
223
229
|
}),
|
|
224
|
-
includeCursor &&
|
|
230
|
+
includeCursor && isCursorLoggedIn()
|
|
225
231
|
? syncCursorCache()
|
|
226
|
-
: Promise.resolve({ synced: false, rows: 0 }),
|
|
232
|
+
: Promise.resolve({ synced: false, rows: 0, error: undefined }),
|
|
227
233
|
]);
|
|
234
|
+
timing["parse-local+cursor-sync"] = performance.now() - t0;
|
|
235
|
+
|
|
236
|
+
if (includeCursor && cursorSync.error && (cursorSync.synced || hasCursorUsageCache())) {
|
|
237
|
+
const prefix = cursorSync.synced ? "Cursor sync warning" : "Cursor sync failed; using cached data";
|
|
238
|
+
console.log(pc.yellow(` ${prefix}: ${cursorSync.error}`));
|
|
239
|
+
}
|
|
228
240
|
|
|
229
241
|
// Phase 2: Finalize with pricing (combines local + cursor)
|
|
230
242
|
// Single subprocess call ensures consistent pricing for both report and graph
|
|
243
|
+
t0 = performance.now();
|
|
231
244
|
const { report, graph } = await finalizeReportAndGraphAsync({
|
|
232
245
|
localMessages,
|
|
233
|
-
includeCursor: includeCursor && cursorSync.synced,
|
|
246
|
+
includeCursor: includeCursor && (cursorSync.synced || hasCursorUsageCache()),
|
|
234
247
|
since: options.since,
|
|
235
248
|
until: options.until,
|
|
236
249
|
year: options.year,
|
|
237
250
|
});
|
|
251
|
+
timing["finalize-report+graph"] = performance.now() - t0;
|
|
238
252
|
|
|
239
253
|
// Use graph structure for submission, report's cost for display
|
|
240
254
|
data = graph;
|
|
@@ -252,6 +266,8 @@ export async function submit(options: SubmitOptions = {}): Promise<void> {
|
|
|
252
266
|
console.log(pc.gray(` Total cost: ${formatCurrency(data.summary.totalCost)}`));
|
|
253
267
|
console.log(pc.gray(` Sources: ${data.summary.sources.join(", ")}`));
|
|
254
268
|
console.log(pc.gray(` Models: ${data.summary.models.length} models`));
|
|
269
|
+
console.log(pc.gray(` Contributions: ${data.contributions.length} days`));
|
|
270
|
+
console.log(pc.gray(` Payload size: ${(JSON.stringify(data).length / 1024).toFixed(1)} KB`));
|
|
255
271
|
console.log();
|
|
256
272
|
|
|
257
273
|
if (data.summary.totalTokens === 0) {
|
|
@@ -271,16 +287,22 @@ export async function submit(options: SubmitOptions = {}): Promise<void> {
|
|
|
271
287
|
const baseUrl = getApiBaseUrl();
|
|
272
288
|
|
|
273
289
|
try {
|
|
290
|
+
t0 = performance.now();
|
|
291
|
+
const body = JSON.stringify(data);
|
|
292
|
+
timing["json-serialize"] = performance.now() - t0;
|
|
293
|
+
|
|
294
|
+
t0 = performance.now();
|
|
274
295
|
const response = await fetch(`${baseUrl}/api/submit`, {
|
|
275
296
|
method: "POST",
|
|
276
297
|
headers: {
|
|
277
298
|
"Content-Type": "application/json",
|
|
278
299
|
Authorization: `Bearer ${credentials.token}`,
|
|
279
300
|
},
|
|
280
|
-
body
|
|
301
|
+
body,
|
|
281
302
|
});
|
|
282
303
|
|
|
283
304
|
const result: SubmitResponse = await response.json();
|
|
305
|
+
timing["server-roundtrip"] = performance.now() - t0;
|
|
284
306
|
|
|
285
307
|
if (!response.ok) {
|
|
286
308
|
console.error(pc.red(`\n Error: ${result.error || "Submission failed"}`));
|
|
@@ -317,4 +339,14 @@ export async function submit(options: SubmitOptions = {}): Promise<void> {
|
|
|
317
339
|
console.error(pc.gray(` ${(error as Error).message}\n`));
|
|
318
340
|
process.exit(1);
|
|
319
341
|
}
|
|
342
|
+
|
|
343
|
+
// Timing summary
|
|
344
|
+
timing["total"] = performance.now() - submitStart;
|
|
345
|
+
console.log(pc.gray(" ⏱ Timing breakdown:"));
|
|
346
|
+
for (const [label, ms] of Object.entries(timing)) {
|
|
347
|
+
const formatted = ms < 1000 ? `${ms.toFixed(0)}ms` : `${(ms / 1000).toFixed(2)}s`;
|
|
348
|
+
const bar = "█".repeat(Math.max(1, Math.round(ms / timing["total"] * 20)));
|
|
349
|
+
console.log(pc.gray(` ${label.padEnd(24)} ${formatted.padStart(8)} ${bar}`));
|
|
350
|
+
}
|
|
351
|
+
console.log();
|
|
320
352
|
}
|
package/src/tui/hooks/useData.ts
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
type ParsedMessages,
|
|
21
21
|
} from "../../native.js";
|
|
22
22
|
|
|
23
|
-
import { syncCursorCache,
|
|
23
|
+
import { syncCursorCache, isCursorLoggedIn, hasCursorUsageCache } from "../../cursor.js";
|
|
24
24
|
import { getModelColor } from "../utils/colors.js";
|
|
25
25
|
import { loadCachedData, saveCachedData, isCacheStale, loadSettings } from "../config/settings.js";
|
|
26
26
|
|
|
@@ -156,7 +156,7 @@ async function loadData(
|
|
|
156
156
|
setPhase?.("parsing-sources");
|
|
157
157
|
|
|
158
158
|
const phase1Results = await Promise.allSettled([
|
|
159
|
-
includeCursor &&
|
|
159
|
+
includeCursor && isCursorLoggedIn() ? syncCursorCache() : Promise.resolve({ synced: false, rows: 0, error: undefined }),
|
|
160
160
|
localSources.length > 0
|
|
161
161
|
? parseLocalSourcesAsync({ sources: localSources as ("opencode" | "claude" | "codex" | "gemini" | "amp" | "droid")[], since, until, year })
|
|
162
162
|
: Promise.resolve({ messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, processingTimeMs: 0 } as ParsedMessages),
|
|
@@ -164,11 +164,17 @@ async function loadData(
|
|
|
164
164
|
|
|
165
165
|
const cursorSync = phase1Results[0].status === "fulfilled"
|
|
166
166
|
? phase1Results[0].value
|
|
167
|
-
: { synced: false, rows: 0 };
|
|
167
|
+
: { synced: false, rows: 0, error: "Cursor sync failed" };
|
|
168
168
|
const localMessages = phase1Results[1].status === "fulfilled"
|
|
169
169
|
? phase1Results[1].value
|
|
170
170
|
: null;
|
|
171
171
|
|
|
172
|
+
if (includeCursor && cursorSync.error && (cursorSync.synced || hasCursorUsageCache())) {
|
|
173
|
+
// TUI should keep working; just emit a warning.
|
|
174
|
+
const prefix = cursorSync.synced ? "Cursor sync warning" : "Cursor sync failed; using cached data";
|
|
175
|
+
console.warn(`${prefix}: ${cursorSync.error}`);
|
|
176
|
+
}
|
|
177
|
+
|
|
172
178
|
const emptyMessages: ParsedMessages = {
|
|
173
179
|
messages: [],
|
|
174
180
|
opencodeCount: 0,
|
|
@@ -184,7 +190,7 @@ async function loadData(
|
|
|
184
190
|
// Single call ensures consistent pricing between report and graph
|
|
185
191
|
const { report, graph } = await finalizeReportAndGraphAsync({
|
|
186
192
|
localMessages: localMessages || emptyMessages,
|
|
187
|
-
includeCursor: includeCursor && cursorSync.synced,
|
|
193
|
+
includeCursor: includeCursor && (cursorSync.synced || hasCursorUsageCache()),
|
|
188
194
|
since,
|
|
189
195
|
until,
|
|
190
196
|
year,
|
package/src/wrapped.ts
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
finalizeReportAndGraphAsync,
|
|
10
10
|
type ParsedMessages,
|
|
11
11
|
} from "./native.js";
|
|
12
|
-
import { syncCursorCache,
|
|
12
|
+
import { syncCursorCache, isCursorLoggedIn, hasCursorUsageCache } from "./cursor.js";
|
|
13
13
|
import { loadCredentials } from "./credentials.js";
|
|
14
14
|
import type { SourceType } from "./graph-types.js";
|
|
15
15
|
|
|
@@ -218,7 +218,7 @@ async function loadWrappedData(options: WrappedOptions): Promise<WrappedData> {
|
|
|
218
218
|
const until = `${year}-12-31`;
|
|
219
219
|
|
|
220
220
|
const phase1Results = await Promise.allSettled([
|
|
221
|
-
includeCursor &&
|
|
221
|
+
includeCursor && isCursorLoggedIn() ? syncCursorCache() : Promise.resolve({ synced: false, rows: 0, error: undefined }),
|
|
222
222
|
localSources.length > 0
|
|
223
223
|
? parseLocalSourcesAsync({ sources: localSources, since, until, year })
|
|
224
224
|
: Promise.resolve({ messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, processingTimeMs: 0 } as ParsedMessages),
|
|
@@ -226,11 +226,16 @@ async function loadWrappedData(options: WrappedOptions): Promise<WrappedData> {
|
|
|
226
226
|
|
|
227
227
|
const cursorSync = phase1Results[0].status === "fulfilled"
|
|
228
228
|
? phase1Results[0].value
|
|
229
|
-
: { synced: false, rows: 0 };
|
|
229
|
+
: { synced: false, rows: 0, error: "Cursor sync failed" };
|
|
230
230
|
const localMessages = phase1Results[1].status === "fulfilled"
|
|
231
231
|
? phase1Results[1].value
|
|
232
232
|
: null;
|
|
233
233
|
|
|
234
|
+
if (includeCursor && cursorSync.error && (cursorSync.synced || hasCursorUsageCache())) {
|
|
235
|
+
const prefix = cursorSync.synced ? "Cursor sync warning" : "Cursor sync failed; using cached data";
|
|
236
|
+
console.log(pc.yellow(` ${prefix}: ${cursorSync.error}`));
|
|
237
|
+
}
|
|
238
|
+
|
|
234
239
|
const emptyMessages: ParsedMessages = {
|
|
235
240
|
messages: [],
|
|
236
241
|
opencodeCount: 0,
|
|
@@ -244,7 +249,7 @@ async function loadWrappedData(options: WrappedOptions): Promise<WrappedData> {
|
|
|
244
249
|
|
|
245
250
|
const { report, graph } = await finalizeReportAndGraphAsync({
|
|
246
251
|
localMessages: localMessages || emptyMessages,
|
|
247
|
-
includeCursor: includeCursor && cursorSync.synced,
|
|
252
|
+
includeCursor: includeCursor && (cursorSync.synced || hasCursorUsageCache()),
|
|
248
253
|
since,
|
|
249
254
|
until,
|
|
250
255
|
year,
|