@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.
@@ -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-B4YgU_cb.js"></script>
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">
@@ -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 accToEntry(date, acc, models) {
419
- const cost = calculateCost(acc, models);
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: cost,
428
- modelsUsed: [...models],
429
- modelBreakdowns: buildModelBreakdowns(acc, models, cost)
436
+ totalCost,
437
+ modelsUsed: modelNames,
438
+ modelBreakdowns
430
439
  };
431
440
  }
432
- function buildModelBreakdowns(acc, models, totalCost) {
433
- const modelList = [...models];
434
- if (modelList.length === 0) return [];
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: costPerModel
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 Set() });
479
+ grouped.set(key, { acc: emptyAcc(), models: /* @__PURE__ */ new Map() });
474
480
  }
475
- const entry = grouped.get(key);
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 getDailyResponse(options) {
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
- for (const [date, { acc, models: models2 }] of grouped) {
488
- daily.push(accToEntry(date, acc, models2));
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 models = /* @__PURE__ */ new Set();
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 getProjectsResponse(options) {
508
- const sessions = parseAllSessions();
513
+ function buildProjectsResponse(sessions, options) {
509
514
  const tz = options?.timezone || "Asia/Shanghai";
510
- const projects = {};
515
+ const projectGroups = /* @__PURE__ */ new Map();
511
516
  for (const session of sessions) {
512
517
  const projectName = extractProjectName(session.cwd);
513
- const dailyMap = /* @__PURE__ */ new Map();
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 Set() });
527
+ dailyMap.set(dayKey, { acc: emptyAcc(), models: /* @__PURE__ */ new Map() });
521
528
  }
522
- addAcc(dailyMap.get(dayKey).acc, ev);
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
- for (const key of Object.keys(projects)) {
531
- projects[key].sort((a, b) => a.date.localeCompare(b.date));
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 getBlocksResponse(options) {
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 = calculateCost(acc, models);
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
- const parts = dirName.replace(/^-/, "").split("-");
1186
- return parts[parts.length - 1] || dirName;
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
- const parts = dirName.replace(/^-/, "").split("-");
1698
- return parts[parts.length - 1] || dirName;
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((resolve, reject) => {
2151
+ return new Promise((resolve2, reject) => {
2052
2152
  const server = app.listen(port);
2053
2153
  const handleListening = () => {
2054
2154
  cleanup();
2055
- resolve(server);
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 = baseDir ?? (0, import_node_path8.dirname)((0, import_node_url.fileURLToPath)(__esbuild_import_meta_url));
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
- res.sendFile(popoverPath);
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