codebuddy-stats 1.1.1 → 1.1.3
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 +64 -19
- package/dist/lib/paths.js +3 -3
- package/package.json +16 -4
package/dist/index.js
CHANGED
|
@@ -40,19 +40,6 @@ Options:
|
|
|
40
40
|
}
|
|
41
41
|
return options;
|
|
42
42
|
}
|
|
43
|
-
// 生成热力图数据
|
|
44
|
-
function generateHeatmapData(dailySummary) {
|
|
45
|
-
const sortedDates = Object.keys(dailySummary).sort();
|
|
46
|
-
if (sortedDates.length === 0)
|
|
47
|
-
return { dates: [], costs: [], maxCost: 0 };
|
|
48
|
-
const costs = sortedDates.map(d => dailySummary[d]?.cost ?? 0);
|
|
49
|
-
const maxCost = Math.max(...costs);
|
|
50
|
-
return {
|
|
51
|
-
dates: sortedDates,
|
|
52
|
-
costs,
|
|
53
|
-
maxCost,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
43
|
// 获取热力图字符
|
|
57
44
|
function getHeatChar(cost, maxCost) {
|
|
58
45
|
if (cost === 0)
|
|
@@ -69,7 +56,6 @@ function getHeatChar(cost, maxCost) {
|
|
|
69
56
|
// 渲染 Overview 视图
|
|
70
57
|
function renderOverview(box, data, width, note) {
|
|
71
58
|
const { dailySummary, grandTotal, topModel, topProject, cacheHitRate, activeDays } = data;
|
|
72
|
-
const heatmap = generateHeatmapData(dailySummary);
|
|
73
59
|
// 根据宽度计算热力图周数
|
|
74
60
|
const availableWidth = width - 10;
|
|
75
61
|
const maxWeeks = Math.min(Math.floor(availableWidth / 2), 26); // 最多 26 周 (半年)
|
|
@@ -96,7 +82,43 @@ function renderOverview(box, data, width, note) {
|
|
|
96
82
|
}
|
|
97
83
|
weeks.push(week);
|
|
98
84
|
}
|
|
99
|
-
|
|
85
|
+
// 以“当前热力图窗口”的最大值做归一化(避免历史极值导致近期全是浅色)
|
|
86
|
+
const visibleCosts = [];
|
|
87
|
+
for (const week of weeks) {
|
|
88
|
+
for (const date of week) {
|
|
89
|
+
if (!date || date > todayStr)
|
|
90
|
+
continue;
|
|
91
|
+
visibleCosts.push(dailySummary[date]?.cost ?? 0);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const maxCost = Math.max(...visibleCosts, 0) || 1;
|
|
95
|
+
// 月份标尺(在列上方标注月份变化)
|
|
96
|
+
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
97
|
+
const colWidth = 2; // 每周一列:字符 + 空格
|
|
98
|
+
const heatStartCol = 4; // 左侧周几标签宽度
|
|
99
|
+
const headerLen = heatStartCol + weeks.length * colWidth;
|
|
100
|
+
const monthHeader = Array.from({ length: headerLen }, () => ' ');
|
|
101
|
+
let lastMonth = -1;
|
|
102
|
+
let lastPlacedAt = -999;
|
|
103
|
+
for (let i = 0; i < weeks.length; i++) {
|
|
104
|
+
const week = weeks[i];
|
|
105
|
+
const repDate = week.find(d => d && d <= todayStr) ?? week[0];
|
|
106
|
+
if (!repDate)
|
|
107
|
+
continue;
|
|
108
|
+
const m = new Date(repDate).getMonth();
|
|
109
|
+
if (m !== lastMonth) {
|
|
110
|
+
const label = monthNames[m];
|
|
111
|
+
const pos = heatStartCol + i * colWidth;
|
|
112
|
+
// 避免月份标签过于拥挤/相互覆盖
|
|
113
|
+
if (pos - lastPlacedAt >= 4 && pos + label.length <= monthHeader.length) {
|
|
114
|
+
for (let k = 0; k < label.length; k++)
|
|
115
|
+
monthHeader[pos + k] = label[k];
|
|
116
|
+
lastPlacedAt = pos;
|
|
117
|
+
}
|
|
118
|
+
lastMonth = m;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
content += `{gray-fg}${monthHeader.join('').trimEnd()}{/gray-fg}\n`;
|
|
100
122
|
const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
101
123
|
for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
|
|
102
124
|
let row = dayLabels[dayOfWeek].padEnd(4);
|
|
@@ -114,6 +136,8 @@ function renderOverview(box, data, width, note) {
|
|
|
114
136
|
}
|
|
115
137
|
content += row + '\n';
|
|
116
138
|
}
|
|
139
|
+
const rangeStart = weeks[0]?.[0] ?? todayStr;
|
|
140
|
+
content += `{gray-fg}Range: ${rangeStart} → ${todayStr}{/gray-fg}\n`;
|
|
117
141
|
content += ' Less {gray-fg}·░▒▓{/gray-fg}{white-fg}█{/white-fg} More\n\n';
|
|
118
142
|
// 汇总指标 - 根据宽度决定布局
|
|
119
143
|
const avgDailyCost = activeDays > 0 ? grandTotal.cost / activeDays : 0;
|
|
@@ -423,7 +447,7 @@ async function main() {
|
|
|
423
447
|
function updateContent() {
|
|
424
448
|
const width = Number(screen.width) || 80;
|
|
425
449
|
const note = currentSource === 'code'
|
|
426
|
-
? `针对 CodeBuddy Code
|
|
450
|
+
? `针对 CodeBuddy Code < 2.20.0 版本产生的数据,由于没有请求级别的 model ID,用量是基于当前 CodeBuddy Code 设置的 model ID(${data.defaultModelId})计算价格的`
|
|
427
451
|
: 'IDE 的 usage 不包含缓存命中/写入 tokens,无法计算缓存相关价格与命中率;成本按 input/output tokens 估算';
|
|
428
452
|
switch (currentTab) {
|
|
429
453
|
case 0:
|
|
@@ -444,10 +468,29 @@ async function main() {
|
|
|
444
468
|
function updateStatusBar() {
|
|
445
469
|
const daysInfo = options.days ? `Last ${options.days} days` : 'All time';
|
|
446
470
|
const sourceInfo = currentSource === 'code' ? 'Code' : 'IDE';
|
|
447
|
-
const
|
|
448
|
-
const rightContent = `v${VERSION} `;
|
|
471
|
+
const rightContent = `v${VERSION}`;
|
|
449
472
|
const width = Number(screen.width) || 80;
|
|
450
|
-
|
|
473
|
+
// 根据剩余宽度决定左侧内容详细程度(预留版本号空间)
|
|
474
|
+
const reservedForRight = rightContent.length + 2; // 版本号 + 两侧空格
|
|
475
|
+
const availableForLeft = width - reservedForRight;
|
|
476
|
+
let leftContent;
|
|
477
|
+
const fullContent = ` ${daysInfo} | Source: ${sourceInfo} | Total: ${formatCost(data.grandTotal.cost)} | q quit, Tab view, s source, r refresh`;
|
|
478
|
+
const mediumContent = ` ${daysInfo} | ${sourceInfo} | ${formatCost(data.grandTotal.cost)} | q/Tab/s/r`;
|
|
479
|
+
const shortContent = ` ${sourceInfo} | ${formatCost(data.grandTotal.cost)} | q/Tab/s/r`;
|
|
480
|
+
const minContent = ` ${formatCost(data.grandTotal.cost)}`;
|
|
481
|
+
if (fullContent.length <= availableForLeft) {
|
|
482
|
+
leftContent = fullContent;
|
|
483
|
+
}
|
|
484
|
+
else if (mediumContent.length <= availableForLeft) {
|
|
485
|
+
leftContent = mediumContent;
|
|
486
|
+
}
|
|
487
|
+
else if (shortContent.length <= availableForLeft) {
|
|
488
|
+
leftContent = shortContent;
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
leftContent = minContent;
|
|
492
|
+
}
|
|
493
|
+
const padding = Math.max(1, width - leftContent.length - rightContent.length);
|
|
451
494
|
statusBar.setContent(leftContent + ' '.repeat(padding) + rightContent);
|
|
452
495
|
}
|
|
453
496
|
// 键盘事件
|
|
@@ -517,7 +560,9 @@ async function main() {
|
|
|
517
560
|
});
|
|
518
561
|
// 监听窗口大小变化
|
|
519
562
|
screen.on('resize', () => {
|
|
563
|
+
updateTabBar();
|
|
520
564
|
updateContent();
|
|
565
|
+
updateStatusBar();
|
|
521
566
|
screen.render();
|
|
522
567
|
});
|
|
523
568
|
// 初始渲染
|
package/dist/lib/paths.js
CHANGED
|
@@ -24,7 +24,7 @@ export function getConfigDir() {
|
|
|
24
24
|
* 获取 CodeBuddy IDE (CodeBuddyExtension) 数据目录
|
|
25
25
|
* - macOS: ~/Library/Application Support/CodeBuddyExtension/Data
|
|
26
26
|
* - Windows: %APPDATA%/CodeBuddyExtension/Data
|
|
27
|
-
* - Linux: $
|
|
27
|
+
* - Linux: $XDG_DATA_HOME/CodeBuddyExtension/Data 或 ~/.local/share/CodeBuddyExtension/Data
|
|
28
28
|
*/
|
|
29
29
|
export function getIdeDataDir() {
|
|
30
30
|
if (process.platform === 'darwin') {
|
|
@@ -34,8 +34,8 @@ export function getIdeDataDir() {
|
|
|
34
34
|
const appData = process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming');
|
|
35
35
|
return path.join(appData, 'CodeBuddyExtension', 'Data');
|
|
36
36
|
}
|
|
37
|
-
const
|
|
38
|
-
return path.join(
|
|
37
|
+
const xdgDataHome = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
|
|
38
|
+
return path.join(xdgDataHome, 'CodeBuddyExtension', 'Data');
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* 获取 CodeBuddy IDE 的 workspaceStorage 目录
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebuddy-stats",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -22,10 +22,22 @@
|
|
|
22
22
|
"pretext": "npm run build",
|
|
23
23
|
"text": "node dist/index.js --no-tui"
|
|
24
24
|
},
|
|
25
|
-
"keywords": [
|
|
26
|
-
|
|
25
|
+
"keywords": [
|
|
26
|
+
"codebuddy",
|
|
27
|
+
"codebuddy-code",
|
|
28
|
+
"ai",
|
|
29
|
+
"cost",
|
|
30
|
+
"analyzer",
|
|
31
|
+
"statistics",
|
|
32
|
+
"tui",
|
|
33
|
+
"cli",
|
|
34
|
+
"token",
|
|
35
|
+
"usage"
|
|
36
|
+
],
|
|
37
|
+
"author": "AnotiaWang",
|
|
27
38
|
"license": "ISC",
|
|
28
|
-
"
|
|
39
|
+
"packageManager": "pnpm@9.15.1",
|
|
40
|
+
"description": "CodeBuddy AI cost analyzer with terminal UI",
|
|
29
41
|
"dependencies": {
|
|
30
42
|
"blessed": "^0.1.81"
|
|
31
43
|
},
|