@zhangferry-dev/tokendash 1.4.2 → 1.5.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/client/assets/{index-B4YgU_cb.js → index-BPWY9q0y.js} +45 -45
- package/dist/client/index.html +1 -1
- package/dist/electron-server.cjs +167 -53
- package/dist/electron-server.cjs.map +3 -3
- package/dist/server/analyticsParser.js +66 -2
- package/dist/server/claudeBlocksParser.js +48 -5
- package/dist/server/claudeJsonlParser.d.ts +6 -0
- package/dist/server/claudeJsonlParser.js +53 -4
- package/dist/server/codexParser.d.ts +6 -2
- package/dist/server/codexParser.js +65 -51
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +25 -9
- package/package.json +1 -1
package/dist/client/index.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>TokenDash</title>
|
|
7
7
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><text y='28' font-size='28'>⚡</text></svg>" />
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-BPWY9q0y.js"></script>
|
|
9
9
|
<link rel="stylesheet" crossorigin href="/assets/index-iYDpTV63.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body class="antialiased" style="background:#faf9f7">
|
package/dist/electron-server.cjs
CHANGED
|
@@ -32,7 +32,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
32
32
|
var index_exports = {};
|
|
33
33
|
__export(index_exports, {
|
|
34
34
|
createApp: () => createApp,
|
|
35
|
-
main: () => main
|
|
35
|
+
main: () => main,
|
|
36
|
+
resolveStaticAssetBaseDir: () => resolveStaticAssetBaseDir
|
|
36
37
|
});
|
|
37
38
|
module.exports = __toCommonJS(index_exports);
|
|
38
39
|
var import_express = __toESM(require("express"), 1);
|
|
@@ -415,8 +416,16 @@ function mergeAcc(a, b) {
|
|
|
415
416
|
a.reasoningOutputTokens += b.reasoningOutputTokens;
|
|
416
417
|
a.totalTokens += b.totalTokens;
|
|
417
418
|
}
|
|
418
|
-
function
|
|
419
|
-
|
|
419
|
+
function addAccToBucket(bucket, ev, model) {
|
|
420
|
+
addAcc(bucket.acc, ev);
|
|
421
|
+
if (!model) return;
|
|
422
|
+
if (!bucket.models.has(model)) bucket.models.set(model, emptyAcc());
|
|
423
|
+
addAcc(bucket.models.get(model), ev);
|
|
424
|
+
}
|
|
425
|
+
function accToEntry(date, acc, modelAccs) {
|
|
426
|
+
const modelNames = [...modelAccs.keys()];
|
|
427
|
+
const modelBreakdowns = buildModelBreakdowns(modelAccs);
|
|
428
|
+
const totalCost = modelBreakdowns.reduce((sum, model) => sum + model.cost, 0);
|
|
420
429
|
return {
|
|
421
430
|
date,
|
|
422
431
|
inputTokens: acc.inputTokens,
|
|
@@ -424,22 +433,19 @@ function accToEntry(date, acc, models) {
|
|
|
424
433
|
cacheCreationTokens: 0,
|
|
425
434
|
cacheReadTokens: acc.cachedInputTokens,
|
|
426
435
|
totalTokens: acc.totalTokens,
|
|
427
|
-
totalCost
|
|
428
|
-
modelsUsed:
|
|
429
|
-
modelBreakdowns
|
|
436
|
+
totalCost,
|
|
437
|
+
modelsUsed: modelNames,
|
|
438
|
+
modelBreakdowns
|
|
430
439
|
};
|
|
431
440
|
}
|
|
432
|
-
function buildModelBreakdowns(
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const costPerModel = totalCost / modelList.length;
|
|
436
|
-
return modelList.map((name) => ({
|
|
437
|
-
modelName: name,
|
|
441
|
+
function buildModelBreakdowns(modelAccs) {
|
|
442
|
+
return [...modelAccs.entries()].map(([modelName, acc]) => ({
|
|
443
|
+
modelName,
|
|
438
444
|
inputTokens: acc.inputTokens,
|
|
439
445
|
outputTokens: acc.outputTokens,
|
|
440
446
|
cacheCreationTokens: 0,
|
|
441
447
|
cacheReadTokens: acc.cachedInputTokens,
|
|
442
|
-
cost:
|
|
448
|
+
cost: calculateCost(acc, /* @__PURE__ */ new Set([modelName]))
|
|
443
449
|
}));
|
|
444
450
|
}
|
|
445
451
|
function groupSessions(sessions, options) {
|
|
@@ -470,28 +476,28 @@ function groupSessions(sessions, options) {
|
|
|
470
476
|
break;
|
|
471
477
|
}
|
|
472
478
|
if (!grouped.has(key)) {
|
|
473
|
-
grouped.set(key, { acc: emptyAcc(), models: /* @__PURE__ */ new
|
|
479
|
+
grouped.set(key, { acc: emptyAcc(), models: /* @__PURE__ */ new Map() });
|
|
474
480
|
}
|
|
475
|
-
|
|
476
|
-
addAcc(entry.acc, ev);
|
|
477
|
-
if (session.model) entry.models.add(session.model);
|
|
481
|
+
addAccToBucket(grouped.get(key), ev, session.model);
|
|
478
482
|
}
|
|
479
483
|
}
|
|
480
484
|
return grouped;
|
|
481
485
|
}
|
|
482
|
-
function
|
|
483
|
-
const sessions = parseAllSessions();
|
|
486
|
+
function buildDailyResponse(sessions, options) {
|
|
484
487
|
const grouped = groupSessions(sessions, { groupBy: "day", ...options });
|
|
485
488
|
const daily = [];
|
|
486
489
|
const totalsAcc = emptyAcc();
|
|
487
|
-
|
|
488
|
-
|
|
490
|
+
const totalModels = /* @__PURE__ */ new Map();
|
|
491
|
+
for (const [date, { acc, models }] of grouped) {
|
|
492
|
+
daily.push(accToEntry(date, acc, models));
|
|
489
493
|
mergeAcc(totalsAcc, acc);
|
|
494
|
+
for (const [model, modelAcc] of models) {
|
|
495
|
+
if (!totalModels.has(model)) totalModels.set(model, emptyAcc());
|
|
496
|
+
mergeAcc(totalModels.get(model), modelAcc);
|
|
497
|
+
}
|
|
490
498
|
}
|
|
491
499
|
daily.sort((a, b) => a.date.localeCompare(b.date));
|
|
492
|
-
const
|
|
493
|
-
for (const s of sessions) if (s.model) models.add(s.model);
|
|
494
|
-
const totalCost = calculateCost(totalsAcc, models);
|
|
500
|
+
const totalCost = buildModelBreakdowns(totalModels).reduce((sum, model) => sum + model.cost, 0);
|
|
495
501
|
return {
|
|
496
502
|
daily,
|
|
497
503
|
totals: {
|
|
@@ -504,41 +510,37 @@ function getDailyResponse(options) {
|
|
|
504
510
|
}
|
|
505
511
|
};
|
|
506
512
|
}
|
|
507
|
-
function
|
|
508
|
-
const sessions = parseAllSessions();
|
|
513
|
+
function buildProjectsResponse(sessions, options) {
|
|
509
514
|
const tz = options?.timezone || "Asia/Shanghai";
|
|
510
|
-
const
|
|
515
|
+
const projectGroups = /* @__PURE__ */ new Map();
|
|
511
516
|
for (const session of sessions) {
|
|
512
517
|
const projectName = extractProjectName(session.cwd);
|
|
513
|
-
|
|
518
|
+
if (options?.project && projectName !== options.project) continue;
|
|
519
|
+
if (!projectGroups.has(projectName)) projectGroups.set(projectName, /* @__PURE__ */ new Map());
|
|
520
|
+
const dailyMap = projectGroups.get(projectName);
|
|
514
521
|
for (const ev of session.tokenEvents) {
|
|
515
522
|
const evDate = new Date(ev.timestamp);
|
|
516
523
|
if (options?.since && evDate < options.since) continue;
|
|
517
524
|
if (options?.until && evDate > options.until) continue;
|
|
518
525
|
const dayKey = getDateKey(ev.timestamp, tz);
|
|
519
526
|
if (!dailyMap.has(dayKey)) {
|
|
520
|
-
dailyMap.set(dayKey, { acc: emptyAcc(), models: /* @__PURE__ */ new
|
|
527
|
+
dailyMap.set(dayKey, { acc: emptyAcc(), models: /* @__PURE__ */ new Map() });
|
|
521
528
|
}
|
|
522
|
-
|
|
523
|
-
if (session.model) dailyMap.get(dayKey).models.add(session.model);
|
|
524
|
-
}
|
|
525
|
-
if (!projects[projectName]) projects[projectName] = [];
|
|
526
|
-
for (const [date, { acc, models }] of dailyMap) {
|
|
527
|
-
projects[projectName].push(accToEntry(date, acc, models));
|
|
529
|
+
addAccToBucket(dailyMap.get(dayKey), ev, session.model);
|
|
528
530
|
}
|
|
529
531
|
}
|
|
530
|
-
|
|
531
|
-
|
|
532
|
+
const projects = {};
|
|
533
|
+
for (const [projectName, dailyMap] of projectGroups) {
|
|
534
|
+
projects[projectName] = [...dailyMap.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([date, { acc, models }]) => accToEntry(date, acc, models));
|
|
532
535
|
}
|
|
533
536
|
return { projects };
|
|
534
537
|
}
|
|
535
|
-
function
|
|
536
|
-
const sessions = parseAllSessions();
|
|
538
|
+
function buildBlocksResponse(sessions, options) {
|
|
537
539
|
const grouped = groupSessions(sessions, { groupBy: "hour", ...options });
|
|
538
540
|
const blocks = [];
|
|
539
541
|
let idx = 0;
|
|
540
542
|
for (const [hourKey, { acc, models }] of grouped) {
|
|
541
|
-
const cost =
|
|
543
|
+
const cost = buildModelBreakdowns(models).reduce((sum, model) => sum + model.cost, 0);
|
|
542
544
|
const [datePart, timePart] = hourKey.split(" ");
|
|
543
545
|
const hour = timePart.split(":")[0];
|
|
544
546
|
blocks.push({
|
|
@@ -557,13 +559,22 @@ function getBlocksResponse(options) {
|
|
|
557
559
|
},
|
|
558
560
|
totalTokens: acc.totalTokens,
|
|
559
561
|
costUSD: cost,
|
|
560
|
-
models: [...models]
|
|
562
|
+
models: [...models.keys()]
|
|
561
563
|
});
|
|
562
564
|
idx++;
|
|
563
565
|
}
|
|
564
566
|
blocks.sort((a, b) => a.startTime.localeCompare(b.startTime));
|
|
565
567
|
return { blocks };
|
|
566
568
|
}
|
|
569
|
+
function getDailyResponse(options) {
|
|
570
|
+
return buildDailyResponse(parseAllSessions(), options);
|
|
571
|
+
}
|
|
572
|
+
function getProjectsResponse(options) {
|
|
573
|
+
return buildProjectsResponse(parseAllSessions(), options);
|
|
574
|
+
}
|
|
575
|
+
function getBlocksResponse(options) {
|
|
576
|
+
return buildBlocksResponse(parseAllSessions(), options);
|
|
577
|
+
}
|
|
567
578
|
|
|
568
579
|
// src/server/openclawParser.ts
|
|
569
580
|
var import_node_fs3 = require("node:fs");
|
|
@@ -1181,9 +1192,46 @@ function calculateCost2(inputTokens, cacheReadTokens, outputTokens, model) {
|
|
|
1181
1192
|
}
|
|
1182
1193
|
var CLAUDE_PROJECTS_DIR = (0, import_node_path5.join)((0, import_node_os5.homedir)(), ".claude", "projects");
|
|
1183
1194
|
var fileCache = /* @__PURE__ */ new Map();
|
|
1195
|
+
var projectNameCache = /* @__PURE__ */ new Map();
|
|
1184
1196
|
function extractProjectName2(dirName) {
|
|
1185
|
-
|
|
1186
|
-
|
|
1197
|
+
if (!dirName.startsWith("-")) return dirName;
|
|
1198
|
+
const cached = projectNameCache.get(dirName);
|
|
1199
|
+
if (cached) return cached;
|
|
1200
|
+
const segments = dirName.replace(/^-/, "").split("-").filter(Boolean);
|
|
1201
|
+
if (segments.length === 0) {
|
|
1202
|
+
projectNameCache.set(dirName, dirName);
|
|
1203
|
+
return dirName;
|
|
1204
|
+
}
|
|
1205
|
+
if (segments.length === 1) {
|
|
1206
|
+
projectNameCache.set(dirName, segments[0]);
|
|
1207
|
+
return segments[0];
|
|
1208
|
+
}
|
|
1209
|
+
let bestName = segments[segments.length - 1];
|
|
1210
|
+
for (let splitAt = segments.length - 1; splitAt >= 1; splitAt--) {
|
|
1211
|
+
const parentSegments = segments.slice(0, splitAt);
|
|
1212
|
+
const candidateName = segments.slice(splitAt).join("-");
|
|
1213
|
+
let parentPath = "/";
|
|
1214
|
+
let valid = true;
|
|
1215
|
+
for (const seg of parentSegments) {
|
|
1216
|
+
const regular = (0, import_node_path5.join)(parentPath, seg);
|
|
1217
|
+
const hidden = (0, import_node_path5.join)(parentPath, "." + seg);
|
|
1218
|
+
if ((0, import_node_fs5.existsSync)(regular)) {
|
|
1219
|
+
parentPath = regular;
|
|
1220
|
+
} else if ((0, import_node_fs5.existsSync)(hidden)) {
|
|
1221
|
+
parentPath = hidden;
|
|
1222
|
+
} else {
|
|
1223
|
+
valid = false;
|
|
1224
|
+
break;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
if (!valid) continue;
|
|
1228
|
+
if ((0, import_node_fs5.existsSync)((0, import_node_path5.join)(parentPath, candidateName)) || (0, import_node_fs5.existsSync)((0, import_node_path5.join)(parentPath, "." + candidateName))) {
|
|
1229
|
+
bestName = candidateName;
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
projectNameCache.set(dirName, bestName);
|
|
1234
|
+
return bestName;
|
|
1187
1235
|
}
|
|
1188
1236
|
function matchesProject(dirName, filter) {
|
|
1189
1237
|
return extractProjectName2(dirName) === extractProjectName2(filter);
|
|
@@ -1358,7 +1406,7 @@ function getProjectsResponse4(tz = DEFAULT_TZ) {
|
|
|
1358
1406
|
const projectMap = /* @__PURE__ */ new Map();
|
|
1359
1407
|
for (const e of entries) {
|
|
1360
1408
|
const date = getDateKey4(e.timestamp, tz);
|
|
1361
|
-
const projectName = e.projectDir;
|
|
1409
|
+
const projectName = extractProjectName2(e.projectDir);
|
|
1362
1410
|
if (!projectMap.has(projectName)) {
|
|
1363
1411
|
projectMap.set(projectName, /* @__PURE__ */ new Map());
|
|
1364
1412
|
}
|
|
@@ -1693,9 +1741,46 @@ function countLines(text) {
|
|
|
1693
1741
|
return text.split("\n").length;
|
|
1694
1742
|
}
|
|
1695
1743
|
var CLAUDE_PROJECTS_DIR2 = (0, import_node_path6.join)((0, import_node_os6.homedir)(), ".claude", "projects");
|
|
1744
|
+
var projectNameCache2 = /* @__PURE__ */ new Map();
|
|
1696
1745
|
function extractProjectName3(dirName) {
|
|
1697
|
-
|
|
1698
|
-
|
|
1746
|
+
if (!dirName.startsWith("-")) return dirName;
|
|
1747
|
+
const cached = projectNameCache2.get(dirName);
|
|
1748
|
+
if (cached) return cached;
|
|
1749
|
+
const segments = dirName.replace(/^-/, "").split("-").filter(Boolean);
|
|
1750
|
+
if (segments.length === 0) {
|
|
1751
|
+
projectNameCache2.set(dirName, dirName);
|
|
1752
|
+
return dirName;
|
|
1753
|
+
}
|
|
1754
|
+
if (segments.length === 1) {
|
|
1755
|
+
projectNameCache2.set(dirName, segments[0]);
|
|
1756
|
+
return segments[0];
|
|
1757
|
+
}
|
|
1758
|
+
let bestName = segments[segments.length - 1];
|
|
1759
|
+
for (let splitAt = segments.length - 1; splitAt >= 1; splitAt--) {
|
|
1760
|
+
const parentSegments = segments.slice(0, splitAt);
|
|
1761
|
+
const candidateName = segments.slice(splitAt).join("-");
|
|
1762
|
+
let parentPath = "/";
|
|
1763
|
+
let valid = true;
|
|
1764
|
+
for (const seg of parentSegments) {
|
|
1765
|
+
const regular = (0, import_node_path6.join)(parentPath, seg);
|
|
1766
|
+
const hidden = (0, import_node_path6.join)(parentPath, "." + seg);
|
|
1767
|
+
if ((0, import_node_fs6.existsSync)(regular)) {
|
|
1768
|
+
parentPath = regular;
|
|
1769
|
+
} else if ((0, import_node_fs6.existsSync)(hidden)) {
|
|
1770
|
+
parentPath = hidden;
|
|
1771
|
+
} else {
|
|
1772
|
+
valid = false;
|
|
1773
|
+
break;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
if (!valid) continue;
|
|
1777
|
+
if ((0, import_node_fs6.existsSync)((0, import_node_path6.join)(parentPath, candidateName)) || (0, import_node_fs6.existsSync)((0, import_node_path6.join)(parentPath, "." + candidateName))) {
|
|
1778
|
+
bestName = candidateName;
|
|
1779
|
+
break;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
projectNameCache2.set(dirName, bestName);
|
|
1783
|
+
return bestName;
|
|
1699
1784
|
}
|
|
1700
1785
|
function matchesProject2(dirName, filter) {
|
|
1701
1786
|
return extractProjectName3(dirName) === extractProjectName3(filter);
|
|
@@ -1880,6 +1965,21 @@ function computeAnalytics(toolCalls, timezone = "Asia/Shanghai") {
|
|
|
1880
1965
|
toolCallTrend.push(entry);
|
|
1881
1966
|
}
|
|
1882
1967
|
toolCallTrend.sort((a, b) => a.date.localeCompare(b.date));
|
|
1968
|
+
if (toolCallTrend.length > 0) {
|
|
1969
|
+
const allTools = /* @__PURE__ */ new Set();
|
|
1970
|
+
for (const entry of toolCallTrend) {
|
|
1971
|
+
for (const key of Object.keys(entry)) {
|
|
1972
|
+
if (key !== "date") allTools.add(key);
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
for (const entry of toolCallTrend) {
|
|
1976
|
+
for (const tool of allTools) {
|
|
1977
|
+
if (entry[tool] === void 0) {
|
|
1978
|
+
entry[tool] = 0;
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
}
|
|
1982
|
+
}
|
|
1883
1983
|
return { codeChangeTrend, toolUsageDistribution, productivityKPIs, toolCallTrend };
|
|
1884
1984
|
}
|
|
1885
1985
|
|
|
@@ -2048,11 +2148,11 @@ function resolvePort(value) {
|
|
|
2048
2148
|
return Number.isInteger(value) && value && value > 0 ? value : 3456;
|
|
2049
2149
|
}
|
|
2050
2150
|
function listen(app, port) {
|
|
2051
|
-
return new Promise((
|
|
2151
|
+
return new Promise((resolve2, reject) => {
|
|
2052
2152
|
const server = app.listen(port);
|
|
2053
2153
|
const handleListening = () => {
|
|
2054
2154
|
cleanup();
|
|
2055
|
-
|
|
2155
|
+
resolve2(server);
|
|
2056
2156
|
};
|
|
2057
2157
|
const handleError = (error) => {
|
|
2058
2158
|
cleanup();
|
|
@@ -2081,16 +2181,29 @@ async function listenWithPortFallback(app, preferredPort) {
|
|
|
2081
2181
|
}
|
|
2082
2182
|
throw new Error(`Could not find an available port starting from ${preferredPort}`);
|
|
2083
2183
|
}
|
|
2184
|
+
function resolveStaticAssetBaseDir(moduleUrl = __esbuild_import_meta_url, baseDir) {
|
|
2185
|
+
if (baseDir) return { baseDir: (0, import_node_path8.resolve)(baseDir), isProduction: true };
|
|
2186
|
+
const moduleDir = (0, import_node_path8.dirname)((0, import_node_url.fileURLToPath)(moduleUrl));
|
|
2187
|
+
const isProduction = moduleUrl.includes("/dist/");
|
|
2188
|
+
if (!isProduction) return { baseDir: (0, import_node_path8.resolve)(moduleDir), isProduction: false };
|
|
2189
|
+
if ((0, import_node_path8.basename)(moduleDir) === "server") {
|
|
2190
|
+
return { baseDir: (0, import_node_path8.resolve)((0, import_node_path8.dirname)(moduleDir)), isProduction: true };
|
|
2191
|
+
}
|
|
2192
|
+
return { baseDir: (0, import_node_path8.resolve)(moduleDir), isProduction: true };
|
|
2193
|
+
}
|
|
2084
2194
|
function createApp(_port, baseDir) {
|
|
2085
2195
|
const app = (0, import_express.default)();
|
|
2086
2196
|
const router = import_express.default.Router();
|
|
2087
2197
|
registerApiRoutes(router);
|
|
2088
2198
|
app.use("/api", router);
|
|
2089
|
-
const _baseDir
|
|
2090
|
-
const isProduction = baseDir ? true : __esbuild_import_meta_url.includes("dist/");
|
|
2199
|
+
const { baseDir: _baseDir, isProduction } = resolveStaticAssetBaseDir(__esbuild_import_meta_url, baseDir);
|
|
2091
2200
|
const popoverPath = isProduction ? (0, import_node_path8.join)(_baseDir, "client", "popover.html") : (0, import_node_path8.join)(_baseDir, "..", "..", "public", "popover.html");
|
|
2092
|
-
app.get("/popover.html", (_req, res) => {
|
|
2093
|
-
|
|
2201
|
+
app.get("/popover.html", (_req, res, next) => {
|
|
2202
|
+
if (!(0, import_node_fs8.existsSync)(popoverPath)) {
|
|
2203
|
+
next();
|
|
2204
|
+
return;
|
|
2205
|
+
}
|
|
2206
|
+
res.type("html").send((0, import_node_fs8.readFileSync)(popoverPath, "utf8"));
|
|
2094
2207
|
});
|
|
2095
2208
|
if (isProduction) {
|
|
2096
2209
|
const clientPath = (0, import_node_path8.join)(_baseDir, "client");
|
|
@@ -2170,6 +2283,7 @@ async function main() {
|
|
|
2170
2283
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2171
2284
|
0 && (module.exports = {
|
|
2172
2285
|
createApp,
|
|
2173
|
-
main
|
|
2286
|
+
main,
|
|
2287
|
+
resolveStaticAssetBaseDir
|
|
2174
2288
|
});
|
|
2175
2289
|
//# sourceMappingURL=electron-server.cjs.map
|