vibestats 1.0.21 → 1.0.22
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 +171 -89
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { defineCommand, runMain } from "citty";
|
|
5
5
|
|
|
6
6
|
// src/usage/loader.ts
|
|
7
|
-
import {
|
|
7
|
+
import { promises as fs } from "fs";
|
|
8
8
|
import { homedir } from "os";
|
|
9
9
|
import { join } from "path";
|
|
10
10
|
|
|
@@ -232,51 +232,66 @@ function calculateCodexCost(modelName, inputTokens, outputTokens, cachedInputTok
|
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
// src/usage/loader.ts
|
|
235
|
+
var MAX_RECURSION_DEPTH = 10;
|
|
235
236
|
function getClaudeDir() {
|
|
236
237
|
return process.env.CLAUDE_HOME || join(homedir(), ".claude");
|
|
237
238
|
}
|
|
238
239
|
function getCodexDir() {
|
|
239
240
|
return join(homedir(), ".codex");
|
|
240
241
|
}
|
|
241
|
-
function
|
|
242
|
-
if (!existsSync(dir)) return result;
|
|
243
|
-
if (depth > 10) return result;
|
|
244
|
-
let realPath;
|
|
242
|
+
async function pathExists(path) {
|
|
245
243
|
try {
|
|
246
|
-
|
|
244
|
+
await fs.access(path);
|
|
245
|
+
return true;
|
|
247
246
|
} catch {
|
|
248
|
-
|
|
247
|
+
return false;
|
|
249
248
|
}
|
|
249
|
+
}
|
|
250
|
+
async function safeRealpath(path) {
|
|
251
|
+
try {
|
|
252
|
+
return await fs.realpath(path);
|
|
253
|
+
} catch {
|
|
254
|
+
return path;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
async function findJsonlFiles(dir, visited = /* @__PURE__ */ new Set(), depth = 0, result = []) {
|
|
258
|
+
if (!await pathExists(dir)) return result;
|
|
259
|
+
if (depth > MAX_RECURSION_DEPTH) return result;
|
|
260
|
+
const realPath = await safeRealpath(dir);
|
|
250
261
|
if (visited.has(realPath)) return result;
|
|
251
262
|
visited.add(realPath);
|
|
263
|
+
let entries;
|
|
252
264
|
try {
|
|
253
|
-
|
|
254
|
-
for (const entry of entries) {
|
|
255
|
-
const fullPath = join(dir, entry);
|
|
256
|
-
try {
|
|
257
|
-
const stat = statSync(fullPath);
|
|
258
|
-
if (stat.isDirectory()) {
|
|
259
|
-
findJsonlFiles(fullPath, visited, depth + 1, result);
|
|
260
|
-
} else if (entry.endsWith(".jsonl")) {
|
|
261
|
-
result.push(fullPath);
|
|
262
|
-
}
|
|
263
|
-
} catch {
|
|
264
|
-
}
|
|
265
|
-
}
|
|
265
|
+
entries = await fs.readdir(dir);
|
|
266
266
|
} catch {
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
for (const entry of entries) {
|
|
270
|
+
const fullPath = join(dir, entry);
|
|
271
|
+
let stat;
|
|
272
|
+
try {
|
|
273
|
+
stat = await fs.stat(fullPath);
|
|
274
|
+
} catch {
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (stat.isDirectory()) {
|
|
278
|
+
await findJsonlFiles(fullPath, visited, depth + 1, result);
|
|
279
|
+
} else if (entry.endsWith(".jsonl")) {
|
|
280
|
+
result.push(fullPath);
|
|
281
|
+
}
|
|
267
282
|
}
|
|
268
283
|
return result;
|
|
269
284
|
}
|
|
270
|
-
function parseClaudeJsonl() {
|
|
285
|
+
async function parseClaudeJsonl() {
|
|
271
286
|
const entries = [];
|
|
272
287
|
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
273
288
|
const claudeDir = getClaudeDir();
|
|
274
289
|
const projectsDir = join(claudeDir, "projects");
|
|
275
|
-
if (!
|
|
276
|
-
const jsonlFiles = findJsonlFiles(projectsDir);
|
|
290
|
+
if (!await pathExists(projectsDir)) return entries;
|
|
291
|
+
const jsonlFiles = await findJsonlFiles(projectsDir);
|
|
277
292
|
for (const filePath of jsonlFiles) {
|
|
278
293
|
try {
|
|
279
|
-
const content =
|
|
294
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
280
295
|
const lines = content.split("\n");
|
|
281
296
|
for (const line of lines) {
|
|
282
297
|
if (!line.trim()) continue;
|
|
@@ -317,19 +332,20 @@ function parseClaudeJsonl() {
|
|
|
317
332
|
}
|
|
318
333
|
return entries;
|
|
319
334
|
}
|
|
320
|
-
function parseCodexJsonl() {
|
|
335
|
+
async function parseCodexJsonl() {
|
|
321
336
|
const entries = [];
|
|
322
337
|
const codexDir = getCodexDir();
|
|
323
338
|
const sessionsDir = join(codexDir, "sessions");
|
|
324
339
|
const archivedDir = join(codexDir, "archived_sessions");
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
];
|
|
340
|
+
const [sessionFiles, archivedFiles] = await Promise.all([
|
|
341
|
+
findJsonlFiles(sessionsDir),
|
|
342
|
+
findJsonlFiles(archivedDir)
|
|
343
|
+
]);
|
|
344
|
+
const jsonlFiles = [...sessionFiles, ...archivedFiles];
|
|
329
345
|
if (jsonlFiles.length === 0) return entries;
|
|
330
346
|
for (const filePath of jsonlFiles) {
|
|
331
347
|
try {
|
|
332
|
-
const content =
|
|
348
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
333
349
|
const lines = content.split("\n");
|
|
334
350
|
let currentModel = "gpt-5";
|
|
335
351
|
for (const line of lines) {
|
|
@@ -356,7 +372,6 @@ function parseCodexJsonl() {
|
|
|
356
372
|
date,
|
|
357
373
|
model: getCodexModelDisplayName(currentModel),
|
|
358
374
|
inputTokens: inputTokens - cachedInputTokens,
|
|
359
|
-
// OpenAI input_tokens includes cached, so subtract
|
|
360
375
|
outputTokens,
|
|
361
376
|
cacheWriteTokens: 0,
|
|
362
377
|
cacheReadTokens: cachedInputTokens,
|
|
@@ -381,7 +396,6 @@ function filterByDateRange(entries, since, until) {
|
|
|
381
396
|
}
|
|
382
397
|
function sortModelsByTier(models) {
|
|
383
398
|
const tierPriority = {
|
|
384
|
-
// Claude models (higher = first)
|
|
385
399
|
"Opus 4.5": 100,
|
|
386
400
|
"Opus 4.1": 99,
|
|
387
401
|
"Opus": 98,
|
|
@@ -391,7 +405,6 @@ function sortModelsByTier(models) {
|
|
|
391
405
|
"Haiku 4.5": 80,
|
|
392
406
|
"Haiku 3.5": 79,
|
|
393
407
|
"Haiku": 78,
|
|
394
|
-
// OpenAI/Codex models
|
|
395
408
|
"GPT-5.2": 70,
|
|
396
409
|
"GPT-5.1 Max": 69,
|
|
397
410
|
"GPT-5.1": 68,
|
|
@@ -526,14 +539,14 @@ function computeModelBreakdown(entries) {
|
|
|
526
539
|
percentage: totalTokens > 0 ? Math.round(data.tokens / totalTokens * 100) : 0
|
|
527
540
|
})).sort((a, b) => b.tokens - a.tokens);
|
|
528
541
|
}
|
|
529
|
-
function loadUsageStats(options) {
|
|
542
|
+
async function loadUsageStats(options) {
|
|
530
543
|
const { aggregation, since, until, codexOnly, combined } = options;
|
|
531
544
|
let entries = [];
|
|
532
545
|
if (!codexOnly) {
|
|
533
|
-
entries = entries.concat(parseClaudeJsonl());
|
|
546
|
+
entries = entries.concat(await parseClaudeJsonl());
|
|
534
547
|
}
|
|
535
548
|
if (codexOnly || combined) {
|
|
536
|
-
entries = entries.concat(parseCodexJsonl());
|
|
549
|
+
entries = entries.concat(await parseCodexJsonl());
|
|
537
550
|
}
|
|
538
551
|
if (entries.length === 0) {
|
|
539
552
|
return null;
|
|
@@ -733,27 +746,39 @@ function displayTotalOnly(stats, options = {}) {
|
|
|
733
746
|
}
|
|
734
747
|
|
|
735
748
|
// src/claude-jsonl-loader.ts
|
|
736
|
-
import {
|
|
749
|
+
import { promises as fs2 } from "fs";
|
|
737
750
|
import { homedir as homedir2 } from "os";
|
|
738
751
|
import { join as join2 } from "path";
|
|
739
752
|
function getClaudeDir2() {
|
|
740
753
|
return process.env.CLAUDE_HOME || join2(homedir2(), ".claude");
|
|
741
754
|
}
|
|
742
|
-
function
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
755
|
+
async function pathExists2(path) {
|
|
756
|
+
try {
|
|
757
|
+
await fs2.access(path);
|
|
758
|
+
return true;
|
|
759
|
+
} catch {
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
async function claudeJsonlDataExists() {
|
|
764
|
+
const projectsDir = join2(getClaudeDir2(), "projects");
|
|
765
|
+
return pathExists2(projectsDir);
|
|
746
766
|
}
|
|
747
|
-
function findJsonlFiles2(dir) {
|
|
767
|
+
async function findJsonlFiles2(dir) {
|
|
748
768
|
const files = [];
|
|
749
|
-
if (!
|
|
750
|
-
|
|
769
|
+
if (!await pathExists2(dir)) return files;
|
|
770
|
+
let entries;
|
|
771
|
+
try {
|
|
772
|
+
entries = await fs2.readdir(dir);
|
|
773
|
+
} catch {
|
|
774
|
+
return files;
|
|
775
|
+
}
|
|
751
776
|
for (const entry of entries) {
|
|
752
777
|
const fullPath = join2(dir, entry);
|
|
753
778
|
try {
|
|
754
|
-
const stat =
|
|
779
|
+
const stat = await fs2.stat(fullPath);
|
|
755
780
|
if (stat.isDirectory()) {
|
|
756
|
-
files.push(...findJsonlFiles2(fullPath));
|
|
781
|
+
files.push(...await findJsonlFiles2(fullPath));
|
|
757
782
|
} else if (entry.endsWith(".jsonl")) {
|
|
758
783
|
files.push(fullPath);
|
|
759
784
|
}
|
|
@@ -762,13 +787,13 @@ function findJsonlFiles2(dir) {
|
|
|
762
787
|
}
|
|
763
788
|
return files;
|
|
764
789
|
}
|
|
765
|
-
function loadClaudeStatsFromJsonl() {
|
|
790
|
+
async function loadClaudeStatsFromJsonl() {
|
|
766
791
|
const claudeDir = getClaudeDir2();
|
|
767
792
|
const projectsDir = join2(claudeDir, "projects");
|
|
768
|
-
if (!claudeJsonlDataExists()) {
|
|
793
|
+
if (!await claudeJsonlDataExists()) {
|
|
769
794
|
return null;
|
|
770
795
|
}
|
|
771
|
-
const jsonlFiles = findJsonlFiles2(projectsDir);
|
|
796
|
+
const jsonlFiles = await findJsonlFiles2(projectsDir);
|
|
772
797
|
if (jsonlFiles.length === 0) {
|
|
773
798
|
return null;
|
|
774
799
|
}
|
|
@@ -780,7 +805,7 @@ function loadClaudeStatsFromJsonl() {
|
|
|
780
805
|
const messageIds = /* @__PURE__ */ new Set();
|
|
781
806
|
for (const filePath of jsonlFiles) {
|
|
782
807
|
try {
|
|
783
|
-
const content =
|
|
808
|
+
const content = await fs2.readFile(filePath, "utf-8");
|
|
784
809
|
const lines = content.split("\n");
|
|
785
810
|
for (const line of lines) {
|
|
786
811
|
if (!line.trim()) continue;
|
|
@@ -839,7 +864,6 @@ function loadClaudeStatsFromJsonl() {
|
|
|
839
864
|
cacheCreationInputTokens: usage.cacheCreationInputTokens,
|
|
840
865
|
webSearchRequests: 0,
|
|
841
866
|
costUSD: 0,
|
|
842
|
-
// Will be calculated by metrics.ts
|
|
843
867
|
contextWindow: 2e5
|
|
844
868
|
};
|
|
845
869
|
}
|
|
@@ -847,16 +871,15 @@ function loadClaudeStatsFromJsonl() {
|
|
|
847
871
|
date,
|
|
848
872
|
messageCount: data.messageCount,
|
|
849
873
|
sessionCount: 1,
|
|
850
|
-
// Each day counts as at least 1 session for activity
|
|
851
874
|
toolCallCount: data.toolCallCount
|
|
852
875
|
})).sort((a, b) => a.date.localeCompare(b.date));
|
|
853
876
|
const sessionsByDate = /* @__PURE__ */ new Map();
|
|
877
|
+
void sessionsByDate;
|
|
854
878
|
return {
|
|
855
879
|
version: 1,
|
|
856
880
|
lastComputedDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
857
881
|
dailyActivity,
|
|
858
882
|
dailyModelTokens: [],
|
|
859
|
-
// Not needed for wrapped stats
|
|
860
883
|
modelUsage: statsCacheModelUsage,
|
|
861
884
|
totalSessions: jsonlFiles.length,
|
|
862
885
|
totalMessages,
|
|
@@ -872,37 +895,55 @@ function loadClaudeStatsFromJsonl() {
|
|
|
872
895
|
}
|
|
873
896
|
|
|
874
897
|
// src/codex-loader.ts
|
|
875
|
-
import {
|
|
898
|
+
import { promises as fs3 } from "fs";
|
|
876
899
|
import { homedir as homedir3 } from "os";
|
|
877
900
|
import { join as join3 } from "path";
|
|
878
901
|
function getCodexDir2() {
|
|
879
902
|
return process.env.CODEX_HOME || join3(homedir3(), ".codex");
|
|
880
903
|
}
|
|
881
|
-
function
|
|
904
|
+
async function pathExists3(path) {
|
|
905
|
+
try {
|
|
906
|
+
await fs3.access(path);
|
|
907
|
+
return true;
|
|
908
|
+
} catch {
|
|
909
|
+
return false;
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
async function codexDataExists() {
|
|
882
913
|
const codexDir = getCodexDir2();
|
|
883
|
-
if (!
|
|
914
|
+
if (!await pathExists3(codexDir)) return false;
|
|
884
915
|
const sessionsDir = join3(codexDir, "sessions");
|
|
885
916
|
const archivedDir = join3(codexDir, "archived_sessions");
|
|
886
|
-
return
|
|
917
|
+
return await pathExists3(sessionsDir) || await pathExists3(archivedDir);
|
|
887
918
|
}
|
|
888
|
-
function findJsonlFiles3(dir) {
|
|
919
|
+
async function findJsonlFiles3(dir) {
|
|
889
920
|
const files = [];
|
|
890
|
-
if (!
|
|
891
|
-
|
|
921
|
+
if (!await pathExists3(dir)) return files;
|
|
922
|
+
let entries;
|
|
923
|
+
try {
|
|
924
|
+
entries = await fs3.readdir(dir);
|
|
925
|
+
} catch {
|
|
926
|
+
return files;
|
|
927
|
+
}
|
|
892
928
|
for (const entry of entries) {
|
|
893
929
|
const fullPath = join3(dir, entry);
|
|
894
|
-
|
|
930
|
+
let stat;
|
|
931
|
+
try {
|
|
932
|
+
stat = await fs3.stat(fullPath);
|
|
933
|
+
} catch {
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
895
936
|
if (stat.isDirectory()) {
|
|
896
|
-
files.push(...findJsonlFiles3(fullPath));
|
|
937
|
+
files.push(...await findJsonlFiles3(fullPath));
|
|
897
938
|
} else if (entry.endsWith(".jsonl")) {
|
|
898
939
|
files.push(fullPath);
|
|
899
940
|
}
|
|
900
941
|
}
|
|
901
942
|
return files;
|
|
902
943
|
}
|
|
903
|
-
function parseSessionFile(filePath) {
|
|
944
|
+
async function parseSessionFile(filePath) {
|
|
904
945
|
try {
|
|
905
|
-
const content =
|
|
946
|
+
const content = await fs3.readFile(filePath, "utf-8");
|
|
906
947
|
const lines = content.trim().split("\n");
|
|
907
948
|
let sessionMeta = null;
|
|
908
949
|
let currentModel = "gpt-5";
|
|
@@ -969,29 +1010,29 @@ function parseSessionFile(filePath) {
|
|
|
969
1010
|
model: primaryModel,
|
|
970
1011
|
tokenUsage: summedUsage,
|
|
971
1012
|
perModelUsage
|
|
972
|
-
// New: per-model token breakdown
|
|
973
1013
|
};
|
|
974
1014
|
} catch {
|
|
975
1015
|
return null;
|
|
976
1016
|
}
|
|
977
1017
|
}
|
|
978
|
-
function loadCodexStats() {
|
|
1018
|
+
async function loadCodexStats() {
|
|
979
1019
|
const codexDir = getCodexDir2();
|
|
980
|
-
if (!codexDataExists()) {
|
|
1020
|
+
if (!await codexDataExists()) {
|
|
981
1021
|
return null;
|
|
982
1022
|
}
|
|
983
1023
|
const sessionsDir = join3(codexDir, "sessions");
|
|
984
1024
|
const archivedDir = join3(codexDir, "archived_sessions");
|
|
985
|
-
const
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
];
|
|
1025
|
+
const [sessionFiles, archivedFiles] = await Promise.all([
|
|
1026
|
+
findJsonlFiles3(sessionsDir),
|
|
1027
|
+
findJsonlFiles3(archivedDir)
|
|
1028
|
+
]);
|
|
1029
|
+
const jsonlFiles = [...sessionFiles, ...archivedFiles];
|
|
989
1030
|
if (jsonlFiles.length === 0) {
|
|
990
1031
|
return null;
|
|
991
1032
|
}
|
|
992
1033
|
const sessions = [];
|
|
993
1034
|
for (const file of jsonlFiles) {
|
|
994
|
-
const session = parseSessionFile(file);
|
|
1035
|
+
const session = await parseSessionFile(file);
|
|
995
1036
|
if (session) {
|
|
996
1037
|
sessions.push(session);
|
|
997
1038
|
}
|
|
@@ -1056,18 +1097,18 @@ function loadCodexStats() {
|
|
|
1056
1097
|
}
|
|
1057
1098
|
|
|
1058
1099
|
// src/shared/data-loader.ts
|
|
1059
|
-
function loadData(options) {
|
|
1100
|
+
async function loadData(options) {
|
|
1060
1101
|
const { codexOnly, combined } = options;
|
|
1061
1102
|
let claude = null;
|
|
1062
1103
|
let codex = null;
|
|
1063
1104
|
if (!codexOnly) {
|
|
1064
|
-
if (claudeJsonlDataExists()) {
|
|
1065
|
-
claude = loadClaudeStatsFromJsonl();
|
|
1105
|
+
if (await claudeJsonlDataExists()) {
|
|
1106
|
+
claude = await loadClaudeStatsFromJsonl();
|
|
1066
1107
|
}
|
|
1067
1108
|
}
|
|
1068
1109
|
if (codexOnly || combined) {
|
|
1069
|
-
if (codexDataExists()) {
|
|
1070
|
-
codex = loadCodexStats();
|
|
1110
|
+
if (await codexDataExists()) {
|
|
1111
|
+
codex = await loadCodexStats();
|
|
1071
1112
|
}
|
|
1072
1113
|
}
|
|
1073
1114
|
let source = "claude";
|
|
@@ -1705,7 +1746,7 @@ async function createShortlink(params, baseUrl) {
|
|
|
1705
1746
|
}
|
|
1706
1747
|
|
|
1707
1748
|
// src/config.ts
|
|
1708
|
-
import { readFileSync
|
|
1749
|
+
import { readFileSync, existsSync, writeFileSync } from "fs";
|
|
1709
1750
|
import { homedir as homedir4 } from "os";
|
|
1710
1751
|
import { join as join4 } from "path";
|
|
1711
1752
|
var CONFIG_PATH = join4(homedir4(), ".vibestats.json");
|
|
@@ -1718,11 +1759,11 @@ var DEFAULT_CONFIG = {
|
|
|
1718
1759
|
hideCost: false
|
|
1719
1760
|
};
|
|
1720
1761
|
function loadConfig() {
|
|
1721
|
-
if (!
|
|
1762
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
1722
1763
|
return DEFAULT_CONFIG;
|
|
1723
1764
|
}
|
|
1724
1765
|
try {
|
|
1725
|
-
const content =
|
|
1766
|
+
const content = readFileSync(CONFIG_PATH, "utf-8");
|
|
1726
1767
|
const userConfig = JSON.parse(content);
|
|
1727
1768
|
return mergeConfig(DEFAULT_CONFIG, userConfig);
|
|
1728
1769
|
} catch {
|
|
@@ -1741,7 +1782,7 @@ function mergeConfig(defaults, user) {
|
|
|
1741
1782
|
};
|
|
1742
1783
|
}
|
|
1743
1784
|
function initConfig() {
|
|
1744
|
-
if (
|
|
1785
|
+
if (existsSync(CONFIG_PATH)) {
|
|
1745
1786
|
console.log(`Config file already exists at ${CONFIG_PATH}`);
|
|
1746
1787
|
return;
|
|
1747
1788
|
}
|
|
@@ -1772,6 +1813,41 @@ function resolveOptions(cliArgs, config) {
|
|
|
1772
1813
|
}
|
|
1773
1814
|
|
|
1774
1815
|
// src/index.ts
|
|
1816
|
+
function createSpinner(label = "Loading vibestats...") {
|
|
1817
|
+
const spinnerFrames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
1818
|
+
const orange = "\x1B[38;5;208m";
|
|
1819
|
+
const bold = "\x1B[1m";
|
|
1820
|
+
const reset = "\x1B[0m";
|
|
1821
|
+
let spinnerIdx = 0;
|
|
1822
|
+
let interval = null;
|
|
1823
|
+
const render = () => {
|
|
1824
|
+
const frame = spinnerFrames[spinnerIdx];
|
|
1825
|
+
process.stdout.write(`\r${orange}${frame}${reset} ${bold}${label}${reset} `);
|
|
1826
|
+
spinnerIdx = (spinnerIdx + 1) % spinnerFrames.length;
|
|
1827
|
+
};
|
|
1828
|
+
const start = () => {
|
|
1829
|
+
if (interval) return;
|
|
1830
|
+
process.stdout.write("\x1B[?25l");
|
|
1831
|
+
render();
|
|
1832
|
+
interval = setInterval(render, 80);
|
|
1833
|
+
};
|
|
1834
|
+
const stop = () => {
|
|
1835
|
+
if (!interval) return;
|
|
1836
|
+
clearInterval(interval);
|
|
1837
|
+
interval = null;
|
|
1838
|
+
process.stdout.write("\r\x1B[2K");
|
|
1839
|
+
process.stdout.write("\x1B[?25h");
|
|
1840
|
+
};
|
|
1841
|
+
const whilePromise = async (promise) => {
|
|
1842
|
+
start();
|
|
1843
|
+
try {
|
|
1844
|
+
return await promise;
|
|
1845
|
+
} finally {
|
|
1846
|
+
stop();
|
|
1847
|
+
}
|
|
1848
|
+
};
|
|
1849
|
+
return { whilePromise, stop };
|
|
1850
|
+
}
|
|
1775
1851
|
var main = defineCommand({
|
|
1776
1852
|
meta: {
|
|
1777
1853
|
name: "vibestats",
|
|
@@ -1899,13 +1975,16 @@ async function runUsage(args, config) {
|
|
|
1899
1975
|
if (args.monthly) aggregation = "monthly";
|
|
1900
1976
|
else if (args.model) aggregation = "model";
|
|
1901
1977
|
else if (args.total) aggregation = "total";
|
|
1902
|
-
const
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1978
|
+
const spinner = createSpinner("Loading vibestats...");
|
|
1979
|
+
const stats = await spinner.whilePromise(
|
|
1980
|
+
loadUsageStats({
|
|
1981
|
+
aggregation,
|
|
1982
|
+
since: args.since,
|
|
1983
|
+
until: args.until,
|
|
1984
|
+
codexOnly: args.codex,
|
|
1985
|
+
combined: args.combined
|
|
1986
|
+
})
|
|
1987
|
+
);
|
|
1909
1988
|
if (!stats) {
|
|
1910
1989
|
if (args.codex) {
|
|
1911
1990
|
console.error("Error: OpenAI Codex data not found at ~/.codex");
|
|
@@ -1959,7 +2038,10 @@ async function runUsage(args, config) {
|
|
|
1959
2038
|
}
|
|
1960
2039
|
async function runWrapped(args, config) {
|
|
1961
2040
|
const options = resolveOptions(args, config);
|
|
1962
|
-
const
|
|
2041
|
+
const spinner = createSpinner("Preparing wrapped...");
|
|
2042
|
+
const data = await spinner.whilePromise(
|
|
2043
|
+
loadData({ codexOnly: args.codex, combined: args.combined })
|
|
2044
|
+
);
|
|
1963
2045
|
validateData(data, { codexOnly: args.codex, combined: args.combined });
|
|
1964
2046
|
let claudeStats = null;
|
|
1965
2047
|
let codexStats = null;
|