@zhangferry-dev/tokendash 1.4.2 → 1.6.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.
@@ -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);
@@ -254,20 +255,24 @@ var TokenUsageSchema = import_zod2.z.object({
254
255
  }).default({ input_tokens: 0, cached_input_tokens: 0, output_tokens: 0, reasoning_output_tokens: 0, total_tokens: 0 });
255
256
  var TokenCountInfoSchema = import_zod2.z.object({
256
257
  total_token_usage: TokenUsageSchema,
257
- last_token_usage: TokenUsageSchema
258
+ last_token_usage: TokenUsageSchema.optional()
258
259
  }).nullable().default(null);
259
260
  var TokenCountPayloadSchema = import_zod2.z.object({
260
261
  type: import_zod2.z.literal("token_count"),
261
262
  info: TokenCountInfoSchema
262
263
  });
263
- function tokenUsageKey(usage) {
264
- return [
265
- usage.input_tokens,
266
- usage.cached_input_tokens,
267
- usage.output_tokens,
268
- usage.reasoning_output_tokens,
269
- usage.total_tokens
270
- ].join(":");
264
+ function subtractTokenUsage(current, previous) {
265
+ return {
266
+ timestamp: "",
267
+ inputTokens: Math.max(0, current.input_tokens - (previous?.input_tokens ?? 0)),
268
+ cachedInputTokens: Math.max(0, current.cached_input_tokens - (previous?.cached_input_tokens ?? 0)),
269
+ outputTokens: Math.max(0, current.output_tokens - (previous?.output_tokens ?? 0)),
270
+ reasoningOutputTokens: Math.max(0, current.reasoning_output_tokens - (previous?.reasoning_output_tokens ?? 0)),
271
+ totalTokens: Math.max(0, current.total_tokens - (previous?.total_tokens ?? 0))
272
+ };
273
+ }
274
+ function displayInputTokens(inputTokens, cachedInputTokens) {
275
+ return Math.max(0, inputTokens - cachedInputTokens);
271
276
  }
272
277
  function getSessionsDir() {
273
278
  return (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".codex", "sessions");
@@ -313,7 +318,9 @@ function parseCodexSession(filepath) {
313
318
  let model = "";
314
319
  let createdAt = "";
315
320
  const tokenEvents = [];
321
+ let previousTotalUsage = null;
316
322
  const seenTotalUsageSnapshots = /* @__PURE__ */ new Set();
323
+ const seenUsageEvents = /* @__PURE__ */ new Set();
317
324
  for (const line of lines) {
318
325
  const trimmed = line.trim();
319
326
  if (!trimmed) continue;
@@ -347,18 +354,40 @@ function parseCodexSession(filepath) {
347
354
  }
348
355
  const info = parseResult.data.info;
349
356
  if (!info) continue;
350
- const totalUsageKey = tokenUsageKey(info.total_token_usage);
357
+ const totalUsageKey = [
358
+ info.total_token_usage.input_tokens,
359
+ info.total_token_usage.cached_input_tokens,
360
+ info.total_token_usage.output_tokens,
361
+ info.total_token_usage.reasoning_output_tokens,
362
+ info.total_token_usage.total_tokens
363
+ ].join(":");
351
364
  if (seenTotalUsageSnapshots.has(totalUsageKey)) continue;
352
365
  seenTotalUsageSnapshots.add(totalUsageKey);
353
- const last = info.last_token_usage;
354
- tokenEvents.push({
366
+ const last = info.last_token_usage ?? info.total_token_usage;
367
+ const rawEvent = info.last_token_usage ? subtractTokenUsage(last, null) : subtractTokenUsage(last, previousTotalUsage);
368
+ previousTotalUsage = info.total_token_usage;
369
+ if (rawEvent.inputTokens === 0 && rawEvent.cachedInputTokens === 0 && rawEvent.outputTokens === 0 && rawEvent.reasoningOutputTokens === 0) {
370
+ continue;
371
+ }
372
+ const event = {
373
+ ...rawEvent,
355
374
  timestamp,
356
- inputTokens: last.input_tokens,
357
- cachedInputTokens: last.cached_input_tokens,
358
- outputTokens: last.output_tokens,
359
- reasoningOutputTokens: last.reasoning_output_tokens,
360
- totalTokens: last.total_tokens
361
- });
375
+ cachedInputTokens: Math.min(rawEvent.cachedInputTokens, rawEvent.inputTokens)
376
+ };
377
+ const eventKey = [
378
+ timestamp,
379
+ model,
380
+ event.inputTokens,
381
+ event.cachedInputTokens,
382
+ event.outputTokens,
383
+ event.reasoningOutputTokens,
384
+ event.totalTokens
385
+ ].join(":");
386
+ if (seenUsageEvents.has(eventKey)) {
387
+ continue;
388
+ }
389
+ seenUsageEvents.add(eventKey);
390
+ tokenEvents.push(event);
362
391
  }
363
392
  }
364
393
  }
@@ -408,6 +437,12 @@ function addAcc(a, ev) {
408
437
  a.reasoningOutputTokens += ev.reasoningOutputTokens;
409
438
  a.totalTokens += ev.totalTokens;
410
439
  }
440
+ function displayAcc(acc) {
441
+ return {
442
+ ...acc,
443
+ inputTokens: displayInputTokens(acc.inputTokens, acc.cachedInputTokens)
444
+ };
445
+ }
411
446
  function mergeAcc(a, b) {
412
447
  a.inputTokens += b.inputTokens;
413
448
  a.cachedInputTokens += b.cachedInputTokens;
@@ -415,32 +450,41 @@ function mergeAcc(a, b) {
415
450
  a.reasoningOutputTokens += b.reasoningOutputTokens;
416
451
  a.totalTokens += b.totalTokens;
417
452
  }
418
- function accToEntry(date, acc, models) {
419
- const cost = calculateCost(acc, models);
453
+ function addAccToBucket(bucket, ev, model) {
454
+ addAcc(bucket.acc, ev);
455
+ if (!model) return;
456
+ if (!bucket.models.has(model)) bucket.models.set(model, emptyAcc());
457
+ addAcc(bucket.models.get(model), ev);
458
+ }
459
+ function accToEntry(date, acc, modelAccs) {
460
+ const display = displayAcc(acc);
461
+ const modelNames = [...modelAccs.keys()];
462
+ const modelBreakdowns = buildModelBreakdowns(modelAccs);
463
+ const totalCost = modelBreakdowns.reduce((sum, model) => sum + model.cost, 0);
420
464
  return {
421
465
  date,
422
- inputTokens: acc.inputTokens,
423
- outputTokens: acc.outputTokens,
466
+ inputTokens: display.inputTokens,
467
+ outputTokens: display.outputTokens,
424
468
  cacheCreationTokens: 0,
425
- cacheReadTokens: acc.cachedInputTokens,
426
- totalTokens: acc.totalTokens,
427
- totalCost: cost,
428
- modelsUsed: [...models],
429
- modelBreakdowns: buildModelBreakdowns(acc, models, cost)
469
+ cacheReadTokens: display.cachedInputTokens,
470
+ totalTokens: display.totalTokens,
471
+ totalCost,
472
+ modelsUsed: modelNames,
473
+ modelBreakdowns
430
474
  };
431
475
  }
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,
438
- inputTokens: acc.inputTokens,
439
- outputTokens: acc.outputTokens,
440
- cacheCreationTokens: 0,
441
- cacheReadTokens: acc.cachedInputTokens,
442
- cost: costPerModel
443
- }));
476
+ function buildModelBreakdowns(modelAccs) {
477
+ return [...modelAccs.entries()].map(([modelName, acc]) => {
478
+ const display = displayAcc(acc);
479
+ return {
480
+ modelName,
481
+ inputTokens: display.inputTokens,
482
+ outputTokens: display.outputTokens,
483
+ cacheCreationTokens: 0,
484
+ cacheReadTokens: display.cachedInputTokens,
485
+ cost: calculateCost(acc, /* @__PURE__ */ new Set([modelName]))
486
+ };
487
+ });
444
488
  }
445
489
  function groupSessions(sessions, options) {
446
490
  const tz = options.timezone || "Asia/Shanghai";
@@ -470,32 +514,32 @@ function groupSessions(sessions, options) {
470
514
  break;
471
515
  }
472
516
  if (!grouped.has(key)) {
473
- grouped.set(key, { acc: emptyAcc(), models: /* @__PURE__ */ new Set() });
517
+ grouped.set(key, { acc: emptyAcc(), models: /* @__PURE__ */ new Map() });
474
518
  }
475
- const entry = grouped.get(key);
476
- addAcc(entry.acc, ev);
477
- if (session.model) entry.models.add(session.model);
519
+ addAccToBucket(grouped.get(key), ev, session.model);
478
520
  }
479
521
  }
480
522
  return grouped;
481
523
  }
482
- function getDailyResponse(options) {
483
- const sessions = parseAllSessions();
524
+ function buildDailyResponse(sessions, options) {
484
525
  const grouped = groupSessions(sessions, { groupBy: "day", ...options });
485
526
  const daily = [];
486
527
  const totalsAcc = emptyAcc();
487
- for (const [date, { acc, models: models2 }] of grouped) {
488
- daily.push(accToEntry(date, acc, models2));
528
+ const totalModels = /* @__PURE__ */ new Map();
529
+ for (const [date, { acc, models }] of grouped) {
530
+ daily.push(accToEntry(date, acc, models));
489
531
  mergeAcc(totalsAcc, acc);
532
+ for (const [model, modelAcc] of models) {
533
+ if (!totalModels.has(model)) totalModels.set(model, emptyAcc());
534
+ mergeAcc(totalModels.get(model), modelAcc);
535
+ }
490
536
  }
491
537
  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);
538
+ const totalCost = buildModelBreakdowns(totalModels).reduce((sum, model) => sum + model.cost, 0);
495
539
  return {
496
540
  daily,
497
541
  totals: {
498
- inputTokens: totalsAcc.inputTokens,
542
+ inputTokens: displayInputTokens(totalsAcc.inputTokens, totalsAcc.cachedInputTokens),
499
543
  outputTokens: totalsAcc.outputTokens,
500
544
  cacheCreationTokens: 0,
501
545
  cacheReadTokens: totalsAcc.cachedInputTokens,
@@ -504,41 +548,37 @@ function getDailyResponse(options) {
504
548
  }
505
549
  };
506
550
  }
507
- function getProjectsResponse(options) {
508
- const sessions = parseAllSessions();
551
+ function buildProjectsResponse(sessions, options) {
509
552
  const tz = options?.timezone || "Asia/Shanghai";
510
- const projects = {};
553
+ const projectGroups = /* @__PURE__ */ new Map();
511
554
  for (const session of sessions) {
512
555
  const projectName = extractProjectName(session.cwd);
513
- const dailyMap = /* @__PURE__ */ new Map();
556
+ if (options?.project && projectName !== options.project) continue;
557
+ if (!projectGroups.has(projectName)) projectGroups.set(projectName, /* @__PURE__ */ new Map());
558
+ const dailyMap = projectGroups.get(projectName);
514
559
  for (const ev of session.tokenEvents) {
515
560
  const evDate = new Date(ev.timestamp);
516
561
  if (options?.since && evDate < options.since) continue;
517
562
  if (options?.until && evDate > options.until) continue;
518
563
  const dayKey = getDateKey(ev.timestamp, tz);
519
564
  if (!dailyMap.has(dayKey)) {
520
- dailyMap.set(dayKey, { acc: emptyAcc(), models: /* @__PURE__ */ new Set() });
565
+ dailyMap.set(dayKey, { acc: emptyAcc(), models: /* @__PURE__ */ new Map() });
521
566
  }
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));
567
+ addAccToBucket(dailyMap.get(dayKey), ev, session.model);
528
568
  }
529
569
  }
530
- for (const key of Object.keys(projects)) {
531
- projects[key].sort((a, b) => a.date.localeCompare(b.date));
570
+ const projects = {};
571
+ for (const [projectName, dailyMap] of projectGroups) {
572
+ projects[projectName] = [...dailyMap.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([date, { acc, models }]) => accToEntry(date, acc, models));
532
573
  }
533
574
  return { projects };
534
575
  }
535
- function getBlocksResponse(options) {
536
- const sessions = parseAllSessions();
576
+ function buildBlocksResponse(sessions, options) {
537
577
  const grouped = groupSessions(sessions, { groupBy: "hour", ...options });
538
578
  const blocks = [];
539
579
  let idx = 0;
540
580
  for (const [hourKey, { acc, models }] of grouped) {
541
- const cost = calculateCost(acc, models);
581
+ const cost = buildModelBreakdowns(models).reduce((sum, model) => sum + model.cost, 0);
542
582
  const [datePart, timePart] = hourKey.split(" ");
543
583
  const hour = timePart.split(":")[0];
544
584
  blocks.push({
@@ -550,20 +590,29 @@ function getBlocksResponse(options) {
550
590
  isGap: false,
551
591
  entries: acc.totalTokens > 0 ? 1 : 0,
552
592
  tokenCounts: {
553
- inputTokens: acc.inputTokens,
593
+ inputTokens: displayInputTokens(acc.inputTokens, acc.cachedInputTokens),
554
594
  outputTokens: acc.outputTokens,
555
595
  cacheCreationInputTokens: 0,
556
596
  cacheReadInputTokens: acc.cachedInputTokens
557
597
  },
558
598
  totalTokens: acc.totalTokens,
559
599
  costUSD: cost,
560
- models: [...models]
600
+ models: [...models.keys()]
561
601
  });
562
602
  idx++;
563
603
  }
564
604
  blocks.sort((a, b) => a.startTime.localeCompare(b.startTime));
565
605
  return { blocks };
566
606
  }
607
+ function getDailyResponse(options) {
608
+ return buildDailyResponse(parseAllSessions(), options);
609
+ }
610
+ function getProjectsResponse(options) {
611
+ return buildProjectsResponse(parseAllSessions(), options);
612
+ }
613
+ function getBlocksResponse(options) {
614
+ return buildBlocksResponse(parseAllSessions(), options);
615
+ }
567
616
 
568
617
  // src/server/openclawParser.ts
569
618
  var import_node_fs3 = require("node:fs");
@@ -1154,18 +1203,18 @@ var import_node_path5 = require("node:path");
1154
1203
  var import_node_os5 = require("node:os");
1155
1204
  var MODEL_PRICING2 = {
1156
1205
  // Claude 4.6
1157
- "claude-opus-4-6": { inputPer1M: 15, cacheReadPer1M: 1.5, outputPer1M: 75 },
1158
- "claude-sonnet-4-6": { inputPer1M: 3, cacheReadPer1M: 0.3, outputPer1M: 15 },
1206
+ "claude-opus-4-6": { inputPer1M: 15, cacheCreationPer1M: 18.75, cacheReadPer1M: 1.5, outputPer1M: 75 },
1207
+ "claude-sonnet-4-6": { inputPer1M: 3, cacheCreationPer1M: 3.75, cacheReadPer1M: 0.3, outputPer1M: 15 },
1159
1208
  // Claude 4.5
1160
- "claude-sonnet-4-5-20250514": { inputPer1M: 3, cacheReadPer1M: 0.3, outputPer1M: 15 },
1161
- "claude-haiku-4-5-20251001": { inputPer1M: 0.8, cacheReadPer1M: 0.08, outputPer1M: 4 },
1209
+ "claude-sonnet-4-5-20250514": { inputPer1M: 3, cacheCreationPer1M: 3.75, cacheReadPer1M: 0.3, outputPer1M: 15 },
1210
+ "claude-haiku-4-5-20251001": { inputPer1M: 0.8, cacheCreationPer1M: 1, cacheReadPer1M: 0.08, outputPer1M: 4 },
1162
1211
  // Older Claude models
1163
- "claude-3-5-sonnet-20241022": { inputPer1M: 3, cacheReadPer1M: 0.3, outputPer1M: 15 },
1164
- "claude-3-5-haiku-20241022": { inputPer1M: 0.8, cacheReadPer1M: 0.08, outputPer1M: 4 },
1165
- "claude-3-opus-20240229": { inputPer1M: 15, cacheReadPer1M: 1.5, outputPer1M: 75 },
1166
- "claude-3-haiku-20240307": { inputPer1M: 0.25, cacheReadPer1M: 0.03, outputPer1M: 1.25 }
1212
+ "claude-3-5-sonnet-20241022": { inputPer1M: 3, cacheCreationPer1M: 3.75, cacheReadPer1M: 0.3, outputPer1M: 15 },
1213
+ "claude-3-5-haiku-20241022": { inputPer1M: 0.8, cacheCreationPer1M: 1, cacheReadPer1M: 0.08, outputPer1M: 4 },
1214
+ "claude-3-opus-20240229": { inputPer1M: 15, cacheCreationPer1M: 18.75, cacheReadPer1M: 1.5, outputPer1M: 75 },
1215
+ "claude-3-haiku-20240307": { inputPer1M: 0.25, cacheCreationPer1M: 0.3, cacheReadPer1M: 0.03, outputPer1M: 1.25 }
1167
1216
  };
1168
- var DEFAULT_PRICING2 = { inputPer1M: 3, cacheReadPer1M: 0.3, outputPer1M: 15 };
1217
+ var DEFAULT_PRICING2 = { inputPer1M: 3, cacheCreationPer1M: 3.75, cacheReadPer1M: 0.3, outputPer1M: 15 };
1169
1218
  function getPricing(model) {
1170
1219
  if (MODEL_PRICING2[model]) return MODEL_PRICING2[model];
1171
1220
  const lower = model.toLowerCase();
@@ -1174,16 +1223,55 @@ function getPricing(model) {
1174
1223
  }
1175
1224
  return DEFAULT_PRICING2;
1176
1225
  }
1177
- function calculateCost2(inputTokens, cacheReadTokens, outputTokens, model) {
1226
+ function calculateCost2(inputTokens, cacheReadTokens, outputTokens, model, cacheCreationTokens = 0) {
1178
1227
  const p = getPricing(model);
1179
- const nonCachedInput = Math.max(inputTokens - cacheReadTokens, 0);
1180
- return nonCachedInput / 1e6 * p.inputPer1M + cacheReadTokens / 1e6 * p.cacheReadPer1M + outputTokens / 1e6 * p.outputPer1M;
1228
+ return inputTokens / 1e6 * p.inputPer1M + cacheCreationTokens / 1e6 * p.cacheCreationPer1M + cacheReadTokens / 1e6 * p.cacheReadPer1M + outputTokens / 1e6 * p.outputPer1M;
1229
+ }
1230
+ function totalClaudeTokens(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens) {
1231
+ return inputTokens + outputTokens + cacheCreationTokens + cacheReadTokens;
1181
1232
  }
1182
1233
  var CLAUDE_PROJECTS_DIR = (0, import_node_path5.join)((0, import_node_os5.homedir)(), ".claude", "projects");
1183
1234
  var fileCache = /* @__PURE__ */ new Map();
1235
+ var projectNameCache = /* @__PURE__ */ new Map();
1184
1236
  function extractProjectName2(dirName) {
1185
- const parts = dirName.replace(/^-/, "").split("-");
1186
- return parts[parts.length - 1] || dirName;
1237
+ if (!dirName.startsWith("-")) return dirName;
1238
+ const cached = projectNameCache.get(dirName);
1239
+ if (cached) return cached;
1240
+ const segments = dirName.replace(/^-/, "").split("-").filter(Boolean);
1241
+ if (segments.length === 0) {
1242
+ projectNameCache.set(dirName, dirName);
1243
+ return dirName;
1244
+ }
1245
+ if (segments.length === 1) {
1246
+ projectNameCache.set(dirName, segments[0]);
1247
+ return segments[0];
1248
+ }
1249
+ let bestName = segments[segments.length - 1];
1250
+ for (let splitAt = segments.length - 1; splitAt >= 1; splitAt--) {
1251
+ const parentSegments = segments.slice(0, splitAt);
1252
+ const candidateName = segments.slice(splitAt).join("-");
1253
+ let parentPath = "/";
1254
+ let valid = true;
1255
+ for (const seg of parentSegments) {
1256
+ const regular = (0, import_node_path5.join)(parentPath, seg);
1257
+ const hidden = (0, import_node_path5.join)(parentPath, "." + seg);
1258
+ if ((0, import_node_fs5.existsSync)(regular)) {
1259
+ parentPath = regular;
1260
+ } else if ((0, import_node_fs5.existsSync)(hidden)) {
1261
+ parentPath = hidden;
1262
+ } else {
1263
+ valid = false;
1264
+ break;
1265
+ }
1266
+ }
1267
+ if (!valid) continue;
1268
+ 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))) {
1269
+ bestName = candidateName;
1270
+ break;
1271
+ }
1272
+ }
1273
+ projectNameCache.set(dirName, bestName);
1274
+ return bestName;
1187
1275
  }
1188
1276
  function matchesProject(dirName, filter) {
1189
1277
  return extractProjectName2(dirName) === extractProjectName2(filter);
@@ -1245,7 +1333,7 @@ function parseAllSessions2(project) {
1245
1333
  const outputTokens = usage.output_tokens || 0;
1246
1334
  const cacheCreationTokens = usage.cache_creation_input_tokens || 0;
1247
1335
  const cacheReadTokens = usage.cache_read_input_tokens || 0;
1248
- const totalTokens = inputTokens + outputTokens + cacheReadTokens;
1336
+ const totalTokens = totalClaudeTokens(inputTokens, outputTokens, cacheCreationTokens, cacheReadTokens);
1249
1337
  if (totalTokens === 0) continue;
1250
1338
  entries.push({
1251
1339
  timestamp: obj.timestamp,
@@ -1329,8 +1417,8 @@ function getDailyResponse4(project, tz = DEFAULT_TZ) {
1329
1417
  agg.outputTokens += e.outputTokens;
1330
1418
  agg.cacheCreationTokens += e.cacheCreationTokens;
1331
1419
  agg.cacheReadTokens += e.cacheReadTokens;
1332
- agg.totalTokens += e.inputTokens + e.outputTokens + e.cacheReadTokens;
1333
- const cost = calculateCost2(e.inputTokens, e.cacheReadTokens, e.outputTokens, e.model);
1420
+ agg.totalTokens += totalClaudeTokens(e.inputTokens, e.outputTokens, e.cacheCreationTokens, e.cacheReadTokens);
1421
+ const cost = calculateCost2(e.inputTokens, e.cacheReadTokens, e.outputTokens, e.model, e.cacheCreationTokens);
1334
1422
  agg.totalCost += cost;
1335
1423
  if (!agg.models.has(e.model)) {
1336
1424
  agg.models.set(e.model, { input: 0, output: 0, cacheCreation: 0, cacheRead: 0, cost: 0 });
@@ -1358,7 +1446,7 @@ function getProjectsResponse4(tz = DEFAULT_TZ) {
1358
1446
  const projectMap = /* @__PURE__ */ new Map();
1359
1447
  for (const e of entries) {
1360
1448
  const date = getDateKey4(e.timestamp, tz);
1361
- const projectName = e.projectDir;
1449
+ const projectName = extractProjectName2(e.projectDir);
1362
1450
  if (!projectMap.has(projectName)) {
1363
1451
  projectMap.set(projectName, /* @__PURE__ */ new Map());
1364
1452
  }
@@ -1380,8 +1468,8 @@ function getProjectsResponse4(tz = DEFAULT_TZ) {
1380
1468
  agg.outputTokens += e.outputTokens;
1381
1469
  agg.cacheCreationTokens += e.cacheCreationTokens;
1382
1470
  agg.cacheReadTokens += e.cacheReadTokens;
1383
- agg.totalTokens += e.inputTokens + e.outputTokens + e.cacheReadTokens;
1384
- const cost = calculateCost2(e.inputTokens, e.cacheReadTokens, e.outputTokens, e.model);
1471
+ agg.totalTokens += totalClaudeTokens(e.inputTokens, e.outputTokens, e.cacheCreationTokens, e.cacheReadTokens);
1472
+ const cost = calculateCost2(e.inputTokens, e.cacheReadTokens, e.outputTokens, e.model, e.cacheCreationTokens);
1385
1473
  agg.totalCost += cost;
1386
1474
  if (!agg.models.has(e.model)) {
1387
1475
  agg.models.set(e.model, { input: 0, output: 0, cacheCreation: 0, cacheRead: 0, cost: 0 });
@@ -1419,13 +1507,13 @@ function getBlocksResponse4(project, tz = DEFAULT_TZ) {
1419
1507
  bucket.outputTokens += e.outputTokens;
1420
1508
  bucket.cacheCreationTokens += e.cacheCreationTokens;
1421
1509
  bucket.cacheReadTokens += e.cacheReadTokens;
1422
- bucket.costUSD += calculateCost2(e.inputTokens, e.cacheReadTokens, e.outputTokens, e.model);
1510
+ bucket.costUSD += calculateCost2(e.inputTokens, e.cacheReadTokens, e.outputTokens, e.model, e.cacheCreationTokens);
1423
1511
  bucket.models.add(e.model);
1424
1512
  }
1425
1513
  const blocks = [];
1426
1514
  let idx = 0;
1427
1515
  for (const [hourKey, bucket] of hourMap) {
1428
- const totalTokens = bucket.inputTokens + bucket.outputTokens + bucket.cacheReadTokens;
1516
+ const totalTokens = totalClaudeTokens(bucket.inputTokens, bucket.outputTokens, bucket.cacheCreationTokens, bucket.cacheReadTokens);
1429
1517
  blocks.push({
1430
1518
  id: `claude-${idx}`,
1431
1519
  startTime: `${hourKey}:00:00`,
@@ -1693,9 +1781,46 @@ function countLines(text) {
1693
1781
  return text.split("\n").length;
1694
1782
  }
1695
1783
  var CLAUDE_PROJECTS_DIR2 = (0, import_node_path6.join)((0, import_node_os6.homedir)(), ".claude", "projects");
1784
+ var projectNameCache2 = /* @__PURE__ */ new Map();
1696
1785
  function extractProjectName3(dirName) {
1697
- const parts = dirName.replace(/^-/, "").split("-");
1698
- return parts[parts.length - 1] || dirName;
1786
+ if (!dirName.startsWith("-")) return dirName;
1787
+ const cached = projectNameCache2.get(dirName);
1788
+ if (cached) return cached;
1789
+ const segments = dirName.replace(/^-/, "").split("-").filter(Boolean);
1790
+ if (segments.length === 0) {
1791
+ projectNameCache2.set(dirName, dirName);
1792
+ return dirName;
1793
+ }
1794
+ if (segments.length === 1) {
1795
+ projectNameCache2.set(dirName, segments[0]);
1796
+ return segments[0];
1797
+ }
1798
+ let bestName = segments[segments.length - 1];
1799
+ for (let splitAt = segments.length - 1; splitAt >= 1; splitAt--) {
1800
+ const parentSegments = segments.slice(0, splitAt);
1801
+ const candidateName = segments.slice(splitAt).join("-");
1802
+ let parentPath = "/";
1803
+ let valid = true;
1804
+ for (const seg of parentSegments) {
1805
+ const regular = (0, import_node_path6.join)(parentPath, seg);
1806
+ const hidden = (0, import_node_path6.join)(parentPath, "." + seg);
1807
+ if ((0, import_node_fs6.existsSync)(regular)) {
1808
+ parentPath = regular;
1809
+ } else if ((0, import_node_fs6.existsSync)(hidden)) {
1810
+ parentPath = hidden;
1811
+ } else {
1812
+ valid = false;
1813
+ break;
1814
+ }
1815
+ }
1816
+ if (!valid) continue;
1817
+ 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))) {
1818
+ bestName = candidateName;
1819
+ break;
1820
+ }
1821
+ }
1822
+ projectNameCache2.set(dirName, bestName);
1823
+ return bestName;
1699
1824
  }
1700
1825
  function matchesProject2(dirName, filter) {
1701
1826
  return extractProjectName3(dirName) === extractProjectName3(filter);
@@ -1880,6 +2005,21 @@ function computeAnalytics(toolCalls, timezone = "Asia/Shanghai") {
1880
2005
  toolCallTrend.push(entry);
1881
2006
  }
1882
2007
  toolCallTrend.sort((a, b) => a.date.localeCompare(b.date));
2008
+ if (toolCallTrend.length > 0) {
2009
+ const allTools = /* @__PURE__ */ new Set();
2010
+ for (const entry of toolCallTrend) {
2011
+ for (const key of Object.keys(entry)) {
2012
+ if (key !== "date") allTools.add(key);
2013
+ }
2014
+ }
2015
+ for (const entry of toolCallTrend) {
2016
+ for (const tool of allTools) {
2017
+ if (entry[tool] === void 0) {
2018
+ entry[tool] = 0;
2019
+ }
2020
+ }
2021
+ }
2022
+ }
1883
2023
  return { codeChangeTrend, toolUsageDistribution, productivityKPIs, toolCallTrend };
1884
2024
  }
1885
2025
 
@@ -2048,11 +2188,11 @@ function resolvePort(value) {
2048
2188
  return Number.isInteger(value) && value && value > 0 ? value : 3456;
2049
2189
  }
2050
2190
  function listen(app, port) {
2051
- return new Promise((resolve, reject) => {
2191
+ return new Promise((resolve2, reject) => {
2052
2192
  const server = app.listen(port);
2053
2193
  const handleListening = () => {
2054
2194
  cleanup();
2055
- resolve(server);
2195
+ resolve2(server);
2056
2196
  };
2057
2197
  const handleError = (error) => {
2058
2198
  cleanup();
@@ -2081,16 +2221,29 @@ async function listenWithPortFallback(app, preferredPort) {
2081
2221
  }
2082
2222
  throw new Error(`Could not find an available port starting from ${preferredPort}`);
2083
2223
  }
2224
+ function resolveStaticAssetBaseDir(moduleUrl = __esbuild_import_meta_url, baseDir) {
2225
+ if (baseDir) return { baseDir: (0, import_node_path8.resolve)(baseDir), isProduction: true };
2226
+ const moduleDir = (0, import_node_path8.dirname)((0, import_node_url.fileURLToPath)(moduleUrl));
2227
+ const isProduction = moduleUrl.includes("/dist/");
2228
+ if (!isProduction) return { baseDir: (0, import_node_path8.resolve)(moduleDir), isProduction: false };
2229
+ if ((0, import_node_path8.basename)(moduleDir) === "server") {
2230
+ return { baseDir: (0, import_node_path8.resolve)((0, import_node_path8.dirname)(moduleDir)), isProduction: true };
2231
+ }
2232
+ return { baseDir: (0, import_node_path8.resolve)(moduleDir), isProduction: true };
2233
+ }
2084
2234
  function createApp(_port, baseDir) {
2085
2235
  const app = (0, import_express.default)();
2086
2236
  const router = import_express.default.Router();
2087
2237
  registerApiRoutes(router);
2088
2238
  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/");
2239
+ const { baseDir: _baseDir, isProduction } = resolveStaticAssetBaseDir(__esbuild_import_meta_url, baseDir);
2091
2240
  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);
2241
+ app.get("/popover.html", (_req, res, next) => {
2242
+ if (!(0, import_node_fs8.existsSync)(popoverPath)) {
2243
+ next();
2244
+ return;
2245
+ }
2246
+ res.type("html").send((0, import_node_fs8.readFileSync)(popoverPath, "utf8"));
2094
2247
  });
2095
2248
  if (isProduction) {
2096
2249
  const clientPath = (0, import_node_path8.join)(_baseDir, "client");
@@ -2170,6 +2323,7 @@ async function main() {
2170
2323
  // Annotate the CommonJS export names for ESM import in node:
2171
2324
  0 && (module.exports = {
2172
2325
  createApp,
2173
- main
2326
+ main,
2327
+ resolveStaticAssetBaseDir
2174
2328
  });
2175
2329
  //# sourceMappingURL=electron-server.cjs.map