@tokscale/cli 1.0.23 → 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/cursor.js CHANGED
@@ -11,6 +11,7 @@
11
11
  import * as fs from "node:fs";
12
12
  import * as path from "node:path";
13
13
  import * as os from "node:os";
14
+ import { createHash } from "node:crypto";
14
15
  import { parse as parseCsv } from "csv-parse/sync";
15
16
  // ============================================================================
16
17
  // Credential Management
@@ -50,31 +51,384 @@ function migrateCursorFromOldPath() {
50
51
  // Migration failed - continue with normal operation
51
52
  }
52
53
  }
53
- export function saveCursorCredentials(credentials) {
54
- ensureConfigDir();
55
- fs.writeFileSync(CURSOR_CREDENTIALS_FILE, JSON.stringify(credentials, null, 2), {
56
- encoding: "utf-8",
57
- mode: 0o600,
58
- });
54
+ export function ensureCursorMigration() {
55
+ // Best-effort: never throw
56
+ try {
57
+ migrateCursorFromOldPath();
58
+ }
59
+ catch { }
60
+ try {
61
+ migrateCursorCacheFromOldPath();
62
+ }
63
+ catch { }
64
+ try {
65
+ // Triggers legacy schema -> v1 store migration if needed
66
+ loadCursorCredentialsStoreInternal();
67
+ }
68
+ catch { }
59
69
  }
60
- export function loadCursorCredentials() {
70
+ function isStoreV1(data) {
71
+ if (!data || typeof data !== "object")
72
+ return false;
73
+ const obj = data;
74
+ return obj.version === 1 && typeof obj.activeAccountId === "string" && typeof obj.accounts === "object" && obj.accounts !== null;
75
+ }
76
+ function extractUserIdFromSessionToken(sessionToken) {
77
+ if (!sessionToken)
78
+ return null;
79
+ const token = sessionToken.trim();
80
+ if (token.includes("%3A%3A")) {
81
+ const userId = token.split("%3A%3A")[0]?.trim();
82
+ return userId ? userId : null;
83
+ }
84
+ if (token.includes("::")) {
85
+ const userId = token.split("::")[0]?.trim();
86
+ return userId ? userId : null;
87
+ }
88
+ return null;
89
+ }
90
+ function sanitizeAccountIdForFilename(accountId) {
91
+ return accountId
92
+ .trim()
93
+ .toLowerCase()
94
+ .replace(/[^a-z0-9._-]+/g, "-")
95
+ .replace(/^-+|-+$/g, "")
96
+ .slice(0, 80) || "account";
97
+ }
98
+ function isCursorUsageCsvFilename(fileName) {
99
+ if (fileName === "usage.csv")
100
+ return true;
101
+ if (!fileName.startsWith("usage."))
102
+ return false;
103
+ if (!fileName.endsWith(".csv"))
104
+ return false;
105
+ // Exclude legacy backups (were previously written as usage.backup-<ts>.csv)
106
+ if (fileName.startsWith("usage.backup"))
107
+ return false;
108
+ const stem = fileName.slice("usage.".length, -".csv".length);
109
+ if (!stem)
110
+ return false;
111
+ return /^[a-z0-9._-]+$/i.test(stem);
112
+ }
113
+ function deriveAccountId(sessionToken) {
114
+ const userId = extractUserIdFromSessionToken(sessionToken);
115
+ if (userId)
116
+ return userId;
117
+ const hash = createHash("sha256").update(sessionToken).digest("hex").slice(0, 12);
118
+ return `anon-${hash}`;
119
+ }
120
+ function atomicWriteFile(filePath, data, mode) {
121
+ const dir = path.dirname(filePath);
122
+ const base = path.basename(filePath);
123
+ const tmp = path.join(dir, `.${base}.tmp-${process.pid}`);
124
+ fs.writeFileSync(tmp, data, { encoding: "utf-8", mode });
125
+ try {
126
+ fs.renameSync(tmp, filePath);
127
+ }
128
+ catch {
129
+ // Best-effort for platforms where rename over an existing file can fail.
130
+ try {
131
+ if (fs.existsSync(filePath))
132
+ fs.rmSync(filePath);
133
+ }
134
+ catch {
135
+ // ignore
136
+ }
137
+ fs.renameSync(tmp, filePath);
138
+ }
139
+ }
140
+ function loadCursorCredentialsStoreInternal() {
61
141
  migrateCursorFromOldPath();
62
142
  try {
63
- if (!fs.existsSync(CURSOR_CREDENTIALS_FILE)) {
143
+ if (!fs.existsSync(CURSOR_CREDENTIALS_FILE))
64
144
  return null;
65
- }
66
145
  const data = fs.readFileSync(CURSOR_CREDENTIALS_FILE, "utf-8");
67
146
  const parsed = JSON.parse(data);
68
- if (!parsed.sessionToken) {
69
- return null;
147
+ if (isStoreV1(parsed)) {
148
+ const store = parsed;
149
+ if (!store.activeAccountId || !store.accounts[store.activeAccountId]) {
150
+ const firstId = Object.keys(store.accounts)[0];
151
+ if (!firstId)
152
+ return null;
153
+ store.activeAccountId = firstId;
154
+ ensureConfigDir();
155
+ atomicWriteFile(CURSOR_CREDENTIALS_FILE, JSON.stringify(store, null, 2), 0o600);
156
+ }
157
+ return store;
158
+ }
159
+ // Legacy single-account schema: { sessionToken, createdAt, ... }
160
+ if (parsed && typeof parsed === "object") {
161
+ const obj = parsed;
162
+ const sessionToken = typeof obj.sessionToken === "string" ? obj.sessionToken : "";
163
+ if (!sessionToken)
164
+ return null;
165
+ const accountId = deriveAccountId(sessionToken);
166
+ const migrated = {
167
+ version: 1,
168
+ activeAccountId: accountId,
169
+ accounts: {
170
+ [accountId]: {
171
+ sessionToken,
172
+ userId: typeof obj.userId === "string" ? obj.userId : extractUserIdFromSessionToken(sessionToken) || undefined,
173
+ createdAt: typeof obj.createdAt === "string" ? obj.createdAt : new Date().toISOString(),
174
+ expiresAt: typeof obj.expiresAt === "string" ? obj.expiresAt : undefined,
175
+ label: typeof obj.label === "string" ? obj.label : undefined,
176
+ },
177
+ },
178
+ };
179
+ ensureConfigDir();
180
+ atomicWriteFile(CURSOR_CREDENTIALS_FILE, JSON.stringify(migrated, null, 2), 0o600);
181
+ return migrated;
70
182
  }
71
- return parsed;
183
+ return null;
72
184
  }
73
185
  catch {
74
186
  return null;
75
187
  }
76
188
  }
189
+ function saveCursorCredentialsStoreInternal(store) {
190
+ ensureConfigDir();
191
+ atomicWriteFile(CURSOR_CREDENTIALS_FILE, JSON.stringify(store, null, 2), 0o600);
192
+ }
193
+ function resolveAccountId(store, nameOrId) {
194
+ const needle = nameOrId.trim();
195
+ if (!needle)
196
+ return null;
197
+ if (store.accounts[needle])
198
+ return needle;
199
+ const needleLower = needle.toLowerCase();
200
+ for (const [id, acct] of Object.entries(store.accounts)) {
201
+ if (acct.label && acct.label.toLowerCase() === needleLower)
202
+ return id;
203
+ }
204
+ return null;
205
+ }
206
+ export function listCursorAccounts() {
207
+ const store = loadCursorCredentialsStoreInternal();
208
+ if (!store)
209
+ return [];
210
+ const accounts = Object.entries(store.accounts).map(([id, acct]) => ({
211
+ id,
212
+ label: acct.label,
213
+ userId: acct.userId,
214
+ createdAt: acct.createdAt,
215
+ isActive: id === store.activeAccountId,
216
+ }));
217
+ accounts.sort((a, b) => {
218
+ if (a.isActive !== b.isActive)
219
+ return a.isActive ? -1 : 1;
220
+ const la = (a.label || a.id).toLowerCase();
221
+ const lb = (b.label || b.id).toLowerCase();
222
+ return la.localeCompare(lb);
223
+ });
224
+ return accounts;
225
+ }
226
+ export function setActiveCursorAccount(nameOrId) {
227
+ const store = loadCursorCredentialsStoreInternal();
228
+ if (!store)
229
+ return { ok: false, error: "Not authenticated" };
230
+ const resolved = resolveAccountId(store, nameOrId);
231
+ if (!resolved)
232
+ return { ok: false, error: `Account not found: ${nameOrId}` };
233
+ const prev = store.activeAccountId;
234
+ store.activeAccountId = resolved;
235
+ saveCursorCredentialsStoreInternal(store);
236
+ // Best-effort cache reconcile (avoid double-counting)
237
+ try {
238
+ migrateCursorCacheFromOldPath();
239
+ ensureCacheDir();
240
+ const archiveDir = path.join(CURSOR_CACHE_DIR, "archive");
241
+ const ensureArchiveDir = () => {
242
+ ensureCacheDir();
243
+ if (!fs.existsSync(archiveDir)) {
244
+ fs.mkdirSync(archiveDir, { recursive: true, mode: 0o700 });
245
+ }
246
+ };
247
+ const archiveFile = (filePath, label) => {
248
+ ensureArchiveDir();
249
+ const safeLabel = sanitizeAccountIdForFilename(label);
250
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
251
+ const dest = path.join(archiveDir, `${safeLabel}-${ts}.csv`);
252
+ fs.renameSync(filePath, dest);
253
+ };
254
+ // Move current active cache to previous account file (preserve any existing file by archiving).
255
+ if (prev && fs.existsSync(CURSOR_CACHE_FILE)) {
256
+ const prevFile = getCursorCacheFilePathForAccount(prev, false);
257
+ if (fs.existsSync(prevFile)) {
258
+ try {
259
+ archiveFile(prevFile, `usage.${prev}.previous`);
260
+ }
261
+ catch {
262
+ // ignore
263
+ }
264
+ }
265
+ try {
266
+ fs.renameSync(CURSOR_CACHE_FILE, prevFile);
267
+ }
268
+ catch {
269
+ // ignore
270
+ }
271
+ }
272
+ // Promote next account cache file into usage.csv.
273
+ const nextFile = getCursorCacheFilePathForAccount(resolved, false);
274
+ if (fs.existsSync(nextFile)) {
275
+ if (fs.existsSync(CURSOR_CACHE_FILE)) {
276
+ try {
277
+ archiveFile(CURSOR_CACHE_FILE, `usage.active.pre-switch`);
278
+ }
279
+ catch {
280
+ // ignore
281
+ }
282
+ }
283
+ try {
284
+ fs.renameSync(nextFile, CURSOR_CACHE_FILE);
285
+ }
286
+ catch {
287
+ // ignore
288
+ }
289
+ }
290
+ // If a per-account cache exists, it was promoted into usage.csv above.
291
+ }
292
+ catch {
293
+ // ignore cache reconcile errors
294
+ }
295
+ return { ok: true };
296
+ }
297
+ export function saveCursorCredentials(credentials, options) {
298
+ const sessionToken = credentials.sessionToken;
299
+ const accountId = deriveAccountId(sessionToken);
300
+ const store = loadCursorCredentialsStoreInternal() || {
301
+ version: 1,
302
+ activeAccountId: accountId,
303
+ accounts: {},
304
+ };
305
+ if (options?.label) {
306
+ const needle = options.label.trim().toLowerCase();
307
+ if (needle) {
308
+ for (const [id, acct] of Object.entries(store.accounts)) {
309
+ if (id === accountId)
310
+ continue;
311
+ if (acct.label && acct.label.trim().toLowerCase() === needle) {
312
+ throw new Error(`Cursor account label already exists: ${options.label}`);
313
+ }
314
+ }
315
+ }
316
+ }
317
+ const next = {
318
+ ...credentials,
319
+ userId: credentials.userId || extractUserIdFromSessionToken(sessionToken) || undefined,
320
+ label: options?.label ?? credentials.label,
321
+ };
322
+ store.accounts[accountId] = next;
323
+ if (options?.setActive !== false) {
324
+ store.activeAccountId = accountId;
325
+ }
326
+ saveCursorCredentialsStoreInternal(store);
327
+ return { accountId };
328
+ }
329
+ export function loadCursorCredentials(nameOrId) {
330
+ const store = loadCursorCredentialsStoreInternal();
331
+ if (!store)
332
+ return null;
333
+ if (nameOrId) {
334
+ const resolved = resolveAccountId(store, nameOrId);
335
+ return resolved ? store.accounts[resolved] : null;
336
+ }
337
+ return store.accounts[store.activeAccountId] || null;
338
+ }
339
+ export function loadCursorCredentialsStore() {
340
+ return loadCursorCredentialsStoreInternal();
341
+ }
342
+ // NOTE: implementation moved below to support cache archiving by default.
343
+ export function removeCursorAccount(nameOrId, options) {
344
+ const store = loadCursorCredentialsStoreInternal();
345
+ if (!store)
346
+ return { removed: false, error: "Not authenticated" };
347
+ const resolved = resolveAccountId(store, nameOrId);
348
+ if (!resolved)
349
+ return { removed: false, error: `Account not found: ${nameOrId}` };
350
+ const wasActive = resolved === store.activeAccountId;
351
+ // Cache behavior:
352
+ // - Default: keep history but remove from aggregation by archiving out of cursor-cache/.
353
+ // - purgeCache: delete cache files.
354
+ const CURSOR_CACHE_ARCHIVE_DIR = path.join(CURSOR_CACHE_DIR, "archive");
355
+ const ensureCacheArchiveDir = () => {
356
+ ensureCacheDir();
357
+ if (!fs.existsSync(CURSOR_CACHE_ARCHIVE_DIR)) {
358
+ fs.mkdirSync(CURSOR_CACHE_ARCHIVE_DIR, { recursive: true, mode: 0o700 });
359
+ }
360
+ };
361
+ const archiveFile = (filePath, label) => {
362
+ ensureCacheArchiveDir();
363
+ const safeLabel = sanitizeAccountIdForFilename(label);
364
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
365
+ const dest = path.join(CURSOR_CACHE_ARCHIVE_DIR, `${safeLabel}-${ts}.csv`);
366
+ fs.renameSync(filePath, dest);
367
+ };
368
+ try {
369
+ migrateCursorCacheFromOldPath();
370
+ if (fs.existsSync(CURSOR_CACHE_DIR)) {
371
+ const perAccount = getCursorCacheFilePathForAccount(resolved, false);
372
+ if (fs.existsSync(perAccount)) {
373
+ if (options?.purgeCache) {
374
+ fs.rmSync(perAccount);
375
+ }
376
+ else {
377
+ archiveFile(perAccount, `usage.${resolved}`);
378
+ }
379
+ }
380
+ if (wasActive && fs.existsSync(CURSOR_CACHE_FILE)) {
381
+ if (options?.purgeCache) {
382
+ fs.rmSync(CURSOR_CACHE_FILE);
383
+ }
384
+ else {
385
+ archiveFile(CURSOR_CACHE_FILE, `usage.active.${resolved}`);
386
+ }
387
+ }
388
+ }
389
+ }
390
+ catch {
391
+ // ignore
392
+ }
393
+ delete store.accounts[resolved];
394
+ const remaining = Object.keys(store.accounts);
395
+ if (remaining.length === 0) {
396
+ try {
397
+ fs.unlinkSync(CURSOR_CREDENTIALS_FILE);
398
+ }
399
+ catch { }
400
+ return { removed: true };
401
+ }
402
+ if (wasActive) {
403
+ store.activeAccountId = remaining[0];
404
+ }
405
+ saveCursorCredentialsStoreInternal(store);
406
+ if (wasActive) {
407
+ // Best-effort: reconcile usage.csv for the new active account.
408
+ try {
409
+ migrateCursorCacheFromOldPath();
410
+ ensureCacheDir();
411
+ const nextId = store.activeAccountId;
412
+ const nextFile = getCursorCacheFilePathForAccount(nextId, false);
413
+ if (fs.existsSync(nextFile)) {
414
+ if (fs.existsSync(CURSOR_CACHE_FILE)) {
415
+ try {
416
+ fs.rmSync(CURSOR_CACHE_FILE);
417
+ }
418
+ catch { }
419
+ }
420
+ fs.renameSync(nextFile, CURSOR_CACHE_FILE);
421
+ }
422
+ // If nextFile existed, it was promoted into usage.csv above.
423
+ }
424
+ catch {
425
+ // ignore
426
+ }
427
+ }
428
+ return { removed: true };
429
+ }
77
430
  export function clearCursorCredentials() {
431
+ // Backward compatible: clears ALL accounts
78
432
  try {
79
433
  if (fs.existsSync(CURSOR_CREDENTIALS_FILE)) {
80
434
  fs.unlinkSync(CURSOR_CREDENTIALS_FILE);
@@ -86,8 +440,51 @@ export function clearCursorCredentials() {
86
440
  return false;
87
441
  }
88
442
  }
443
+ export function clearCursorCredentialsAndCache(options) {
444
+ const cleared = clearCursorCredentials();
445
+ if (!cleared)
446
+ return false;
447
+ try {
448
+ migrateCursorCacheFromOldPath();
449
+ if (!fs.existsSync(CURSOR_CACHE_DIR))
450
+ return true;
451
+ const archiveDir = path.join(CURSOR_CACHE_DIR, "archive");
452
+ const ensureArchiveDir = () => {
453
+ ensureCacheDir();
454
+ if (!fs.existsSync(archiveDir)) {
455
+ fs.mkdirSync(archiveDir, { recursive: true, mode: 0o700 });
456
+ }
457
+ };
458
+ const archiveFile = (filePath, label) => {
459
+ ensureArchiveDir();
460
+ const safeLabel = sanitizeAccountIdForFilename(label);
461
+ const ts = new Date().toISOString().replace(/[:.]/g, "-");
462
+ const dest = path.join(archiveDir, `${safeLabel}-${ts}.csv`);
463
+ fs.renameSync(filePath, dest);
464
+ };
465
+ for (const f of fs.readdirSync(CURSOR_CACHE_DIR)) {
466
+ if (!f.startsWith("usage") || !f.endsWith(".csv"))
467
+ continue;
468
+ const filePath = path.join(CURSOR_CACHE_DIR, f);
469
+ try {
470
+ if (options?.purgeCache) {
471
+ fs.rmSync(filePath);
472
+ }
473
+ else {
474
+ archiveFile(filePath, `usage.all.${f}`);
475
+ }
476
+ }
477
+ catch { }
478
+ }
479
+ }
480
+ catch {
481
+ // ignore
482
+ }
483
+ return true;
484
+ }
89
485
  export function isCursorLoggedIn() {
90
- return loadCursorCredentials() !== null;
486
+ const store = loadCursorCredentialsStoreInternal();
487
+ return !!store && Object.keys(store.accounts).length > 0;
91
488
  }
92
489
  // ============================================================================
93
490
  // API Client
@@ -302,8 +699,8 @@ export function cursorRowsToMessages(rows) {
302
699
  * Fetch and parse Cursor usage data
303
700
  * Requires valid credentials to be stored
304
701
  */
305
- export async function readCursorUsage() {
306
- const credentials = loadCursorCredentials();
702
+ export async function readCursorUsage(nameOrId) {
703
+ const credentials = loadCursorCredentials(nameOrId);
307
704
  if (!credentials) {
308
705
  throw new Error("Cursor not authenticated. Run 'tokscale cursor login' first.");
309
706
  }
@@ -325,6 +722,12 @@ export function getCursorCredentialsPath() {
325
722
  const OLD_CURSOR_CACHE_DIR = path.join(os.homedir(), ".tokscale", "cursor-cache");
326
723
  const CURSOR_CACHE_DIR = path.join(CONFIG_DIR, "cursor-cache");
327
724
  const CURSOR_CACHE_FILE = path.join(CURSOR_CACHE_DIR, "usage.csv");
725
+ function getCursorCacheFilePathForAccount(accountId, isActive) {
726
+ if (isActive)
727
+ return CURSOR_CACHE_FILE;
728
+ const safe = sanitizeAccountIdForFilename(accountId);
729
+ return path.join(CURSOR_CACHE_DIR, `usage.${safe}.csv`);
730
+ }
328
731
  function ensureCacheDir() {
329
732
  if (!fs.existsSync(CURSOR_CACHE_DIR)) {
330
733
  fs.mkdirSync(CURSOR_CACHE_DIR, { recursive: true, mode: 0o700 });
@@ -358,17 +761,49 @@ function migrateCursorCacheFromOldPath() {
358
761
  */
359
762
  export async function syncCursorCache() {
360
763
  migrateCursorCacheFromOldPath();
361
- const credentials = loadCursorCredentials();
362
- if (!credentials) {
764
+ const store = loadCursorCredentialsStoreInternal();
765
+ if (!store)
766
+ return { synced: false, rows: 0, error: "Not authenticated" };
767
+ const accounts = Object.entries(store.accounts);
768
+ if (accounts.length === 0)
363
769
  return { synced: false, rows: 0, error: "Not authenticated" };
364
- }
365
770
  try {
366
- const csvText = await fetchCursorUsageCsv(credentials.sessionToken);
367
771
  ensureCacheDir();
368
- fs.writeFileSync(CURSOR_CACHE_FILE, csvText, { encoding: "utf-8", mode: 0o600 });
369
- // Count rows for feedback
370
- const rows = parseCursorCsv(csvText);
371
- return { synced: true, rows: rows.length };
772
+ // Ensure we don't double-count active account (usage.csv + usage.<active>.csv)
773
+ const activeId = store.activeAccountId;
774
+ if (activeId) {
775
+ const dup = getCursorCacheFilePathForAccount(activeId, false);
776
+ if (fs.existsSync(dup)) {
777
+ try {
778
+ fs.rmSync(dup);
779
+ }
780
+ catch { }
781
+ }
782
+ }
783
+ let totalRows = 0;
784
+ let successCount = 0;
785
+ const errors = [];
786
+ for (const [accountId, credentials] of accounts) {
787
+ const isActive = accountId === store.activeAccountId;
788
+ try {
789
+ const csvText = await fetchCursorUsageCsv(credentials.sessionToken);
790
+ const filePath = getCursorCacheFilePathForAccount(accountId, isActive);
791
+ atomicWriteFile(filePath, csvText, 0o600);
792
+ totalRows += parseCursorCsv(csvText).length;
793
+ successCount += 1;
794
+ }
795
+ catch (e) {
796
+ errors.push(`${accountId}: ${e.message}`);
797
+ }
798
+ }
799
+ if (successCount === 0) {
800
+ return { synced: false, rows: 0, error: errors[0] || "Cursor sync failed" };
801
+ }
802
+ return {
803
+ synced: true,
804
+ rows: totalRows,
805
+ error: errors.length > 0 ? `Some accounts failed to sync (${errors.length}/${accounts.length})` : undefined,
806
+ };
372
807
  }
373
808
  catch (error) {
374
809
  return { synced: false, rows: 0, error: error.message };
@@ -378,12 +813,15 @@ export async function syncCursorCache() {
378
813
  * Get the cache file path
379
814
  */
380
815
  export function getCursorCachePath() {
816
+ // Ensure legacy cache is migrated before reporting paths
817
+ migrateCursorCacheFromOldPath();
381
818
  return CURSOR_CACHE_FILE;
382
819
  }
383
820
  /**
384
821
  * Check if cache exists and when it was last updated
385
822
  */
386
823
  export function getCursorCacheStatus() {
824
+ migrateCursorCacheFromOldPath();
387
825
  const exists = fs.existsSync(CURSOR_CACHE_FILE);
388
826
  let lastModified;
389
827
  if (exists) {
@@ -397,36 +835,72 @@ export function getCursorCacheStatus() {
397
835
  }
398
836
  return { exists, lastModified, path: CURSOR_CACHE_FILE };
399
837
  }
838
+ export function hasCursorUsageCache() {
839
+ migrateCursorCacheFromOldPath();
840
+ try {
841
+ if (!fs.existsSync(CURSOR_CACHE_DIR))
842
+ return false;
843
+ const files = fs.readdirSync(CURSOR_CACHE_DIR);
844
+ return files.some((f) => isCursorUsageCsvFilename(f));
845
+ }
846
+ catch {
847
+ return false;
848
+ }
849
+ }
400
850
  export function readCursorMessagesFromCache() {
401
- if (!fs.existsSync(CURSOR_CACHE_FILE)) {
851
+ migrateCursorCacheFromOldPath();
852
+ if (!fs.existsSync(CURSOR_CACHE_DIR)) {
402
853
  return [];
403
854
  }
855
+ let files;
404
856
  try {
405
- const csvText = fs.readFileSync(CURSOR_CACHE_FILE, "utf-8");
406
- const rows = parseCursorCsv(csvText);
407
- return rows.map((row) => {
408
- const cacheWrite = Math.max(0, row.inputWithCacheWrite - row.inputWithoutCacheWrite);
409
- const input = row.inputWithoutCacheWrite;
410
- return {
411
- source: "cursor",
412
- modelId: row.model,
413
- providerId: inferProvider(row.model),
414
- sessionId: `cursor-${row.date}-${row.model}`,
415
- timestamp: row.timestamp,
416
- date: row.date,
417
- tokens: {
418
- input,
419
- output: row.outputTokens,
420
- cacheRead: row.cacheRead,
421
- cacheWrite,
422
- reasoning: 0,
423
- },
424
- cost: row.costToYou || row.apiCost,
425
- };
426
- });
857
+ files = fs
858
+ .readdirSync(CURSOR_CACHE_DIR)
859
+ .filter((f) => isCursorUsageCsvFilename(f));
427
860
  }
428
861
  catch {
429
862
  return [];
430
863
  }
864
+ const store = loadCursorCredentialsStoreInternal();
865
+ const activeId = store?.activeAccountId;
866
+ const all = [];
867
+ for (const file of files) {
868
+ const filePath = path.join(CURSOR_CACHE_DIR, file);
869
+ let accountId = "unknown";
870
+ if (file === "usage.csv") {
871
+ accountId = activeId || "active";
872
+ }
873
+ else if (file.startsWith("usage.") && file.endsWith(".csv")) {
874
+ accountId = file.slice("usage.".length, -".csv".length);
875
+ }
876
+ try {
877
+ const csvText = fs.readFileSync(filePath, "utf-8");
878
+ const rows = parseCursorCsv(csvText);
879
+ for (const row of rows) {
880
+ const cacheWrite = Math.max(0, row.inputWithCacheWrite - row.inputWithoutCacheWrite);
881
+ const input = row.inputWithoutCacheWrite;
882
+ all.push({
883
+ source: "cursor",
884
+ modelId: row.model,
885
+ providerId: inferProvider(row.model),
886
+ sessionId: `cursor-${accountId}-${row.date}-${row.model}`,
887
+ timestamp: row.timestamp,
888
+ date: row.date,
889
+ tokens: {
890
+ input,
891
+ output: row.outputTokens,
892
+ cacheRead: row.cacheRead,
893
+ cacheWrite,
894
+ reasoning: 0,
895
+ },
896
+ cost: row.costToYou || row.apiCost,
897
+ });
898
+ }
899
+ }
900
+ catch {
901
+ // ignore file
902
+ }
903
+ }
904
+ return all;
431
905
  }
432
906
  //# sourceMappingURL=cursor.js.map