@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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tokscale/cli",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A high-performance CLI tool and visualization dashboard for tracking AI coding assistant token usage and costs across multiple platforms.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -29,23 +29,20 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@napi-rs/canvas": "^0.1.68",
|
|
31
31
|
"@opentui/core": "0.1.60",
|
|
32
|
-
"@opentui/react": "^0.1.60",
|
|
33
32
|
"@opentui/solid": "^0.1.60",
|
|
34
33
|
"@resvg/resvg-js": "^2.6.2",
|
|
35
|
-
"@tokscale/core": "1.0
|
|
34
|
+
"@tokscale/core": "1.1.0",
|
|
36
35
|
"cli-table3": "^0.6.5",
|
|
37
36
|
"clipboardy": "^5.0.2",
|
|
38
37
|
"commander": "^14.0.2",
|
|
39
38
|
"csv-parse": "^5.6.0",
|
|
40
39
|
"date-fns": "^4.1.0",
|
|
41
40
|
"picocolors": "^1.1.1",
|
|
42
|
-
"react": "^19.2.3",
|
|
43
41
|
"solid-js": "1.9.9",
|
|
44
42
|
"string-width": "^8.1.0"
|
|
45
43
|
},
|
|
46
44
|
"devDependencies": {
|
|
47
45
|
"@types/node": "^20.0.0",
|
|
48
|
-
"@types/react": "^19.2.7",
|
|
49
46
|
"typescript": "^5.0.0"
|
|
50
47
|
},
|
|
51
48
|
"engines": {
|
package/src/cli.ts
CHANGED
|
@@ -16,9 +16,16 @@ import { submit } from "./submit.js";
|
|
|
16
16
|
import { generateWrapped } from "./wrapped.js";
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
|
+
ensureCursorMigration,
|
|
19
20
|
loadCursorCredentials,
|
|
20
21
|
saveCursorCredentials,
|
|
21
22
|
clearCursorCredentials,
|
|
23
|
+
clearCursorCredentialsAndCache,
|
|
24
|
+
isCursorLoggedIn,
|
|
25
|
+
hasCursorUsageCache,
|
|
26
|
+
listCursorAccounts,
|
|
27
|
+
setActiveCursorAccount,
|
|
28
|
+
removeCursorAccount,
|
|
22
29
|
validateCursorSession,
|
|
23
30
|
readCursorUsage,
|
|
24
31
|
getCursorCredentialsPath,
|
|
@@ -428,22 +435,69 @@ async function main() {
|
|
|
428
435
|
cursorCommand
|
|
429
436
|
.command("login")
|
|
430
437
|
.description("Login to Cursor (paste your session token)")
|
|
431
|
-
.
|
|
432
|
-
|
|
438
|
+
.option("--name <name>", "Label for this Cursor account (e.g., work, personal)")
|
|
439
|
+
.action(async (options: { name?: string }) => {
|
|
440
|
+
ensureCursorMigration();
|
|
441
|
+
await cursorLogin(options);
|
|
433
442
|
});
|
|
434
443
|
|
|
435
444
|
cursorCommand
|
|
436
445
|
.command("logout")
|
|
437
|
-
.description("Logout from Cursor")
|
|
438
|
-
.
|
|
439
|
-
|
|
446
|
+
.description("Logout from a Cursor account")
|
|
447
|
+
.option("--name <name>", "Account label or id")
|
|
448
|
+
.option("--all", "Logout from all Cursor accounts")
|
|
449
|
+
.option("--purge-cache", "Also delete cached Cursor usage for the logged-out account(s)")
|
|
450
|
+
.action(async (options: { name?: string; all?: boolean; purgeCache?: boolean }) => {
|
|
451
|
+
ensureCursorMigration();
|
|
452
|
+
await cursorLogout(options);
|
|
440
453
|
});
|
|
441
454
|
|
|
442
455
|
cursorCommand
|
|
443
456
|
.command("status")
|
|
444
457
|
.description("Check Cursor authentication status")
|
|
445
|
-
.
|
|
446
|
-
|
|
458
|
+
.option("--name <name>", "Account label or id")
|
|
459
|
+
.action(async (options: { name?: string }) => {
|
|
460
|
+
ensureCursorMigration();
|
|
461
|
+
await cursorStatus(options);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
cursorCommand
|
|
465
|
+
.command("accounts")
|
|
466
|
+
.description("List saved Cursor accounts")
|
|
467
|
+
.option("--json", "Output as JSON")
|
|
468
|
+
.action(async (options: { json?: boolean }) => {
|
|
469
|
+
ensureCursorMigration();
|
|
470
|
+
const accounts = listCursorAccounts();
|
|
471
|
+
if (options.json) {
|
|
472
|
+
console.log(JSON.stringify({ accounts }, null, 2));
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
if (accounts.length === 0) {
|
|
477
|
+
console.log(pc.yellow("\n No saved Cursor accounts.\n"));
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
console.log(pc.cyan("\n Cursor IDE - Accounts\n"));
|
|
482
|
+
for (const acct of accounts) {
|
|
483
|
+
const name = acct.label ? `${acct.label} ${pc.gray(`(${acct.id})`)}` : acct.id;
|
|
484
|
+
console.log(` ${acct.isActive ? pc.green("*") : pc.gray("-")} ${name}`);
|
|
485
|
+
}
|
|
486
|
+
console.log();
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
cursorCommand
|
|
490
|
+
.command("switch")
|
|
491
|
+
.description("Switch active Cursor account")
|
|
492
|
+
.argument("<name>", "Account label or id")
|
|
493
|
+
.action(async (name: string) => {
|
|
494
|
+
ensureCursorMigration();
|
|
495
|
+
const result = setActiveCursorAccount(name);
|
|
496
|
+
if (!result.ok) {
|
|
497
|
+
console.log(pc.red(`\n Error: ${result.error}\n`));
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
console.log(pc.green(`\n Active Cursor account set to ${pc.bold(name)}\n`));
|
|
447
501
|
});
|
|
448
502
|
|
|
449
503
|
// Check if a subcommand was provided
|
|
@@ -521,8 +575,7 @@ function getEnabledSources(options: FilterOptions): SourceType[] | undefined {
|
|
|
521
575
|
* Only attempts sync if user is authenticated with Cursor.
|
|
522
576
|
*/
|
|
523
577
|
async function syncCursorData(): Promise<CursorSyncResult> {
|
|
524
|
-
|
|
525
|
-
if (!credentials) {
|
|
578
|
+
if (!isCursorLoggedIn()) {
|
|
526
579
|
return { attempted: false, synced: false, rows: 0 };
|
|
527
580
|
}
|
|
528
581
|
|
|
@@ -578,8 +631,7 @@ async function showModelReport(options: FilterOptions & DateFilterOptions & { be
|
|
|
578
631
|
|
|
579
632
|
// Check cursor auth early if cursor-only mode
|
|
580
633
|
if (onlyCursor) {
|
|
581
|
-
|
|
582
|
-
if (!credentials) {
|
|
634
|
+
if (!isCursorLoggedIn() && !hasCursorUsageCache()) {
|
|
583
635
|
console.log(pc.red("\n Error: Cursor authentication required."));
|
|
584
636
|
console.log(pc.gray(" Run 'tokscale cursor login' to authenticate with Cursor.\n"));
|
|
585
637
|
process.exit(1);
|
|
@@ -610,6 +662,11 @@ async function showModelReport(options: FilterOptions & DateFilterOptions & { be
|
|
|
610
662
|
dateFilters,
|
|
611
663
|
(phase) => spinner?.update(phase)
|
|
612
664
|
);
|
|
665
|
+
|
|
666
|
+
if (includeCursor && cursorSync.attempted && cursorSync.error) {
|
|
667
|
+
// Don't block report generation; just warn about partial Cursor sync.
|
|
668
|
+
console.log(pc.yellow(` Cursor sync warning: ${cursorSync.error}`));
|
|
669
|
+
}
|
|
613
670
|
|
|
614
671
|
if (!localMessages && !onlyCursor) {
|
|
615
672
|
if (spinner) {
|
|
@@ -628,7 +685,7 @@ async function showModelReport(options: FilterOptions & DateFilterOptions & { be
|
|
|
628
685
|
const emptyMessages: ParsedMessages = { messages: [], opencodeCount: 0, claudeCount: 0, codexCount: 0, geminiCount: 0, ampCount: 0, droidCount: 0, processingTimeMs: 0 };
|
|
629
686
|
report = await finalizeReportAsync({
|
|
630
687
|
localMessages: localMessages || emptyMessages,
|
|
631
|
-
includeCursor: includeCursor && cursorSync.synced,
|
|
688
|
+
includeCursor: includeCursor && (cursorSync.synced || hasCursorUsageCache()),
|
|
632
689
|
since: dateFilters.since,
|
|
633
690
|
until: dateFilters.until,
|
|
634
691
|
year: dateFilters.year,
|
|
@@ -760,7 +817,7 @@ async function showMonthlyReport(options: FilterOptions & DateFilterOptions & {
|
|
|
760
817
|
try {
|
|
761
818
|
report = await finalizeMonthlyReportAsync({
|
|
762
819
|
localMessages,
|
|
763
|
-
includeCursor: includeCursor && cursorSync.synced,
|
|
820
|
+
includeCursor: includeCursor && (cursorSync.synced || hasCursorUsageCache()),
|
|
764
821
|
since: dateFilters.since,
|
|
765
822
|
until: dateFilters.until,
|
|
766
823
|
year: dateFilters.year,
|
|
@@ -859,7 +916,7 @@ async function outputJsonReport(
|
|
|
859
916
|
if (reportType === "models") {
|
|
860
917
|
const report = await finalizeReportAsync({
|
|
861
918
|
localMessages: localMessages || emptyMessages,
|
|
862
|
-
includeCursor: includeCursor && cursorSync.synced,
|
|
919
|
+
includeCursor: includeCursor && (cursorSync.synced || hasCursorUsageCache()),
|
|
863
920
|
since: dateFilters.since,
|
|
864
921
|
until: dateFilters.until,
|
|
865
922
|
year: dateFilters.year,
|
|
@@ -868,7 +925,7 @@ async function outputJsonReport(
|
|
|
868
925
|
} else {
|
|
869
926
|
const report = await finalizeMonthlyReportAsync({
|
|
870
927
|
localMessages: localMessages || emptyMessages,
|
|
871
|
-
includeCursor: includeCursor && cursorSync.synced,
|
|
928
|
+
includeCursor: includeCursor && (cursorSync.synced || hasCursorUsageCache()),
|
|
872
929
|
since: dateFilters.since,
|
|
873
930
|
until: dateFilters.until,
|
|
874
931
|
year: dateFilters.year,
|
|
@@ -911,7 +968,7 @@ async function handleGraphCommand(options: GraphCommandOptions) {
|
|
|
911
968
|
|
|
912
969
|
const data = await finalizeGraphAsync({
|
|
913
970
|
localMessages,
|
|
914
|
-
includeCursor: includeCursor && cursorSync.synced,
|
|
971
|
+
includeCursor: includeCursor && (cursorSync.synced || hasCursorUsageCache()),
|
|
915
972
|
since: dateFilters.since,
|
|
916
973
|
until: dateFilters.until,
|
|
917
974
|
year: dateFilters.year,
|
|
@@ -1124,14 +1181,7 @@ function getSourceLabel(source: string): string {
|
|
|
1124
1181
|
// Cursor IDE Authentication
|
|
1125
1182
|
// =============================================================================
|
|
1126
1183
|
|
|
1127
|
-
async function cursorLogin(): Promise<void> {
|
|
1128
|
-
const credentials = loadCursorCredentials();
|
|
1129
|
-
if (credentials) {
|
|
1130
|
-
console.log(pc.yellow("\n Already logged in to Cursor."));
|
|
1131
|
-
console.log(pc.gray(" Run 'tokscale cursor logout' to sign out first.\n"));
|
|
1132
|
-
return;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1184
|
+
async function cursorLogin(options: { name?: string } = {}): Promise<void> {
|
|
1135
1185
|
console.log(pc.cyan("\n Cursor IDE - Login\n"));
|
|
1136
1186
|
console.log(pc.white(" To get your session token:"));
|
|
1137
1187
|
console.log(pc.gray(" 1. Open https://www.cursor.com/settings in your browser"));
|
|
@@ -1169,47 +1219,97 @@ async function cursorLogin(): Promise<void> {
|
|
|
1169
1219
|
return;
|
|
1170
1220
|
}
|
|
1171
1221
|
|
|
1172
|
-
// Save credentials
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1222
|
+
// Save credentials (multi-account)
|
|
1223
|
+
let savedAccountId: string;
|
|
1224
|
+
try {
|
|
1225
|
+
const saved = saveCursorCredentials(
|
|
1226
|
+
{
|
|
1227
|
+
sessionToken: token,
|
|
1228
|
+
createdAt: new Date().toISOString(),
|
|
1229
|
+
},
|
|
1230
|
+
{ label: options.name }
|
|
1231
|
+
);
|
|
1232
|
+
savedAccountId = saved.accountId;
|
|
1233
|
+
} catch (e) {
|
|
1234
|
+
console.log(pc.red(`\n Failed to save credentials: ${(e as Error).message}\n`));
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1177
1237
|
|
|
1178
1238
|
console.log(pc.green("\n Success! Logged in to Cursor."));
|
|
1239
|
+
if (options.name) {
|
|
1240
|
+
console.log(pc.gray(` Account: ${options.name} (${savedAccountId})`));
|
|
1241
|
+
} else {
|
|
1242
|
+
console.log(pc.gray(` Account ID: ${savedAccountId}`));
|
|
1243
|
+
}
|
|
1179
1244
|
if (validation.membershipType) {
|
|
1180
1245
|
console.log(pc.gray(` Membership: ${validation.membershipType}`));
|
|
1181
1246
|
}
|
|
1182
1247
|
console.log(pc.gray(" Your usage data will now be included in reports.\n"));
|
|
1183
1248
|
}
|
|
1184
1249
|
|
|
1185
|
-
async function cursorLogout(): Promise<void> {
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
if (!credentials) {
|
|
1250
|
+
async function cursorLogout(options: { name?: string; all?: boolean; purgeCache?: boolean } = {}): Promise<void> {
|
|
1251
|
+
if (!isCursorLoggedIn()) {
|
|
1189
1252
|
console.log(pc.yellow("\n Not logged in to Cursor.\n"));
|
|
1190
1253
|
return;
|
|
1191
1254
|
}
|
|
1192
1255
|
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1256
|
+
if (options.all) {
|
|
1257
|
+
const cleared = options.purgeCache ? clearCursorCredentialsAndCache({ purgeCache: true }) : clearCursorCredentialsAndCache();
|
|
1258
|
+
if (cleared) {
|
|
1259
|
+
console.log(pc.green("\n Logged out from all Cursor accounts.\n"));
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1198
1262
|
console.error(pc.red("\n Failed to clear Cursor credentials.\n"));
|
|
1199
1263
|
process.exit(1);
|
|
1200
1264
|
}
|
|
1201
|
-
}
|
|
1202
1265
|
|
|
1203
|
-
|
|
1204
|
-
|
|
1266
|
+
const target = options.name || listCursorAccounts().find((a) => a.isActive)?.id;
|
|
1267
|
+
if (!target) {
|
|
1268
|
+
console.log(pc.yellow("\n No saved Cursor accounts.\n"));
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1205
1271
|
|
|
1206
|
-
|
|
1272
|
+
const removed = removeCursorAccount(target, { purgeCache: options.purgeCache });
|
|
1273
|
+
if (!removed.removed) {
|
|
1274
|
+
console.error(pc.red(`\n Failed to log out: ${removed.error}\n`));
|
|
1275
|
+
process.exit(1);
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
if (options.purgeCache) {
|
|
1279
|
+
console.log(pc.green(`\n Logged out from Cursor account (cache purged): ${pc.bold(target)}\n`));
|
|
1280
|
+
} else {
|
|
1281
|
+
console.log(pc.green(`\n Logged out from Cursor account (history archived): ${pc.bold(target)}\n`));
|
|
1282
|
+
}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
async function cursorStatus(options: { name?: string } = {}): Promise<void> {
|
|
1286
|
+
if (!isCursorLoggedIn()) {
|
|
1207
1287
|
console.log(pc.yellow("\n Not logged in to Cursor."));
|
|
1208
1288
|
console.log(pc.gray(" Run 'tokscale cursor login' to authenticate.\n"));
|
|
1209
1289
|
return;
|
|
1210
1290
|
}
|
|
1211
1291
|
|
|
1292
|
+
const accounts = listCursorAccounts();
|
|
1293
|
+
const target = options.name
|
|
1294
|
+
? options.name
|
|
1295
|
+
: accounts.find((a) => a.isActive)?.id;
|
|
1296
|
+
|
|
1297
|
+
const credentials = target ? loadCursorCredentials(target) : null;
|
|
1298
|
+
if (!credentials) {
|
|
1299
|
+
console.log(pc.red("\n Error: Cursor account not found."));
|
|
1300
|
+
console.log(pc.gray(" Run 'tokscale cursor accounts' to list saved accounts.\n"));
|
|
1301
|
+
process.exit(1);
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1212
1304
|
console.log(pc.cyan("\n Cursor IDE - Status\n"));
|
|
1305
|
+
if (accounts.length > 0) {
|
|
1306
|
+
console.log(pc.white(" Accounts:"));
|
|
1307
|
+
for (const acct of accounts) {
|
|
1308
|
+
const name = acct.label ? `${acct.label} ${pc.gray(`(${acct.id})`)}` : acct.id;
|
|
1309
|
+
console.log(` ${acct.isActive ? pc.green("*") : pc.gray("-")} ${name}`);
|
|
1310
|
+
}
|
|
1311
|
+
console.log();
|
|
1312
|
+
}
|
|
1213
1313
|
console.log(pc.gray(" Checking session validity..."));
|
|
1214
1314
|
|
|
1215
1315
|
const validation = await validateCursorSession(credentials.sessionToken);
|
|
@@ -1223,7 +1323,7 @@ async function cursorStatus(): Promise<void> {
|
|
|
1223
1323
|
|
|
1224
1324
|
// Try to fetch usage to show summary
|
|
1225
1325
|
try {
|
|
1226
|
-
const usage = await readCursorUsage();
|
|
1326
|
+
const usage = await readCursorUsage(target);
|
|
1227
1327
|
const totalCost = usage.byModel.reduce((sum, m) => sum + m.cost, 0);
|
|
1228
1328
|
console.log(pc.gray(` Models used: ${usage.byModel.length}`));
|
|
1229
1329
|
console.log(pc.gray(` Total usage events: ${usage.rows.length}`));
|