vibestats 1.3.7 → 1.3.8
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 +376 -30
- package/package.json +8 -7
package/dist/index.js
CHANGED
|
@@ -788,7 +788,7 @@ function getClaudeDir() {
|
|
|
788
788
|
return process.env.CLAUDE_HOME || join(homedir(), ".claude");
|
|
789
789
|
}
|
|
790
790
|
function getCodexDir() {
|
|
791
|
-
return join(homedir(), ".codex");
|
|
791
|
+
return process.env.CODEX_HOME || join(homedir(), ".codex");
|
|
792
792
|
}
|
|
793
793
|
async function pathExists(path) {
|
|
794
794
|
try {
|
|
@@ -861,11 +861,12 @@ async function parseClaudeJsonl(projectFilter) {
|
|
|
861
861
|
}
|
|
862
862
|
const jsonlFiles = await findJsonlFiles(searchDir);
|
|
863
863
|
for (const filePath of jsonlFiles) {
|
|
864
|
-
const sessionId = basename(filePath, ".jsonl");
|
|
865
864
|
try {
|
|
866
865
|
const content = await fs.readFile(filePath, "utf-8");
|
|
867
866
|
if (!content.trim()) continue;
|
|
868
867
|
const lines = content.split("\n");
|
|
868
|
+
const fallbackSessionId = basename(filePath, ".jsonl");
|
|
869
|
+
const isSubagentFile = filePath.includes("/subagents/");
|
|
869
870
|
for (const line of lines) {
|
|
870
871
|
if (!line.trim()) continue;
|
|
871
872
|
try {
|
|
@@ -880,6 +881,8 @@ async function parseClaudeJsonl(projectFilter) {
|
|
|
880
881
|
const model = entry.message.model || "unknown";
|
|
881
882
|
const timestamp = entry.timestamp;
|
|
882
883
|
if (!timestamp) continue;
|
|
884
|
+
const sessionId = entry.sessionId || fallbackSessionId;
|
|
885
|
+
const sessionKind = entry.isSidechain === true || isSubagentFile ? "subagent" : "main";
|
|
883
886
|
const date = toLocalDateString(timestamp);
|
|
884
887
|
const pricing = getModelPricing(model);
|
|
885
888
|
const inputTokens = usage.input_tokens || 0;
|
|
@@ -898,6 +901,8 @@ async function parseClaudeJsonl(projectFilter) {
|
|
|
898
901
|
messageCount: 1,
|
|
899
902
|
source: "claude",
|
|
900
903
|
sessionId,
|
|
904
|
+
sessionKind,
|
|
905
|
+
subagentId: sessionKind === "subagent" ? filePath : void 0,
|
|
901
906
|
timestamp
|
|
902
907
|
});
|
|
903
908
|
} catch {
|
|
@@ -924,10 +929,22 @@ async function parseCodexJsonl() {
|
|
|
924
929
|
const content = await fs.readFile(filePath, "utf-8");
|
|
925
930
|
const lines = content.split("\n");
|
|
926
931
|
let currentModel = "gpt-5";
|
|
932
|
+
let fileSessionId = basename(filePath, ".jsonl");
|
|
933
|
+
let canonicalSessionId = fileSessionId;
|
|
934
|
+
let sessionKind = "main";
|
|
927
935
|
for (const line of lines) {
|
|
928
936
|
if (!line.trim()) continue;
|
|
929
937
|
try {
|
|
930
938
|
const entry = JSON.parse(line);
|
|
939
|
+
if (entry.type === "session_meta") {
|
|
940
|
+
const payload = entry.payload || {};
|
|
941
|
+
const payloadSessionId = typeof payload.id === "string" ? payload.id : fileSessionId;
|
|
942
|
+
const parentThreadId = typeof payload.source?.subagent?.thread_spawn?.parent_thread_id === "string" ? payload.source.subagent.thread_spawn.parent_thread_id : null;
|
|
943
|
+
fileSessionId = payloadSessionId;
|
|
944
|
+
canonicalSessionId = parentThreadId || payloadSessionId;
|
|
945
|
+
sessionKind = parentThreadId ? "subagent" : "main";
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
931
948
|
if (entry.type === "turn_context" && entry.payload?.model) {
|
|
932
949
|
currentModel = entry.payload.model;
|
|
933
950
|
continue;
|
|
@@ -954,7 +971,9 @@ async function parseCodexJsonl() {
|
|
|
954
971
|
cost,
|
|
955
972
|
messageCount: 1,
|
|
956
973
|
source: "codex",
|
|
957
|
-
sessionId:
|
|
974
|
+
sessionId: canonicalSessionId,
|
|
975
|
+
sessionKind,
|
|
976
|
+
subagentId: sessionKind === "subagent" ? fileSessionId : void 0,
|
|
958
977
|
timestamp
|
|
959
978
|
});
|
|
960
979
|
}
|
|
@@ -966,6 +985,23 @@ async function parseCodexJsonl() {
|
|
|
966
985
|
}
|
|
967
986
|
return entries;
|
|
968
987
|
}
|
|
988
|
+
function computeSessionCounts(entries) {
|
|
989
|
+
const mainSessions = /* @__PURE__ */ new Set();
|
|
990
|
+
const subagentSessions = /* @__PURE__ */ new Set();
|
|
991
|
+
for (const entry of entries) {
|
|
992
|
+
if (entry.sessionId) {
|
|
993
|
+
mainSessions.add(`${entry.source}:${entry.sessionId}`);
|
|
994
|
+
}
|
|
995
|
+
if (entry.sessionKind === "subagent" && entry.subagentId) {
|
|
996
|
+
subagentSessions.add(`${entry.source}:${entry.subagentId}`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
return {
|
|
1000
|
+
main: mainSessions.size,
|
|
1001
|
+
subagent: subagentSessions.size,
|
|
1002
|
+
total: mainSessions.size + subagentSessions.size
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
969
1005
|
function filterByDateRange(entries, since, until) {
|
|
970
1006
|
return entries.filter((e) => {
|
|
971
1007
|
if (since && e.date < since) return false;
|
|
@@ -1082,7 +1118,7 @@ function aggregateByModel(entries) {
|
|
|
1082
1118
|
function aggregateBySession(entries) {
|
|
1083
1119
|
const sessionMap = /* @__PURE__ */ new Map();
|
|
1084
1120
|
for (const e of entries) {
|
|
1085
|
-
const sid = e.sessionId
|
|
1121
|
+
const sid = e.sessionId ? `${e.source}:${e.sessionId}` : "unknown";
|
|
1086
1122
|
const existing = sessionMap.get(sid);
|
|
1087
1123
|
if (existing) {
|
|
1088
1124
|
existing.inputTokens += e.inputTokens;
|
|
@@ -1097,6 +1133,7 @@ function aggregateBySession(entries) {
|
|
|
1097
1133
|
}
|
|
1098
1134
|
} else {
|
|
1099
1135
|
sessionMap.set(sid, {
|
|
1136
|
+
displaySessionId: e.sessionId || sid,
|
|
1100
1137
|
modelsSet: /* @__PURE__ */ new Set([e.model]),
|
|
1101
1138
|
inputTokens: e.inputTokens,
|
|
1102
1139
|
outputTokens: e.outputTokens,
|
|
@@ -1110,7 +1147,7 @@ function aggregateBySession(entries) {
|
|
|
1110
1147
|
}
|
|
1111
1148
|
return Array.from(sessionMap.entries()).filter(([, data]) => data.totalTokens > 0).map(([sessionId, data]) => {
|
|
1112
1149
|
const date = data.firstTimestamp ? toLocalDateString(data.firstTimestamp) : "unknown";
|
|
1113
|
-
const shortId =
|
|
1150
|
+
const shortId = data.displaySessionId.slice(0, 12);
|
|
1114
1151
|
return {
|
|
1115
1152
|
key: `${date} ${shortId}`,
|
|
1116
1153
|
inputTokens: data.inputTokens,
|
|
@@ -1167,9 +1204,6 @@ function computeActivityStats(entries, source) {
|
|
|
1167
1204
|
existing.cacheReadTokens += entry.cacheReadTokens;
|
|
1168
1205
|
existing.totalTokens += entry.inputTokens + entry.outputTokens + entry.cacheWriteTokens + entry.cacheReadTokens;
|
|
1169
1206
|
existing.messageCount += entry.messageCount;
|
|
1170
|
-
if (entry.sessionId) {
|
|
1171
|
-
existing.sessionCount += 0;
|
|
1172
|
-
}
|
|
1173
1207
|
} else {
|
|
1174
1208
|
dayMap.set(entry.date, {
|
|
1175
1209
|
date: entry.date,
|
|
@@ -1187,7 +1221,7 @@ function computeActivityStats(entries, source) {
|
|
|
1187
1221
|
for (const entry of entries) {
|
|
1188
1222
|
if (!entry.sessionId) continue;
|
|
1189
1223
|
const sessions = sessionMap.get(entry.date) || /* @__PURE__ */ new Set();
|
|
1190
|
-
sessions.add(entry.sessionId);
|
|
1224
|
+
sessions.add(`${entry.source}:${entry.sessionId}`);
|
|
1191
1225
|
sessionMap.set(entry.date, sessions);
|
|
1192
1226
|
}
|
|
1193
1227
|
const days = Array.from(dayMap.values()).map((day) => ({
|
|
@@ -1223,7 +1257,8 @@ function computeActivityStats(entries, source) {
|
|
|
1223
1257
|
},
|
|
1224
1258
|
days,
|
|
1225
1259
|
totals,
|
|
1226
|
-
modelBreakdown: computeModelBreakdown(entries)
|
|
1260
|
+
modelBreakdown: computeModelBreakdown(entries),
|
|
1261
|
+
sessionCounts: computeSessionCounts(entries)
|
|
1227
1262
|
};
|
|
1228
1263
|
}
|
|
1229
1264
|
async function loadUsageStats(options) {
|
|
@@ -1269,6 +1304,7 @@ async function loadUsageStats(options) {
|
|
|
1269
1304
|
}
|
|
1270
1305
|
const totals = aggregation === "total" ? computeTotals(aggregateByDay(entries)) : computeTotals(rows);
|
|
1271
1306
|
const modelBreakdown = computeModelBreakdown(entries);
|
|
1307
|
+
const sessionCounts = computeSessionCounts(entries);
|
|
1272
1308
|
let source = "claude";
|
|
1273
1309
|
if (codexOnly) source = "codex";
|
|
1274
1310
|
else if (combined) source = "combined";
|
|
@@ -1278,7 +1314,8 @@ async function loadUsageStats(options) {
|
|
|
1278
1314
|
source,
|
|
1279
1315
|
aggregation,
|
|
1280
1316
|
dateRange,
|
|
1281
|
-
modelBreakdown
|
|
1317
|
+
modelBreakdown,
|
|
1318
|
+
sessionCounts
|
|
1282
1319
|
};
|
|
1283
1320
|
}
|
|
1284
1321
|
async function loadActivityStats(options) {
|
|
@@ -1340,6 +1377,9 @@ function padLeft(str, width) {
|
|
|
1340
1377
|
function padRight(str, width) {
|
|
1341
1378
|
return str.padEnd(width);
|
|
1342
1379
|
}
|
|
1380
|
+
function formatCount(n) {
|
|
1381
|
+
return n.toLocaleString("en-US");
|
|
1382
|
+
}
|
|
1343
1383
|
function displayUsageTable(stats, options = {}) {
|
|
1344
1384
|
const c = getColors(options.showColors);
|
|
1345
1385
|
const { compact, hideCost } = options;
|
|
@@ -1352,6 +1392,9 @@ function displayUsageTable(stats, options = {}) {
|
|
|
1352
1392
|
const aggLabel = stats.aggregation === "daily" ? "Daily" : stats.aggregation === "monthly" ? "Monthly" : stats.aggregation === "model" ? "By Model" : stats.aggregation === "session" ? "By Session" : "Total";
|
|
1353
1393
|
console.log(`${c.orange}${c.bold}${title} - ${aggLabel} Report${c.reset}`);
|
|
1354
1394
|
console.log(`${c.gray}Date range: ${stats.dateRange.start} to ${stats.dateRange.end}${c.reset}`);
|
|
1395
|
+
if (stats.sessionCounts) {
|
|
1396
|
+
console.log(`${c.gray}Tracked mix: ${formatCount(stats.sessionCounts.main)} main, ${formatCount(stats.sessionCounts.subagent)} subagents${c.reset}`);
|
|
1397
|
+
}
|
|
1355
1398
|
console.log(`${c.gray}${"\u2500".repeat(Math.min(termWidth - 2, 120))}${c.reset}`);
|
|
1356
1399
|
console.log();
|
|
1357
1400
|
const isSession = stats.aggregation === "session";
|
|
@@ -1431,6 +1474,9 @@ function displayTotalOnly(stats, options = {}) {
|
|
|
1431
1474
|
else if (stats.source === "combined") title = "AI Coding Usage (Claude + Codex)";
|
|
1432
1475
|
console.log(`${c.orange}${c.bold}${title} - Summary${c.reset}`);
|
|
1433
1476
|
console.log(`${c.gray}Date range: ${stats.dateRange.start} to ${stats.dateRange.end}${c.reset}`);
|
|
1477
|
+
if (stats.sessionCounts) {
|
|
1478
|
+
console.log(`${c.gray}Tracked mix: ${formatCount(stats.sessionCounts.main)} main, ${formatCount(stats.sessionCounts.subagent)} subagents${c.reset}`);
|
|
1479
|
+
}
|
|
1434
1480
|
console.log(`${c.gray}${"\u2500".repeat(50)}${c.reset}`);
|
|
1435
1481
|
console.log();
|
|
1436
1482
|
const t = stats.totals;
|
|
@@ -1463,7 +1509,7 @@ function displayTotalOnly(stats, options = {}) {
|
|
|
1463
1509
|
// src/claude-jsonl-loader.ts
|
|
1464
1510
|
import { promises as fs2 } from "fs";
|
|
1465
1511
|
import { homedir as homedir2 } from "os";
|
|
1466
|
-
import { join as join2 } from "path";
|
|
1512
|
+
import { basename as basename2, join as join2 } from "path";
|
|
1467
1513
|
function getClaudeDir2() {
|
|
1468
1514
|
return process.env.CLAUDE_HOME || join2(homedir2(), ".claude");
|
|
1469
1515
|
}
|
|
@@ -1521,6 +1567,9 @@ async function loadClaudeStatsFromJsonl() {
|
|
|
1521
1567
|
}
|
|
1522
1568
|
const modelUsage = {};
|
|
1523
1569
|
const dailyMap = /* @__PURE__ */ new Map();
|
|
1570
|
+
const sessionsByDate = /* @__PURE__ */ new Map();
|
|
1571
|
+
const mainSessions = /* @__PURE__ */ new Set();
|
|
1572
|
+
const subagentSessions = /* @__PURE__ */ new Set();
|
|
1524
1573
|
const hourCounts = {};
|
|
1525
1574
|
let firstTimestamp = null;
|
|
1526
1575
|
let totalMessages = 0;
|
|
@@ -1529,6 +1578,8 @@ async function loadClaudeStatsFromJsonl() {
|
|
|
1529
1578
|
try {
|
|
1530
1579
|
const content = await fs2.readFile(filePath, "utf-8");
|
|
1531
1580
|
const lines = content.split("\n");
|
|
1581
|
+
const fallbackSessionId = basename2(filePath, ".jsonl");
|
|
1582
|
+
const isSubagentFile = filePath.includes("/subagents/");
|
|
1532
1583
|
for (const line of lines) {
|
|
1533
1584
|
if (!line.trim()) continue;
|
|
1534
1585
|
try {
|
|
@@ -1539,6 +1590,8 @@ async function loadClaudeStatsFromJsonl() {
|
|
|
1539
1590
|
const model = entry.message.model || "unknown";
|
|
1540
1591
|
const timestamp = entry.timestamp;
|
|
1541
1592
|
const messageId = entry.message.id;
|
|
1593
|
+
const sessionId = entry.sessionId || fallbackSessionId;
|
|
1594
|
+
const sessionKind = entry.isSidechain === true || isSubagentFile ? "subagent" : "main";
|
|
1542
1595
|
if (!modelUsage[model]) {
|
|
1543
1596
|
modelUsage[model] = {
|
|
1544
1597
|
inputTokens: 0,
|
|
@@ -1562,8 +1615,15 @@ async function loadClaudeStatsFromJsonl() {
|
|
|
1562
1615
|
}
|
|
1563
1616
|
const daily = dailyMap.get(date);
|
|
1564
1617
|
daily.messageCount++;
|
|
1618
|
+
const dateSessions = sessionsByDate.get(date) || /* @__PURE__ */ new Set();
|
|
1619
|
+
dateSessions.add(sessionId);
|
|
1620
|
+
sessionsByDate.set(date, dateSessions);
|
|
1565
1621
|
hourCounts[hour] = (hourCounts[hour] || 0) + 1;
|
|
1566
1622
|
}
|
|
1623
|
+
mainSessions.add(sessionId);
|
|
1624
|
+
if (sessionKind === "subagent") {
|
|
1625
|
+
subagentSessions.add(filePath);
|
|
1626
|
+
}
|
|
1567
1627
|
if (timestamp && (!firstTimestamp || timestamp < firstTimestamp)) {
|
|
1568
1628
|
firstTimestamp = timestamp;
|
|
1569
1629
|
}
|
|
@@ -1592,18 +1652,21 @@ async function loadClaudeStatsFromJsonl() {
|
|
|
1592
1652
|
const dailyActivity = Array.from(dailyMap.entries()).map(([date, data]) => ({
|
|
1593
1653
|
date,
|
|
1594
1654
|
messageCount: data.messageCount,
|
|
1595
|
-
sessionCount:
|
|
1655
|
+
sessionCount: sessionsByDate.get(date)?.size || 0,
|
|
1596
1656
|
toolCallCount: data.toolCallCount
|
|
1597
1657
|
})).sort((a, b) => a.date.localeCompare(b.date));
|
|
1598
|
-
const
|
|
1599
|
-
|
|
1658
|
+
const sessionCounts = {
|
|
1659
|
+
main: mainSessions.size,
|
|
1660
|
+
subagent: subagentSessions.size,
|
|
1661
|
+
total: mainSessions.size + subagentSessions.size
|
|
1662
|
+
};
|
|
1600
1663
|
return {
|
|
1601
1664
|
version: 1,
|
|
1602
1665
|
lastComputedDate: toLocalDateString2((/* @__PURE__ */ new Date()).toISOString()),
|
|
1603
1666
|
dailyActivity,
|
|
1604
1667
|
dailyModelTokens: [],
|
|
1605
1668
|
modelUsage: statsCacheModelUsage,
|
|
1606
|
-
totalSessions:
|
|
1669
|
+
totalSessions: sessionCounts.main,
|
|
1607
1670
|
totalMessages,
|
|
1608
1671
|
longestSession: {
|
|
1609
1672
|
sessionId: "",
|
|
@@ -1612,7 +1675,8 @@ async function loadClaudeStatsFromJsonl() {
|
|
|
1612
1675
|
timestamp: ""
|
|
1613
1676
|
},
|
|
1614
1677
|
firstSessionDate: firstTimestamp || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1615
|
-
hourCounts
|
|
1678
|
+
hourCounts,
|
|
1679
|
+
sessionCounts
|
|
1616
1680
|
};
|
|
1617
1681
|
}
|
|
1618
1682
|
|
|
@@ -1677,12 +1741,16 @@ async function parseSessionFile(filePath) {
|
|
|
1677
1741
|
let sessionMeta = null;
|
|
1678
1742
|
let currentModel = "gpt-5";
|
|
1679
1743
|
const perModelUsage = {};
|
|
1744
|
+
let sessionKind = "main";
|
|
1745
|
+
let canonicalId = "";
|
|
1680
1746
|
for (const line of lines) {
|
|
1681
1747
|
if (!line.trim()) continue;
|
|
1682
1748
|
try {
|
|
1683
1749
|
const entry = JSON.parse(line);
|
|
1684
1750
|
if (entry.type === "session_meta") {
|
|
1685
1751
|
sessionMeta = entry.payload;
|
|
1752
|
+
canonicalId = sessionMeta.source?.subagent?.thread_spawn?.parent_thread_id || sessionMeta.id;
|
|
1753
|
+
sessionKind = sessionMeta.source?.subagent?.thread_spawn?.parent_thread_id ? "subagent" : "main";
|
|
1686
1754
|
} else if (entry.type === "turn_context") {
|
|
1687
1755
|
const payload = entry.payload;
|
|
1688
1756
|
if (payload.model) {
|
|
@@ -1734,11 +1802,13 @@ async function parseSessionFile(filePath) {
|
|
|
1734
1802
|
}
|
|
1735
1803
|
return {
|
|
1736
1804
|
id: sessionMeta.id,
|
|
1805
|
+
canonicalId: canonicalId || sessionMeta.id,
|
|
1737
1806
|
timestamp: sessionMeta.timestamp,
|
|
1738
1807
|
cwd: sessionMeta.cwd,
|
|
1739
1808
|
model: primaryModel,
|
|
1740
1809
|
tokenUsage: summedUsage,
|
|
1741
|
-
perModelUsage
|
|
1810
|
+
perModelUsage,
|
|
1811
|
+
sessionKind
|
|
1742
1812
|
};
|
|
1743
1813
|
} catch {
|
|
1744
1814
|
return null;
|
|
@@ -1771,7 +1841,13 @@ async function loadCodexStats() {
|
|
|
1771
1841
|
}
|
|
1772
1842
|
sessions.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
1773
1843
|
const modelUsage = {};
|
|
1844
|
+
const mainSessions = /* @__PURE__ */ new Set();
|
|
1845
|
+
const subagentSessions = /* @__PURE__ */ new Set();
|
|
1774
1846
|
for (const session of sessions) {
|
|
1847
|
+
mainSessions.add(session.canonicalId);
|
|
1848
|
+
if (session.sessionKind === "subagent") {
|
|
1849
|
+
subagentSessions.add(session.id);
|
|
1850
|
+
}
|
|
1775
1851
|
if (session.perModelUsage) {
|
|
1776
1852
|
for (const [model, usage] of Object.entries(session.perModelUsage)) {
|
|
1777
1853
|
if (!modelUsage[model]) {
|
|
@@ -1805,23 +1881,36 @@ async function loadCodexStats() {
|
|
|
1805
1881
|
const dailyMap = /* @__PURE__ */ new Map();
|
|
1806
1882
|
for (const session of sessions) {
|
|
1807
1883
|
const date = toLocalDateString3(session.timestamp);
|
|
1808
|
-
dailyMap.set(date,
|
|
1884
|
+
dailyMap.set(date, 0);
|
|
1809
1885
|
}
|
|
1810
|
-
const
|
|
1886
|
+
const sessionsByDate = /* @__PURE__ */ new Map();
|
|
1887
|
+
for (const session of sessions) {
|
|
1888
|
+
const date = toLocalDateString3(session.timestamp);
|
|
1889
|
+
const dateSessions = sessionsByDate.get(date) || /* @__PURE__ */ new Set();
|
|
1890
|
+
dateSessions.add(session.canonicalId);
|
|
1891
|
+
sessionsByDate.set(date, dateSessions);
|
|
1892
|
+
}
|
|
1893
|
+
const dailyActivity = Array.from(dailyMap.entries()).map(([date]) => ({ date, sessionCount: sessionsByDate.get(date)?.size || 0 })).sort((a, b) => a.date.localeCompare(b.date));
|
|
1811
1894
|
const hourCounts = {};
|
|
1812
1895
|
for (const session of sessions) {
|
|
1813
1896
|
const hour = new Date(session.timestamp).getHours().toString();
|
|
1814
1897
|
hourCounts[hour] = (hourCounts[hour] || 0) + 1;
|
|
1815
1898
|
}
|
|
1816
1899
|
const totalMessages = sessions.length;
|
|
1900
|
+
const sessionCounts = {
|
|
1901
|
+
main: mainSessions.size,
|
|
1902
|
+
subagent: subagentSessions.size,
|
|
1903
|
+
total: mainSessions.size + subagentSessions.size
|
|
1904
|
+
};
|
|
1817
1905
|
return {
|
|
1818
1906
|
sessions,
|
|
1819
|
-
totalSessions:
|
|
1907
|
+
totalSessions: sessionCounts.main,
|
|
1820
1908
|
totalMessages,
|
|
1821
1909
|
modelUsage,
|
|
1822
1910
|
dailyActivity,
|
|
1823
1911
|
hourCounts,
|
|
1824
|
-
firstSessionDate: toLocalDateString3(sessions[0].timestamp)
|
|
1912
|
+
firstSessionDate: toLocalDateString3(sessions[0].timestamp),
|
|
1913
|
+
sessionCounts
|
|
1825
1914
|
};
|
|
1826
1915
|
}
|
|
1827
1916
|
|
|
@@ -2009,6 +2098,23 @@ function calculateWordsGenerated(modelUsage) {
|
|
|
2009
2098
|
}
|
|
2010
2099
|
return Math.round(totalOutputTokens * 0.75);
|
|
2011
2100
|
}
|
|
2101
|
+
function mergeSessionCounts(...counts) {
|
|
2102
|
+
let main2 = 0;
|
|
2103
|
+
let subagent = 0;
|
|
2104
|
+
let found = false;
|
|
2105
|
+
for (const count of counts) {
|
|
2106
|
+
if (!count) continue;
|
|
2107
|
+
found = true;
|
|
2108
|
+
main2 += count.main;
|
|
2109
|
+
subagent += count.subagent;
|
|
2110
|
+
}
|
|
2111
|
+
if (!found) return void 0;
|
|
2112
|
+
return {
|
|
2113
|
+
main: main2,
|
|
2114
|
+
subagent,
|
|
2115
|
+
total: main2 + subagent
|
|
2116
|
+
};
|
|
2117
|
+
}
|
|
2012
2118
|
function computeWrappedStats(cache) {
|
|
2013
2119
|
let totalCost = 0;
|
|
2014
2120
|
for (const [modelName, usage] of Object.entries(cache.modelUsage)) {
|
|
@@ -2038,7 +2144,8 @@ function computeWrappedStats(cache) {
|
|
|
2038
2144
|
modelBreakdown,
|
|
2039
2145
|
wordsGenerated,
|
|
2040
2146
|
firstSessionDate: cache.firstSessionDate,
|
|
2041
|
-
source: "claude"
|
|
2147
|
+
source: "claude",
|
|
2148
|
+
sessionCounts: cache.sessionCounts
|
|
2042
2149
|
};
|
|
2043
2150
|
}
|
|
2044
2151
|
function computeCodexWrappedStats(cache) {
|
|
@@ -2122,7 +2229,8 @@ function computeCodexWrappedStats(cache) {
|
|
|
2122
2229
|
modelBreakdown,
|
|
2123
2230
|
wordsGenerated,
|
|
2124
2231
|
firstSessionDate: cache.firstSessionDate,
|
|
2125
|
-
source: "codex"
|
|
2232
|
+
source: "codex",
|
|
2233
|
+
sessionCounts: cache.sessionCounts
|
|
2126
2234
|
};
|
|
2127
2235
|
}
|
|
2128
2236
|
function combineWrappedStats(claude, codex) {
|
|
@@ -2154,8 +2262,9 @@ function combineWrappedStats(claude, codex) {
|
|
|
2154
2262
|
const peakHour = claude.sessions > codex.sessions ? claude.peakHour : codex.peakHour;
|
|
2155
2263
|
const peakDay = claude.sessions > codex.sessions ? claude.peakDay : codex.peakDay;
|
|
2156
2264
|
const busiestMonth = claude.sessions > codex.sessions ? claude.busiestMonth : codex.busiestMonth;
|
|
2265
|
+
const sessionCounts = mergeSessionCounts(claude.sessionCounts, codex.sessionCounts);
|
|
2157
2266
|
return {
|
|
2158
|
-
sessions: claude.sessions + codex.sessions,
|
|
2267
|
+
sessions: sessionCounts?.main ?? claude.sessions + codex.sessions,
|
|
2159
2268
|
messages: claude.messages + codex.messages,
|
|
2160
2269
|
totalTokens: claude.totalTokens + codex.totalTokens,
|
|
2161
2270
|
totalCost: claude.totalCost + codex.totalCost,
|
|
@@ -2170,7 +2279,8 @@ function combineWrappedStats(claude, codex) {
|
|
|
2170
2279
|
modelBreakdown: combinedBreakdown,
|
|
2171
2280
|
wordsGenerated: claude.wordsGenerated + codex.wordsGenerated,
|
|
2172
2281
|
firstSessionDate,
|
|
2173
|
-
source: "combined"
|
|
2282
|
+
source: "combined",
|
|
2283
|
+
sessionCounts
|
|
2174
2284
|
};
|
|
2175
2285
|
}
|
|
2176
2286
|
|
|
@@ -2196,6 +2306,9 @@ function getColors2(theme) {
|
|
|
2196
2306
|
}
|
|
2197
2307
|
return colors2;
|
|
2198
2308
|
}
|
|
2309
|
+
function formatCount2(n) {
|
|
2310
|
+
return n.toLocaleString("en-US");
|
|
2311
|
+
}
|
|
2199
2312
|
function displayWrappedStats(stats, url, options) {
|
|
2200
2313
|
const c = getColors2(options?.theme);
|
|
2201
2314
|
console.log();
|
|
@@ -2224,6 +2337,11 @@ function displayWrappedStats(stats, url, options) {
|
|
|
2224
2337
|
`${c.amber}${c.bold}${stats.daysActive}${c.reset} days active`
|
|
2225
2338
|
];
|
|
2226
2339
|
console.log(` ${statsLine.join(" \u2502 ")}`);
|
|
2340
|
+
if (stats.sessionCounts) {
|
|
2341
|
+
console.log(
|
|
2342
|
+
` ${c.dim}Tracked mix: ${formatCount2(stats.sessionCounts.main)} main, ${formatCount2(stats.sessionCounts.subagent)} subagents${c.reset}`
|
|
2343
|
+
);
|
|
2344
|
+
}
|
|
2227
2345
|
console.log();
|
|
2228
2346
|
console.log(`${c.bold}\u{1F525} Consistency${c.reset}`);
|
|
2229
2347
|
console.log(`${c.gray}${"\u2500".repeat(60)}${c.reset}`);
|
|
@@ -2369,11 +2487,164 @@ async function publishArtifact(artifact, baseUrl, legacyUrl) {
|
|
|
2369
2487
|
}
|
|
2370
2488
|
}
|
|
2371
2489
|
|
|
2372
|
-
// src/
|
|
2373
|
-
import {
|
|
2490
|
+
// src/claude-inspector.ts
|
|
2491
|
+
import { promises as fs4 } from "fs";
|
|
2374
2492
|
import { homedir as homedir4 } from "os";
|
|
2375
2493
|
import { join as join4 } from "path";
|
|
2376
|
-
|
|
2494
|
+
function getClaudeDir3() {
|
|
2495
|
+
return process.env.CLAUDE_HOME || join4(homedir4(), ".claude");
|
|
2496
|
+
}
|
|
2497
|
+
async function pathExists4(path) {
|
|
2498
|
+
try {
|
|
2499
|
+
await fs4.access(path);
|
|
2500
|
+
return true;
|
|
2501
|
+
} catch {
|
|
2502
|
+
return false;
|
|
2503
|
+
}
|
|
2504
|
+
}
|
|
2505
|
+
async function readJsonFile(path) {
|
|
2506
|
+
try {
|
|
2507
|
+
const content = await fs4.readFile(path, "utf-8");
|
|
2508
|
+
return JSON.parse(content);
|
|
2509
|
+
} catch {
|
|
2510
|
+
return null;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
2513
|
+
async function walkFiles(dir, visitor) {
|
|
2514
|
+
if (!await pathExists4(dir)) return;
|
|
2515
|
+
let entries;
|
|
2516
|
+
try {
|
|
2517
|
+
entries = await fs4.readdir(dir);
|
|
2518
|
+
} catch {
|
|
2519
|
+
return;
|
|
2520
|
+
}
|
|
2521
|
+
for (const entry of entries) {
|
|
2522
|
+
const fullPath = join4(dir, entry);
|
|
2523
|
+
let stat;
|
|
2524
|
+
try {
|
|
2525
|
+
stat = await fs4.stat(fullPath);
|
|
2526
|
+
} catch {
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
if (stat.isDirectory()) {
|
|
2530
|
+
await walkFiles(fullPath, visitor);
|
|
2531
|
+
} else {
|
|
2532
|
+
await visitor(fullPath, entry);
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
function daysSince(dateStr) {
|
|
2537
|
+
if (!dateStr) return null;
|
|
2538
|
+
const parsed = /* @__PURE__ */ new Date(`${dateStr}T12:00:00`);
|
|
2539
|
+
if (Number.isNaN(parsed.getTime())) return null;
|
|
2540
|
+
const diffMs = Date.now() - parsed.getTime();
|
|
2541
|
+
return Math.max(0, Math.floor(diffMs / (24 * 60 * 60 * 1e3)));
|
|
2542
|
+
}
|
|
2543
|
+
async function inspectClaudeSystem(home = homedir4()) {
|
|
2544
|
+
const path = join4(home, ".claude.json");
|
|
2545
|
+
const json = await readJsonFile(path);
|
|
2546
|
+
return {
|
|
2547
|
+
path,
|
|
2548
|
+
exists: Boolean(json),
|
|
2549
|
+
oauthAccount: json?.oauthAccount || null,
|
|
2550
|
+
hasAvailableSubscription: json?.hasAvailableSubscription,
|
|
2551
|
+
cachedExtraUsageDisabledReason: json?.cachedExtraUsageDisabledReason,
|
|
2552
|
+
subscriptionNoticeCount: json?.subscriptionNoticeCount,
|
|
2553
|
+
toolUsageCount: json?.toolUsage ? Object.keys(json.toolUsage).length : void 0,
|
|
2554
|
+
skillUsageCount: json?.skillUsage ? Object.keys(json.skillUsage).length : void 0,
|
|
2555
|
+
projectCount: json?.projects ? Object.keys(json.projects).length : void 0
|
|
2556
|
+
};
|
|
2557
|
+
}
|
|
2558
|
+
async function inspectClaudeUsage(claudeDir = getClaudeDir3()) {
|
|
2559
|
+
const statsCachePath = join4(claudeDir, "stats-cache.json");
|
|
2560
|
+
const statsCache = await readJsonFile(statsCachePath);
|
|
2561
|
+
const facetsDir = join4(claudeDir, "usage-data", "facets");
|
|
2562
|
+
const sessionMetaDir = join4(claudeDir, "usage-data", "session-meta");
|
|
2563
|
+
const projectsDir = join4(claudeDir, "projects");
|
|
2564
|
+
const facetFiles = await pathExists4(facetsDir) ? (await fs4.readdir(facetsDir)).filter((file) => file.endsWith(".json")) : [];
|
|
2565
|
+
const sessionMetaFiles = await pathExists4(sessionMetaDir) ? (await fs4.readdir(sessionMetaDir)).filter((file) => file.endsWith(".json")) : [];
|
|
2566
|
+
let rateLimitHit = 0;
|
|
2567
|
+
let apiTokenLimit = 0;
|
|
2568
|
+
let contextLimit = 0;
|
|
2569
|
+
for (const file of facetFiles) {
|
|
2570
|
+
const json = await readJsonFile(join4(facetsDir, file));
|
|
2571
|
+
const friction = json?.friction_counts || {};
|
|
2572
|
+
rateLimitHit += friction.rate_limit_hit || 0;
|
|
2573
|
+
apiTokenLimit += friction.api_token_limit || 0;
|
|
2574
|
+
contextLimit += friction.context_limit || 0;
|
|
2575
|
+
}
|
|
2576
|
+
let sessionsUsingTaskAgent = 0;
|
|
2577
|
+
let inputTokens = 0;
|
|
2578
|
+
let outputTokens = 0;
|
|
2579
|
+
for (const file of sessionMetaFiles) {
|
|
2580
|
+
const json = await readJsonFile(join4(sessionMetaDir, file));
|
|
2581
|
+
if (!json) continue;
|
|
2582
|
+
if (json.uses_task_agent) sessionsUsingTaskAgent += 1;
|
|
2583
|
+
inputTokens += json.input_tokens || 0;
|
|
2584
|
+
outputTokens += json.output_tokens || 0;
|
|
2585
|
+
}
|
|
2586
|
+
let mainJsonlFiles = 0;
|
|
2587
|
+
let subagentJsonlFiles = 0;
|
|
2588
|
+
let sessionsIndexFiles = 0;
|
|
2589
|
+
let indexedSessionEntries = 0;
|
|
2590
|
+
await walkFiles(projectsDir, async (fullPath, entryName) => {
|
|
2591
|
+
if (entryName === "sessions-index.json") {
|
|
2592
|
+
sessionsIndexFiles += 1;
|
|
2593
|
+
const indexJson = await readJsonFile(fullPath);
|
|
2594
|
+
if (Array.isArray(indexJson)) {
|
|
2595
|
+
indexedSessionEntries += indexJson.length;
|
|
2596
|
+
} else if (Array.isArray(indexJson?.entries)) {
|
|
2597
|
+
indexedSessionEntries += indexJson.entries.length;
|
|
2598
|
+
}
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2601
|
+
if (!entryName.endsWith(".jsonl")) return;
|
|
2602
|
+
if (fullPath.includes("/subagents/")) {
|
|
2603
|
+
subagentJsonlFiles += 1;
|
|
2604
|
+
} else {
|
|
2605
|
+
mainJsonlFiles += 1;
|
|
2606
|
+
}
|
|
2607
|
+
});
|
|
2608
|
+
return {
|
|
2609
|
+
claudeDir,
|
|
2610
|
+
statsCache: {
|
|
2611
|
+
path: statsCachePath,
|
|
2612
|
+
exists: Boolean(statsCache),
|
|
2613
|
+
lastComputedDate: statsCache?.lastComputedDate,
|
|
2614
|
+
totalSessions: statsCache?.totalSessions,
|
|
2615
|
+
totalMessages: statsCache?.totalMessages,
|
|
2616
|
+
ageDays: daysSince(statsCache?.lastComputedDate)
|
|
2617
|
+
},
|
|
2618
|
+
usageData: {
|
|
2619
|
+
sessionMetaFiles: sessionMetaFiles.length,
|
|
2620
|
+
facetFiles: facetFiles.length
|
|
2621
|
+
},
|
|
2622
|
+
facets: {
|
|
2623
|
+
rateLimitHit,
|
|
2624
|
+
apiTokenLimit,
|
|
2625
|
+
contextLimit
|
|
2626
|
+
},
|
|
2627
|
+
sessionMeta: {
|
|
2628
|
+
sessionsUsingTaskAgent,
|
|
2629
|
+
taskAgentShare: sessionMetaFiles.length > 0 ? Number((sessionsUsingTaskAgent / sessionMetaFiles.length * 100).toFixed(1)) : 0,
|
|
2630
|
+
inputTokens,
|
|
2631
|
+
outputTokens,
|
|
2632
|
+
totalTokens: inputTokens + outputTokens
|
|
2633
|
+
},
|
|
2634
|
+
projects: {
|
|
2635
|
+
mainJsonlFiles,
|
|
2636
|
+
subagentJsonlFiles,
|
|
2637
|
+
sessionsIndexFiles,
|
|
2638
|
+
indexedSessionEntries
|
|
2639
|
+
}
|
|
2640
|
+
};
|
|
2641
|
+
}
|
|
2642
|
+
|
|
2643
|
+
// src/config.ts
|
|
2644
|
+
import { readFileSync, existsSync, writeFileSync } from "fs";
|
|
2645
|
+
import { homedir as homedir5 } from "os";
|
|
2646
|
+
import { join as join5 } from "path";
|
|
2647
|
+
var CONFIG_PATH = join5(homedir5(), ".vibestats.json");
|
|
2377
2648
|
var DEFAULT_CONFIG = {
|
|
2378
2649
|
baseUrl: "https://vibestats.wolfai.dev",
|
|
2379
2650
|
outputFormat: "normal",
|
|
@@ -2579,7 +2850,7 @@ async function publishArtifactWithFallback(artifact, baseUrl, fallbackUrl, prefe
|
|
|
2579
2850
|
var main = defineCommand({
|
|
2580
2851
|
meta: {
|
|
2581
2852
|
name: "vibestats",
|
|
2582
|
-
version: "1.3.
|
|
2853
|
+
version: "1.3.8",
|
|
2583
2854
|
description: "AI coding stats - usage tracking and annual wrapped for Claude Code & Codex"
|
|
2584
2855
|
},
|
|
2585
2856
|
args: {
|
|
@@ -2708,6 +2979,16 @@ var main = defineCommand({
|
|
|
2708
2979
|
type: "boolean",
|
|
2709
2980
|
description: "Show current config location and values",
|
|
2710
2981
|
default: false
|
|
2982
|
+
},
|
|
2983
|
+
"claude-system": {
|
|
2984
|
+
type: "boolean",
|
|
2985
|
+
description: "Inspect ~/.claude.json account and app state",
|
|
2986
|
+
default: false
|
|
2987
|
+
},
|
|
2988
|
+
"claude-limits": {
|
|
2989
|
+
type: "boolean",
|
|
2990
|
+
description: "Inspect Claude usage-data, cache freshness, and local limit signals",
|
|
2991
|
+
default: false
|
|
2711
2992
|
}
|
|
2712
2993
|
},
|
|
2713
2994
|
async run({ args }) {
|
|
@@ -2721,6 +3002,14 @@ var main = defineCommand({
|
|
|
2721
3002
|
console.log(JSON.stringify(config, null, 2));
|
|
2722
3003
|
return;
|
|
2723
3004
|
}
|
|
3005
|
+
if (args["claude-system"]) {
|
|
3006
|
+
await runClaudeSystem(args);
|
|
3007
|
+
return;
|
|
3008
|
+
}
|
|
3009
|
+
if (args["claude-limits"]) {
|
|
3010
|
+
await runClaudeLimits(args);
|
|
3011
|
+
return;
|
|
3012
|
+
}
|
|
2724
3013
|
if (args.activity) {
|
|
2725
3014
|
await runActivity(args, config);
|
|
2726
3015
|
} else if (args.wrapped) {
|
|
@@ -2942,10 +3231,67 @@ async function runActivity(args, config) {
|
|
|
2942
3231
|
imageUrl: shared?.imageUrl || null
|
|
2943
3232
|
});
|
|
2944
3233
|
}
|
|
3234
|
+
async function runClaudeSystem(args) {
|
|
3235
|
+
const summary = await inspectClaudeSystem();
|
|
3236
|
+
if (args.json) {
|
|
3237
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
3238
|
+
return;
|
|
3239
|
+
}
|
|
3240
|
+
console.log("Claude System");
|
|
3241
|
+
console.log(`Path: ${summary.path}`);
|
|
3242
|
+
console.log(`Exists: ${summary.exists ? "yes" : "no"}`);
|
|
3243
|
+
if (!summary.exists) return;
|
|
3244
|
+
console.log(`Billing type: ${summary.oauthAccount?.billingType || "unknown"}`);
|
|
3245
|
+
console.log(`Extra usage enabled: ${summary.oauthAccount?.hasExtraUsageEnabled === true ? "yes" : "no"}`);
|
|
3246
|
+
console.log(`Available subscription: ${summary.hasAvailableSubscription === true ? "yes" : "no"}`);
|
|
3247
|
+
console.log(`Extra usage disabled reason: ${summary.cachedExtraUsageDisabledReason || "none"}`);
|
|
3248
|
+
console.log(`Subscription notices shown: ${formatPlainNumber(summary.subscriptionNoticeCount)}`);
|
|
3249
|
+
console.log(`Tracked tools in root file: ${formatPlainNumber(summary.toolUsageCount)}`);
|
|
3250
|
+
console.log(`Tracked skills in root file: ${formatPlainNumber(summary.skillUsageCount)}`);
|
|
3251
|
+
console.log(`Tracked projects in root file: ${formatPlainNumber(summary.projectCount)}`);
|
|
3252
|
+
}
|
|
3253
|
+
async function runClaudeLimits(args) {
|
|
3254
|
+
const diagnostics = await inspectClaudeUsage();
|
|
3255
|
+
if (args.json) {
|
|
3256
|
+
console.log(JSON.stringify(diagnostics, null, 2));
|
|
3257
|
+
return;
|
|
3258
|
+
}
|
|
3259
|
+
console.log("Claude Limits");
|
|
3260
|
+
console.log(`Claude dir: ${diagnostics.claudeDir}`);
|
|
3261
|
+
console.log(`Stats cache: ${diagnostics.statsCache.path}`);
|
|
3262
|
+
console.log(
|
|
3263
|
+
`Stats cache freshness: ${diagnostics.statsCache.exists ? `${diagnostics.statsCache.lastComputedDate || "unknown"} (${formatPlainNumber(diagnostics.statsCache.ageDays ?? void 0)} days old)` : "missing"}`
|
|
3264
|
+
);
|
|
3265
|
+
console.log(
|
|
3266
|
+
`Stats cache totals: ${formatPlainNumber(diagnostics.statsCache.totalSessions)} sessions, ${formatPlainNumber(diagnostics.statsCache.totalMessages)} messages`
|
|
3267
|
+
);
|
|
3268
|
+
console.log(
|
|
3269
|
+
`Usage-data files: ${formatPlainNumber(diagnostics.usageData.sessionMetaFiles)} session-meta, ${formatPlainNumber(diagnostics.usageData.facetFiles)} facets`
|
|
3270
|
+
);
|
|
3271
|
+
console.log(
|
|
3272
|
+
`Facet limits: ${formatPlainNumber(diagnostics.facets.rateLimitHit)} rate-limit hits, ${formatPlainNumber(diagnostics.facets.apiTokenLimit)} api-token limits, ${formatPlainNumber(diagnostics.facets.contextLimit)} context limits`
|
|
3273
|
+
);
|
|
3274
|
+
console.log(
|
|
3275
|
+
`Task-agent share: ${formatPlainNumber(diagnostics.sessionMeta.sessionsUsingTaskAgent)} / ${formatPlainNumber(diagnostics.usageData.sessionMetaFiles)} sessions (${diagnostics.sessionMeta.taskAgentShare}%)`
|
|
3276
|
+
);
|
|
3277
|
+
console.log(
|
|
3278
|
+
`Session-meta tokens: ${formatNumber2(diagnostics.sessionMeta.totalTokens)} total (${formatNumber2(diagnostics.sessionMeta.inputTokens)} in, ${formatNumber2(diagnostics.sessionMeta.outputTokens)} out)`
|
|
3279
|
+
);
|
|
3280
|
+
console.log(
|
|
3281
|
+
`Project logs: ${formatPlainNumber(diagnostics.projects.mainJsonlFiles)} main JSONL files, ${formatPlainNumber(diagnostics.projects.subagentJsonlFiles)} subagent JSONL files`
|
|
3282
|
+
);
|
|
3283
|
+
console.log(
|
|
3284
|
+
`Session indexes: ${formatPlainNumber(diagnostics.projects.sessionsIndexFiles)} files, ${formatPlainNumber(diagnostics.projects.indexedSessionEntries)} indexed entries`
|
|
3285
|
+
);
|
|
3286
|
+
}
|
|
2945
3287
|
function formatNumber2(n) {
|
|
2946
3288
|
if (n >= 1e9) return `${(n / 1e9).toFixed(1)}B`;
|
|
2947
3289
|
if (n >= 1e6) return `${(n / 1e6).toFixed(1)}M`;
|
|
2948
3290
|
if (n >= 1e3) return `${(n / 1e3).toFixed(1)}K`;
|
|
2949
3291
|
return n.toString();
|
|
2950
3292
|
}
|
|
3293
|
+
function formatPlainNumber(n) {
|
|
3294
|
+
if (typeof n !== "number") return "unknown";
|
|
3295
|
+
return n.toLocaleString("en-US");
|
|
3296
|
+
}
|
|
2951
3297
|
runMain(main);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibestats",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.8",
|
|
4
4
|
"description": "AI coding stats - usage tracking and annual wrapped for Claude Code & Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"dev": "tsx src/index.ts",
|
|
23
|
+
"build": "tsup src/index.ts --format esm --dts --clean --shims",
|
|
24
|
+
"test": "tsx --test src/**/*.test.ts",
|
|
25
|
+
"prepublishOnly": "pnpm build"
|
|
26
|
+
},
|
|
21
27
|
"keywords": [
|
|
22
28
|
"claude",
|
|
23
29
|
"claude-code",
|
|
@@ -45,10 +51,5 @@
|
|
|
45
51
|
},
|
|
46
52
|
"engines": {
|
|
47
53
|
"node": ">=18.0.0"
|
|
48
|
-
},
|
|
49
|
-
"scripts": {
|
|
50
|
-
"dev": "tsx src/index.ts",
|
|
51
|
-
"build": "tsup src/index.ts --format esm --dts --clean --shims",
|
|
52
|
-
"test": "tsx --test src/**/*.test.ts"
|
|
53
54
|
}
|
|
54
|
-
}
|
|
55
|
+
}
|