@zhangferry-dev/tokendash 1.4.0 → 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/README.md +32 -40
- package/dist/client/assets/{index-B4YgU_cb.js → index-BPWY9q0y.js} +45 -45
- package/dist/client/index.html +1 -1
- package/dist/client/popover.html +44 -17
- package/dist/electron-server.cjs +180 -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 +10 -7
- package/dist/server/codexParser.js +83 -57
- package/dist/server/index.d.ts +4 -0
- package/dist/server/index.js +25 -9
- package/electron/main.cjs +67 -12
- package/electron/preload.cjs +3 -0
- package/electron/trayBadge.cjs +3 -1
- package/electron/trayHelper +0 -0
- package/electron/trayHelper.swift +38 -16
- package/electron-builder.yml +4 -1
- package/package.json +1 -1
- package/resources/icon.icns +0 -0
- package/resources/product_menu.png +0 -0
- package/resources/cache_diagram.html +0 -456
- package/resources/cache_diagram.png +0 -0
- package/resources/pr1_preview.png +0 -0
- package/resources/test_single_agent.png +0 -0
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/client/popover.html
CHANGED
|
@@ -177,9 +177,10 @@
|
|
|
177
177
|
}
|
|
178
178
|
|
|
179
179
|
.y-axis {
|
|
180
|
-
display:
|
|
181
|
-
|
|
182
|
-
justify-
|
|
180
|
+
display: flex;
|
|
181
|
+
flex-direction: column;
|
|
182
|
+
justify-content: space-between;
|
|
183
|
+
align-items: flex-end;
|
|
183
184
|
width: max-content;
|
|
184
185
|
padding-block: 6px 24px;
|
|
185
186
|
color: var(--muted);
|
|
@@ -257,14 +258,14 @@
|
|
|
257
258
|
|
|
258
259
|
.bar-tip {
|
|
259
260
|
position: absolute;
|
|
260
|
-
inset-block-end: calc(var(--height) +
|
|
261
|
+
inset-block-end: calc(var(--height) + 4px);
|
|
261
262
|
font-size: 10px;
|
|
262
263
|
color: var(--fg);
|
|
263
264
|
font-family: var(--font-mono);
|
|
264
|
-
background:
|
|
265
|
-
border:
|
|
266
|
-
border-radius:
|
|
267
|
-
padding:
|
|
265
|
+
background: transparent;
|
|
266
|
+
border: none;
|
|
267
|
+
border-radius: 0;
|
|
268
|
+
padding: 0;
|
|
268
269
|
white-space: nowrap;
|
|
269
270
|
}
|
|
270
271
|
|
|
@@ -742,6 +743,7 @@
|
|
|
742
743
|
var totalOutput = 0;
|
|
743
744
|
var totalCacheRead = 0;
|
|
744
745
|
var totalTokens = 0;
|
|
746
|
+
var totalCost = 0;
|
|
745
747
|
|
|
746
748
|
(allDaily || []).forEach(function(daily) {
|
|
747
749
|
if (!daily || !daily.daily) return;
|
|
@@ -751,6 +753,7 @@
|
|
|
751
753
|
totalOutput += entry.outputTokens || 0;
|
|
752
754
|
totalCacheRead += entry.cacheReadTokens || 0;
|
|
753
755
|
totalTokens += entry.totalTokens || 0;
|
|
756
|
+
totalCost += entry.totalCost || 0;
|
|
754
757
|
});
|
|
755
758
|
|
|
756
759
|
var denominator = totalInput + totalCacheRead;
|
|
@@ -760,6 +763,14 @@
|
|
|
760
763
|
setCardValue('input', formatNumber(totalInput), totalInput === 0);
|
|
761
764
|
setCardValue('output', formatNumber(totalOutput), totalOutput === 0);
|
|
762
765
|
setCardValue('cache-rate', formatPercent(cacheRate), cacheRate === 0);
|
|
766
|
+
|
|
767
|
+
return {
|
|
768
|
+
today: todayStr,
|
|
769
|
+
agentKey: selectedAgents.slice().sort().join(','),
|
|
770
|
+
totalTokens: totalTokens,
|
|
771
|
+
totalCost: totalCost,
|
|
772
|
+
totalCacheRead: totalCacheRead
|
|
773
|
+
};
|
|
763
774
|
}
|
|
764
775
|
|
|
765
776
|
function setCardValue(id, text, isEmpty) {
|
|
@@ -809,22 +820,34 @@
|
|
|
809
820
|
return;
|
|
810
821
|
}
|
|
811
822
|
|
|
823
|
+
var axisInfo = niceStep(maxValue);
|
|
812
824
|
renderAxis(maxValue);
|
|
813
825
|
var maxEntry = selected.reduce(function(max, entry) {
|
|
814
826
|
return !max || entry.value > max.value ? entry : max;
|
|
815
827
|
}, null);
|
|
816
|
-
renderBars(selected,
|
|
828
|
+
renderBars(selected, axisInfo.top, maxEntry);
|
|
817
829
|
}
|
|
818
830
|
|
|
819
|
-
function
|
|
831
|
+
function niceStep(value) {
|
|
832
|
+
// Always divide into 3 intervals, pick a step that gives round numbers
|
|
833
|
+
if (value <= 0) return { top: 3, step: 1 };
|
|
834
|
+
var rawStep = value / 3;
|
|
835
|
+
var exp = Math.floor(Math.log10(rawStep));
|
|
836
|
+
var base = Math.pow(10, exp);
|
|
837
|
+
var frac = rawStep / base;
|
|
838
|
+
// Round step up to nearest 1, 2, or 5 × 10^n
|
|
839
|
+
var nice = frac <= 1 ? base : frac <= 2 ? 2 * base : frac <= 5 ? 5 * base : 10 * base;
|
|
840
|
+
var top = nice * 3;
|
|
841
|
+
return { top: top, step: nice };
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
function renderAxis(axisMax) {
|
|
820
845
|
var axis = document.getElementById('y-axis');
|
|
821
|
-
var
|
|
822
|
-
var midHigh = top * (2 / 3);
|
|
823
|
-
var midLow = top * (1 / 3);
|
|
846
|
+
var info = niceStep(axisMax);
|
|
824
847
|
axis.innerHTML = [
|
|
825
|
-
'<span>' + formatNumber(top) + '</span>',
|
|
826
|
-
'<span>' + formatNumber(
|
|
827
|
-
'<span>' + formatNumber(
|
|
848
|
+
'<span>' + formatNumber(info.top) + '</span>',
|
|
849
|
+
'<span>' + formatNumber(info.top - info.step) + '</span>',
|
|
850
|
+
'<span>' + formatNumber(info.top - 2 * info.step) + '</span>',
|
|
828
851
|
'<span>0</span>'
|
|
829
852
|
].join('');
|
|
830
853
|
}
|
|
@@ -969,6 +992,7 @@
|
|
|
969
992
|
selectedAgents = agents.slice(); // default: all selected
|
|
970
993
|
}
|
|
971
994
|
renderAgentDropdown();
|
|
995
|
+
saveSelectedAgents();
|
|
972
996
|
}
|
|
973
997
|
|
|
974
998
|
// --- Data fetching ---
|
|
@@ -1003,8 +1027,11 @@
|
|
|
1003
1027
|
});
|
|
1004
1028
|
})
|
|
1005
1029
|
.then(function(data) {
|
|
1006
|
-
renderMetrics(data.dailyResults);
|
|
1030
|
+
var traySnapshot = renderMetrics(data.dailyResults);
|
|
1007
1031
|
renderChart(data.blockResults);
|
|
1032
|
+
if (window.electronAPI && window.electronAPI.updateTraySnapshot && traySnapshot.totalTokens > 0) {
|
|
1033
|
+
window.electronAPI.updateTraySnapshot(traySnapshot).catch(function() {});
|
|
1034
|
+
}
|
|
1008
1035
|
hideError();
|
|
1009
1036
|
})
|
|
1010
1037
|
.catch(function(error) {
|
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);
|
|
@@ -260,6 +261,15 @@ var TokenCountPayloadSchema = import_zod2.z.object({
|
|
|
260
261
|
type: import_zod2.z.literal("token_count"),
|
|
261
262
|
info: TokenCountInfoSchema
|
|
262
263
|
});
|
|
264
|
+
function tokenUsageKey(usage) {
|
|
265
|
+
return [
|
|
266
|
+
usage.input_tokens,
|
|
267
|
+
usage.cached_input_tokens,
|
|
268
|
+
usage.output_tokens,
|
|
269
|
+
usage.reasoning_output_tokens,
|
|
270
|
+
usage.total_tokens
|
|
271
|
+
].join(":");
|
|
272
|
+
}
|
|
263
273
|
function getSessionsDir() {
|
|
264
274
|
return (0, import_node_path2.join)((0, import_node_os2.homedir)(), ".codex", "sessions");
|
|
265
275
|
}
|
|
@@ -304,6 +314,7 @@ function parseCodexSession(filepath) {
|
|
|
304
314
|
let model = "";
|
|
305
315
|
let createdAt = "";
|
|
306
316
|
const tokenEvents = [];
|
|
317
|
+
const seenTotalUsageSnapshots = /* @__PURE__ */ new Set();
|
|
307
318
|
for (const line of lines) {
|
|
308
319
|
const trimmed = line.trim();
|
|
309
320
|
if (!trimmed) continue;
|
|
@@ -337,6 +348,9 @@ function parseCodexSession(filepath) {
|
|
|
337
348
|
}
|
|
338
349
|
const info = parseResult.data.info;
|
|
339
350
|
if (!info) continue;
|
|
351
|
+
const totalUsageKey = tokenUsageKey(info.total_token_usage);
|
|
352
|
+
if (seenTotalUsageSnapshots.has(totalUsageKey)) continue;
|
|
353
|
+
seenTotalUsageSnapshots.add(totalUsageKey);
|
|
340
354
|
const last = info.last_token_usage;
|
|
341
355
|
tokenEvents.push({
|
|
342
356
|
timestamp,
|
|
@@ -402,8 +416,16 @@ function mergeAcc(a, b) {
|
|
|
402
416
|
a.reasoningOutputTokens += b.reasoningOutputTokens;
|
|
403
417
|
a.totalTokens += b.totalTokens;
|
|
404
418
|
}
|
|
405
|
-
function
|
|
406
|
-
|
|
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);
|
|
407
429
|
return {
|
|
408
430
|
date,
|
|
409
431
|
inputTokens: acc.inputTokens,
|
|
@@ -411,22 +433,19 @@ function accToEntry(date, acc, models) {
|
|
|
411
433
|
cacheCreationTokens: 0,
|
|
412
434
|
cacheReadTokens: acc.cachedInputTokens,
|
|
413
435
|
totalTokens: acc.totalTokens,
|
|
414
|
-
totalCost
|
|
415
|
-
modelsUsed:
|
|
416
|
-
modelBreakdowns
|
|
436
|
+
totalCost,
|
|
437
|
+
modelsUsed: modelNames,
|
|
438
|
+
modelBreakdowns
|
|
417
439
|
};
|
|
418
440
|
}
|
|
419
|
-
function buildModelBreakdowns(
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
const costPerModel = totalCost / modelList.length;
|
|
423
|
-
return modelList.map((name) => ({
|
|
424
|
-
modelName: name,
|
|
441
|
+
function buildModelBreakdowns(modelAccs) {
|
|
442
|
+
return [...modelAccs.entries()].map(([modelName, acc]) => ({
|
|
443
|
+
modelName,
|
|
425
444
|
inputTokens: acc.inputTokens,
|
|
426
445
|
outputTokens: acc.outputTokens,
|
|
427
446
|
cacheCreationTokens: 0,
|
|
428
447
|
cacheReadTokens: acc.cachedInputTokens,
|
|
429
|
-
cost:
|
|
448
|
+
cost: calculateCost(acc, /* @__PURE__ */ new Set([modelName]))
|
|
430
449
|
}));
|
|
431
450
|
}
|
|
432
451
|
function groupSessions(sessions, options) {
|
|
@@ -457,28 +476,28 @@ function groupSessions(sessions, options) {
|
|
|
457
476
|
break;
|
|
458
477
|
}
|
|
459
478
|
if (!grouped.has(key)) {
|
|
460
|
-
grouped.set(key, { acc: emptyAcc(), models: /* @__PURE__ */ new
|
|
479
|
+
grouped.set(key, { acc: emptyAcc(), models: /* @__PURE__ */ new Map() });
|
|
461
480
|
}
|
|
462
|
-
|
|
463
|
-
addAcc(entry.acc, ev);
|
|
464
|
-
if (session.model) entry.models.add(session.model);
|
|
481
|
+
addAccToBucket(grouped.get(key), ev, session.model);
|
|
465
482
|
}
|
|
466
483
|
}
|
|
467
484
|
return grouped;
|
|
468
485
|
}
|
|
469
|
-
function
|
|
470
|
-
const sessions = parseAllSessions();
|
|
486
|
+
function buildDailyResponse(sessions, options) {
|
|
471
487
|
const grouped = groupSessions(sessions, { groupBy: "day", ...options });
|
|
472
488
|
const daily = [];
|
|
473
489
|
const totalsAcc = emptyAcc();
|
|
474
|
-
|
|
475
|
-
|
|
490
|
+
const totalModels = /* @__PURE__ */ new Map();
|
|
491
|
+
for (const [date, { acc, models }] of grouped) {
|
|
492
|
+
daily.push(accToEntry(date, acc, models));
|
|
476
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
|
+
}
|
|
477
498
|
}
|
|
478
499
|
daily.sort((a, b) => a.date.localeCompare(b.date));
|
|
479
|
-
const
|
|
480
|
-
for (const s of sessions) if (s.model) models.add(s.model);
|
|
481
|
-
const totalCost = calculateCost(totalsAcc, models);
|
|
500
|
+
const totalCost = buildModelBreakdowns(totalModels).reduce((sum, model) => sum + model.cost, 0);
|
|
482
501
|
return {
|
|
483
502
|
daily,
|
|
484
503
|
totals: {
|
|
@@ -491,41 +510,37 @@ function getDailyResponse(options) {
|
|
|
491
510
|
}
|
|
492
511
|
};
|
|
493
512
|
}
|
|
494
|
-
function
|
|
495
|
-
const sessions = parseAllSessions();
|
|
513
|
+
function buildProjectsResponse(sessions, options) {
|
|
496
514
|
const tz = options?.timezone || "Asia/Shanghai";
|
|
497
|
-
const
|
|
515
|
+
const projectGroups = /* @__PURE__ */ new Map();
|
|
498
516
|
for (const session of sessions) {
|
|
499
517
|
const projectName = extractProjectName(session.cwd);
|
|
500
|
-
|
|
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);
|
|
501
521
|
for (const ev of session.tokenEvents) {
|
|
502
522
|
const evDate = new Date(ev.timestamp);
|
|
503
523
|
if (options?.since && evDate < options.since) continue;
|
|
504
524
|
if (options?.until && evDate > options.until) continue;
|
|
505
525
|
const dayKey = getDateKey(ev.timestamp, tz);
|
|
506
526
|
if (!dailyMap.has(dayKey)) {
|
|
507
|
-
dailyMap.set(dayKey, { acc: emptyAcc(), models: /* @__PURE__ */ new
|
|
527
|
+
dailyMap.set(dayKey, { acc: emptyAcc(), models: /* @__PURE__ */ new Map() });
|
|
508
528
|
}
|
|
509
|
-
|
|
510
|
-
if (session.model) dailyMap.get(dayKey).models.add(session.model);
|
|
511
|
-
}
|
|
512
|
-
if (!projects[projectName]) projects[projectName] = [];
|
|
513
|
-
for (const [date, { acc, models }] of dailyMap) {
|
|
514
|
-
projects[projectName].push(accToEntry(date, acc, models));
|
|
529
|
+
addAccToBucket(dailyMap.get(dayKey), ev, session.model);
|
|
515
530
|
}
|
|
516
531
|
}
|
|
517
|
-
|
|
518
|
-
|
|
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));
|
|
519
535
|
}
|
|
520
536
|
return { projects };
|
|
521
537
|
}
|
|
522
|
-
function
|
|
523
|
-
const sessions = parseAllSessions();
|
|
538
|
+
function buildBlocksResponse(sessions, options) {
|
|
524
539
|
const grouped = groupSessions(sessions, { groupBy: "hour", ...options });
|
|
525
540
|
const blocks = [];
|
|
526
541
|
let idx = 0;
|
|
527
542
|
for (const [hourKey, { acc, models }] of grouped) {
|
|
528
|
-
const cost =
|
|
543
|
+
const cost = buildModelBreakdowns(models).reduce((sum, model) => sum + model.cost, 0);
|
|
529
544
|
const [datePart, timePart] = hourKey.split(" ");
|
|
530
545
|
const hour = timePart.split(":")[0];
|
|
531
546
|
blocks.push({
|
|
@@ -544,13 +559,22 @@ function getBlocksResponse(options) {
|
|
|
544
559
|
},
|
|
545
560
|
totalTokens: acc.totalTokens,
|
|
546
561
|
costUSD: cost,
|
|
547
|
-
models: [...models]
|
|
562
|
+
models: [...models.keys()]
|
|
548
563
|
});
|
|
549
564
|
idx++;
|
|
550
565
|
}
|
|
551
566
|
blocks.sort((a, b) => a.startTime.localeCompare(b.startTime));
|
|
552
567
|
return { blocks };
|
|
553
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
|
+
}
|
|
554
578
|
|
|
555
579
|
// src/server/openclawParser.ts
|
|
556
580
|
var import_node_fs3 = require("node:fs");
|
|
@@ -1168,9 +1192,46 @@ function calculateCost2(inputTokens, cacheReadTokens, outputTokens, model) {
|
|
|
1168
1192
|
}
|
|
1169
1193
|
var CLAUDE_PROJECTS_DIR = (0, import_node_path5.join)((0, import_node_os5.homedir)(), ".claude", "projects");
|
|
1170
1194
|
var fileCache = /* @__PURE__ */ new Map();
|
|
1195
|
+
var projectNameCache = /* @__PURE__ */ new Map();
|
|
1171
1196
|
function extractProjectName2(dirName) {
|
|
1172
|
-
|
|
1173
|
-
|
|
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;
|
|
1174
1235
|
}
|
|
1175
1236
|
function matchesProject(dirName, filter) {
|
|
1176
1237
|
return extractProjectName2(dirName) === extractProjectName2(filter);
|
|
@@ -1345,7 +1406,7 @@ function getProjectsResponse4(tz = DEFAULT_TZ) {
|
|
|
1345
1406
|
const projectMap = /* @__PURE__ */ new Map();
|
|
1346
1407
|
for (const e of entries) {
|
|
1347
1408
|
const date = getDateKey4(e.timestamp, tz);
|
|
1348
|
-
const projectName = e.projectDir;
|
|
1409
|
+
const projectName = extractProjectName2(e.projectDir);
|
|
1349
1410
|
if (!projectMap.has(projectName)) {
|
|
1350
1411
|
projectMap.set(projectName, /* @__PURE__ */ new Map());
|
|
1351
1412
|
}
|
|
@@ -1680,9 +1741,46 @@ function countLines(text) {
|
|
|
1680
1741
|
return text.split("\n").length;
|
|
1681
1742
|
}
|
|
1682
1743
|
var CLAUDE_PROJECTS_DIR2 = (0, import_node_path6.join)((0, import_node_os6.homedir)(), ".claude", "projects");
|
|
1744
|
+
var projectNameCache2 = /* @__PURE__ */ new Map();
|
|
1683
1745
|
function extractProjectName3(dirName) {
|
|
1684
|
-
|
|
1685
|
-
|
|
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;
|
|
1686
1784
|
}
|
|
1687
1785
|
function matchesProject2(dirName, filter) {
|
|
1688
1786
|
return extractProjectName3(dirName) === extractProjectName3(filter);
|
|
@@ -1867,6 +1965,21 @@ function computeAnalytics(toolCalls, timezone = "Asia/Shanghai") {
|
|
|
1867
1965
|
toolCallTrend.push(entry);
|
|
1868
1966
|
}
|
|
1869
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
|
+
}
|
|
1870
1983
|
return { codeChangeTrend, toolUsageDistribution, productivityKPIs, toolCallTrend };
|
|
1871
1984
|
}
|
|
1872
1985
|
|
|
@@ -2035,11 +2148,11 @@ function resolvePort(value) {
|
|
|
2035
2148
|
return Number.isInteger(value) && value && value > 0 ? value : 3456;
|
|
2036
2149
|
}
|
|
2037
2150
|
function listen(app, port) {
|
|
2038
|
-
return new Promise((
|
|
2151
|
+
return new Promise((resolve2, reject) => {
|
|
2039
2152
|
const server = app.listen(port);
|
|
2040
2153
|
const handleListening = () => {
|
|
2041
2154
|
cleanup();
|
|
2042
|
-
|
|
2155
|
+
resolve2(server);
|
|
2043
2156
|
};
|
|
2044
2157
|
const handleError = (error) => {
|
|
2045
2158
|
cleanup();
|
|
@@ -2068,16 +2181,29 @@ async function listenWithPortFallback(app, preferredPort) {
|
|
|
2068
2181
|
}
|
|
2069
2182
|
throw new Error(`Could not find an available port starting from ${preferredPort}`);
|
|
2070
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
|
+
}
|
|
2071
2194
|
function createApp(_port, baseDir) {
|
|
2072
2195
|
const app = (0, import_express.default)();
|
|
2073
2196
|
const router = import_express.default.Router();
|
|
2074
2197
|
registerApiRoutes(router);
|
|
2075
2198
|
app.use("/api", router);
|
|
2076
|
-
const _baseDir
|
|
2077
|
-
const isProduction = baseDir ? true : __esbuild_import_meta_url.includes("dist/");
|
|
2199
|
+
const { baseDir: _baseDir, isProduction } = resolveStaticAssetBaseDir(__esbuild_import_meta_url, baseDir);
|
|
2078
2200
|
const popoverPath = isProduction ? (0, import_node_path8.join)(_baseDir, "client", "popover.html") : (0, import_node_path8.join)(_baseDir, "..", "..", "public", "popover.html");
|
|
2079
|
-
app.get("/popover.html", (_req, res) => {
|
|
2080
|
-
|
|
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"));
|
|
2081
2207
|
});
|
|
2082
2208
|
if (isProduction) {
|
|
2083
2209
|
const clientPath = (0, import_node_path8.join)(_baseDir, "client");
|
|
@@ -2157,6 +2283,7 @@ async function main() {
|
|
|
2157
2283
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2158
2284
|
0 && (module.exports = {
|
|
2159
2285
|
createApp,
|
|
2160
|
-
main
|
|
2286
|
+
main,
|
|
2287
|
+
resolveStaticAssetBaseDir
|
|
2161
2288
|
});
|
|
2162
2289
|
//# sourceMappingURL=electron-server.cjs.map
|