codebuddy-stats 1.1.5 → 1.2.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/index.js +457 -140
- package/dist/lib/workspace-resolver.js +2 -2
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -26,9 +26,9 @@ function parseArgs() {
|
|
|
26
26
|
}
|
|
27
27
|
else if (args[i] === '--help' || args[i] === '-h') {
|
|
28
28
|
console.log(`
|
|
29
|
-
CodeBuddy
|
|
29
|
+
CodeBuddy Stats
|
|
30
30
|
|
|
31
|
-
Usage:
|
|
31
|
+
Usage: codebuddy-stats [options]
|
|
32
32
|
|
|
33
33
|
Options:
|
|
34
34
|
--days <n> 只显示最近 n 天的数据
|
|
@@ -54,129 +54,210 @@ function getHeatChar(cost, maxCost) {
|
|
|
54
54
|
return '█';
|
|
55
55
|
}
|
|
56
56
|
// 渲染 Overview 视图
|
|
57
|
-
function renderOverview(box, data, width, note) {
|
|
57
|
+
function renderOverview(box, data, width, height, note) {
|
|
58
58
|
const { dailySummary, grandTotal, topModel, topProject, cacheHitRate, activeDays } = data;
|
|
59
|
-
// 根据宽度计算热力图周数
|
|
60
|
-
const availableWidth = width - 10;
|
|
61
|
-
const maxWeeks = Math.min(Math.floor(availableWidth / 2), 26); // 最多 26 周 (半年)
|
|
62
|
-
let content = '{bold}Cost Heatmap{/bold}\n\n';
|
|
63
|
-
// 生成正确的日期网格 - 从今天往前推算
|
|
64
|
-
const today = new Date();
|
|
65
|
-
const todayStr = today.toISOString().split('T')[0];
|
|
66
|
-
// 找到最近的周六作为结束点(或今天)
|
|
67
|
-
const endDate = new Date(today);
|
|
68
|
-
// 往前推 maxWeeks 周
|
|
69
|
-
const startDate = new Date(endDate);
|
|
70
|
-
startDate.setDate(startDate.getDate() - maxWeeks * 7 + 1);
|
|
71
|
-
// 调整到周日开始
|
|
72
|
-
startDate.setDate(startDate.getDate() - startDate.getDay());
|
|
73
|
-
// 构建周数组,每周从周日到周六
|
|
74
|
-
const weeks = [];
|
|
75
|
-
const currentDate = new Date(startDate);
|
|
76
|
-
while (currentDate <= endDate) {
|
|
77
|
-
const week = [];
|
|
78
|
-
for (let d = 0; d < 7; d++) {
|
|
79
|
-
const dateStr = currentDate.toISOString().split('T')[0];
|
|
80
|
-
week.push(dateStr);
|
|
81
|
-
currentDate.setDate(currentDate.getDate() + 1);
|
|
82
|
-
}
|
|
83
|
-
weeks.push(week);
|
|
84
|
-
}
|
|
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
59
|
const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
60
|
+
const dayLabels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
|
|
61
|
+
const stripTags = (s) => s.replace(/\{[^}]+\}/g, '');
|
|
62
|
+
const visibleLen = (s) => stripTags(s).length;
|
|
63
|
+
const padEndVisible = (s, target) => {
|
|
64
|
+
const pad = Math.max(0, target - visibleLen(s));
|
|
65
|
+
return s + ' '.repeat(pad);
|
|
66
|
+
};
|
|
67
|
+
const wrapGrayNoteLines = (text, maxWidth) => {
|
|
68
|
+
const prefix = '{gray-fg}';
|
|
69
|
+
const suffix = '{/gray-fg}';
|
|
70
|
+
const full = `备注:${text}`;
|
|
71
|
+
const w = Math.max(10, Math.floor(maxWidth || 10));
|
|
72
|
+
const lines = [];
|
|
73
|
+
let i = 0;
|
|
74
|
+
while (i < full.length) {
|
|
75
|
+
const chunk = full.slice(i, i + w);
|
|
76
|
+
lines.push(prefix + chunk + suffix);
|
|
77
|
+
i += w;
|
|
78
|
+
}
|
|
79
|
+
return lines;
|
|
80
|
+
};
|
|
81
|
+
const buildHeatmapLines = (heatWidth) => {
|
|
82
|
+
const safeWidth = Math.max(30, Math.floor(heatWidth || 30));
|
|
83
|
+
// 根据宽度计算热力图周数
|
|
84
|
+
const availableWidth = safeWidth - 10;
|
|
85
|
+
const maxWeeks = Math.min(Math.floor(availableWidth / 2), 26); // 最多 26 周 (半年)
|
|
86
|
+
// 生成正确的日期网格 - 从今天往前推算
|
|
87
|
+
const today = new Date();
|
|
88
|
+
const todayStr = today.toISOString().split('T')[0];
|
|
89
|
+
// 找到最近的周六作为结束点(或今天)
|
|
90
|
+
const endDate = new Date(today);
|
|
91
|
+
// 往前推 maxWeeks 周
|
|
92
|
+
const startDate = new Date(endDate);
|
|
93
|
+
startDate.setDate(startDate.getDate() - maxWeeks * 7 + 1);
|
|
94
|
+
// 调整到周一开始(getDay(): 0=Sun, 1=Mon, ..., 6=Sat)
|
|
95
|
+
const dayOfWeekStart = startDate.getDay();
|
|
96
|
+
const offsetToMonday = dayOfWeekStart === 0 ? -6 : 1 - dayOfWeekStart;
|
|
97
|
+
startDate.setDate(startDate.getDate() + offsetToMonday);
|
|
98
|
+
// 构建周数组,每周从周一到周日
|
|
99
|
+
const weeks = [];
|
|
100
|
+
const currentDate = new Date(startDate);
|
|
101
|
+
while (currentDate <= endDate) {
|
|
102
|
+
const week = [];
|
|
103
|
+
for (let d = 0; d < 7; d++) {
|
|
104
|
+
const dateStr = currentDate.toISOString().split('T')[0];
|
|
105
|
+
week.push(dateStr);
|
|
106
|
+
currentDate.setDate(currentDate.getDate() + 1);
|
|
117
107
|
}
|
|
118
|
-
|
|
108
|
+
weeks.push(week);
|
|
119
109
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const dayLabels = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
|
123
|
-
for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
|
|
124
|
-
let row = dayLabels[dayOfWeek].padEnd(4);
|
|
110
|
+
// 以“当前热力图窗口”的最大值做归一化(避免历史极值导致近期全是浅色)
|
|
111
|
+
const visibleCosts = [];
|
|
125
112
|
for (const week of weeks) {
|
|
126
|
-
const date
|
|
127
|
-
|
|
128
|
-
|
|
113
|
+
for (const date of week) {
|
|
114
|
+
if (!date || date > todayStr)
|
|
115
|
+
continue;
|
|
116
|
+
visibleCosts.push(dailySummary[date]?.cost ?? 0);
|
|
129
117
|
}
|
|
130
|
-
|
|
131
|
-
|
|
118
|
+
}
|
|
119
|
+
const maxCost = Math.max(...visibleCosts, 0) || 1;
|
|
120
|
+
// 月份标尺(在列上方标注月份变化)
|
|
121
|
+
const colWidth = 2; // 每周一列:字符 + 空格
|
|
122
|
+
const heatStartCol = 4; // 左侧周几标签宽度
|
|
123
|
+
const headerLen = heatStartCol + weeks.length * colWidth;
|
|
124
|
+
const monthHeader = Array.from({ length: headerLen }, () => ' ');
|
|
125
|
+
let lastMonth = -1;
|
|
126
|
+
let lastPlacedAt = -999;
|
|
127
|
+
for (let i = 0; i < weeks.length; i++) {
|
|
128
|
+
const week = weeks[i];
|
|
129
|
+
const repDate = week.find(d => d && d <= todayStr) ?? week[0];
|
|
130
|
+
if (!repDate)
|
|
131
|
+
continue;
|
|
132
|
+
const m = new Date(repDate).getMonth();
|
|
133
|
+
if (m !== lastMonth) {
|
|
134
|
+
const label = monthNames[m];
|
|
135
|
+
const pos = heatStartCol + i * colWidth;
|
|
136
|
+
// 避免月份标签过于拥挤/相互覆盖
|
|
137
|
+
if (pos - lastPlacedAt >= 4 && pos + label.length <= monthHeader.length) {
|
|
138
|
+
for (let k = 0; k < label.length; k++)
|
|
139
|
+
monthHeader[pos + k] = label[k];
|
|
140
|
+
lastPlacedAt = pos;
|
|
141
|
+
}
|
|
142
|
+
lastMonth = m;
|
|
132
143
|
}
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
}
|
|
145
|
+
const lines = [];
|
|
146
|
+
lines.push('{bold}Cost Heatmap{/bold}');
|
|
147
|
+
lines.push('');
|
|
148
|
+
lines.push(`{gray-fg}${monthHeader.join('').trimEnd()}{/gray-fg}`);
|
|
149
|
+
for (let dayOfWeek = 0; dayOfWeek < 7; dayOfWeek++) {
|
|
150
|
+
let row = dayLabels[dayOfWeek].padEnd(4);
|
|
151
|
+
for (const week of weeks) {
|
|
152
|
+
const date = week[dayOfWeek];
|
|
153
|
+
if (date && date <= todayStr && dailySummary[date]) {
|
|
154
|
+
row += getHeatChar(dailySummary[date].cost, maxCost) + ' ';
|
|
155
|
+
}
|
|
156
|
+
else if (date && date <= todayStr) {
|
|
157
|
+
row += '· '; // 有日期但无数据
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
row += ' '; // 未来日期
|
|
161
|
+
}
|
|
135
162
|
}
|
|
163
|
+
lines.push(row.trimEnd());
|
|
136
164
|
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
165
|
+
const rangeStart = weeks[0]?.[0] ?? todayStr;
|
|
166
|
+
lines.push(`{gray-fg}Range: ${rangeStart} → ${todayStr}{/gray-fg}`);
|
|
167
|
+
lines.push(' Less {gray-fg}·░▒▓{/gray-fg}{white-fg}█{/white-fg} More');
|
|
168
|
+
return lines;
|
|
169
|
+
};
|
|
170
|
+
const buildSummaryLines = (summaryWidth, compact) => {
|
|
171
|
+
const avgDailyCost = activeDays > 0 ? grandTotal.cost / activeDays : 0;
|
|
172
|
+
const w = Math.max(24, Math.floor(summaryWidth || 24));
|
|
173
|
+
const maxW = Math.min(w, 80);
|
|
174
|
+
const lines = [];
|
|
175
|
+
lines.push('{bold}Summary{/bold}');
|
|
176
|
+
if (!compact)
|
|
177
|
+
lines.push('─'.repeat(Math.min(Math.max(10, maxW - 2), 70)));
|
|
178
|
+
const twoCol = maxW >= 46;
|
|
179
|
+
if (twoCol) {
|
|
180
|
+
const leftLabelW = 18;
|
|
181
|
+
const rightLabelW = 18;
|
|
182
|
+
const leftValW = 12;
|
|
183
|
+
const rightValW = 8;
|
|
184
|
+
const leftPartW = leftLabelW + leftValW + 4;
|
|
185
|
+
lines.push(padEndVisible(padEndVisible('{green-fg}~Total cost:{/green-fg}', leftLabelW) + formatCost(grandTotal.cost).padStart(leftValW), leftPartW) +
|
|
186
|
+
padEndVisible(' {green-fg}Active days:{/green-fg}', rightLabelW) +
|
|
187
|
+
String(activeDays).padStart(rightValW));
|
|
188
|
+
lines.push(padEndVisible(padEndVisible('{green-fg}Total tokens:{/green-fg}', leftLabelW) +
|
|
189
|
+
formatTokens(grandTotal.tokens).padStart(leftValW), leftPartW) +
|
|
190
|
+
padEndVisible(' {green-fg}Total requests:{/green-fg}', rightLabelW) +
|
|
191
|
+
formatNumber(grandTotal.requests).padStart(rightValW));
|
|
192
|
+
lines.push(padEndVisible(padEndVisible('{green-fg}Cache hit rate:{/green-fg}', leftLabelW) +
|
|
193
|
+
formatPercent(cacheHitRate).padStart(leftValW), leftPartW) +
|
|
194
|
+
padEndVisible(' {green-fg}Avg daily cost:{/green-fg}', rightLabelW) +
|
|
195
|
+
formatCost(avgDailyCost).padStart(rightValW));
|
|
196
|
+
}
|
|
197
|
+
else {
|
|
198
|
+
lines.push(`{green-fg}~Total cost:{/green-fg} ${formatCost(grandTotal.cost)}`);
|
|
199
|
+
lines.push(`{green-fg}Total tokens:{/green-fg} ${formatTokens(grandTotal.tokens)}`);
|
|
200
|
+
lines.push(`{green-fg}Total requests:{/green-fg} ${formatNumber(grandTotal.requests)}`);
|
|
201
|
+
lines.push(`{green-fg}Active days:{/green-fg} ${activeDays}`);
|
|
202
|
+
lines.push(`{green-fg}Cache hit rate:{/green-fg} ${formatPercent(cacheHitRate)}`);
|
|
203
|
+
lines.push(`{green-fg}Avg daily cost:{/green-fg} ${formatCost(avgDailyCost)}`);
|
|
204
|
+
}
|
|
205
|
+
if (!compact)
|
|
206
|
+
lines.push('');
|
|
207
|
+
if (topModel) {
|
|
208
|
+
const label = '{cyan-fg}Top model:{/cyan-fg} ';
|
|
209
|
+
const tail = `(${formatCost(topModel.cost)})`;
|
|
210
|
+
const maxIdLen = Math.max(4, maxW - visibleLen(label) - visibleLen(tail) - 2);
|
|
211
|
+
lines.push(label + truncate(topModel.id, maxIdLen) + ' ' + tail);
|
|
212
|
+
}
|
|
213
|
+
if (topProject) {
|
|
214
|
+
const label = '{cyan-fg}Top project:{/cyan-fg} ';
|
|
215
|
+
const shortName = resolveProjectName(topProject.name, data.workspaceMappings);
|
|
216
|
+
const tail = `(${formatCost(topProject.cost)})`;
|
|
217
|
+
const maxNameLen = Math.max(4, maxW - visibleLen(label) - visibleLen(tail) - 2);
|
|
218
|
+
lines.push(label + truncate(shortName, maxNameLen) + ' ' + tail);
|
|
219
|
+
}
|
|
220
|
+
return lines;
|
|
221
|
+
};
|
|
222
|
+
const noteLines = note ? wrapGrayNoteLines(note, Math.max(20, width - 6)) : [];
|
|
223
|
+
// 尝试默认:热力图在上,Summary 在下
|
|
224
|
+
const verticalHeat = buildHeatmapLines(width);
|
|
225
|
+
const verticalSummary = buildSummaryLines(width, false);
|
|
226
|
+
const verticalLines = [...verticalHeat, '', ...verticalSummary];
|
|
227
|
+
if (noteLines.length)
|
|
228
|
+
verticalLines.push('', ...noteLines);
|
|
229
|
+
if (verticalLines.length <= height) {
|
|
230
|
+
box.setContent(verticalLines.join('\n'));
|
|
231
|
+
return;
|
|
172
232
|
}
|
|
173
|
-
|
|
174
|
-
|
|
233
|
+
// 终端偏矮:尝试把 Summary 放到右侧(需要足够宽度)
|
|
234
|
+
const gap = 6;
|
|
235
|
+
const minSummaryWidth = 34;
|
|
236
|
+
const leftWidthBudget = Math.max(30, width - minSummaryWidth - gap);
|
|
237
|
+
const leftHeat = buildHeatmapLines(leftWidthBudget);
|
|
238
|
+
const leftVisibleWidth = Math.max(...leftHeat.map(l => visibleLen(l)), 0);
|
|
239
|
+
const rightWidth = Math.max(0, width - leftVisibleWidth - gap);
|
|
240
|
+
if (rightWidth >= minSummaryWidth) {
|
|
241
|
+
const rightSummary = buildSummaryLines(rightWidth, true);
|
|
242
|
+
const rowCount = Math.max(leftHeat.length, rightSummary.length);
|
|
243
|
+
const sideLines = [];
|
|
244
|
+
for (let i = 0; i < rowCount; i++) {
|
|
245
|
+
const l = leftHeat[i] ?? '';
|
|
246
|
+
const r = rightSummary[i] ?? '';
|
|
247
|
+
sideLines.push(padEndVisible(l, leftVisibleWidth) + ' '.repeat(gap) + r);
|
|
248
|
+
}
|
|
249
|
+
if (noteLines.length)
|
|
250
|
+
sideLines.push('', ...noteLines);
|
|
251
|
+
if (sideLines.length <= height) {
|
|
252
|
+
box.setContent(sideLines.join('\n'));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
175
255
|
}
|
|
176
|
-
|
|
256
|
+
// fallback:仍然输出纵向布局(可滚动)
|
|
257
|
+
box.setContent(verticalLines.join('\n'));
|
|
177
258
|
}
|
|
178
259
|
// 渲染 By Model 视图
|
|
179
|
-
function renderByModel(box, data, width, note) {
|
|
260
|
+
function renderByModel(box, data, scrollOffset = 0, width, note, pageSize) {
|
|
180
261
|
const { modelTotals, grandTotal } = data;
|
|
181
262
|
const sorted = Object.entries(modelTotals).sort((a, b) => b[1].cost - a[1].cost);
|
|
182
263
|
// 根据宽度计算列宽
|
|
@@ -193,7 +274,9 @@ function renderByModel(box, data, width, note) {
|
|
|
193
274
|
'Tokens'.padStart(12) +
|
|
194
275
|
'Avg/Req'.padStart(10) +
|
|
195
276
|
'{/underline}\n';
|
|
196
|
-
|
|
277
|
+
const safePageSize = Math.max(1, Math.floor(pageSize || 1));
|
|
278
|
+
const visibleModels = sorted.slice(scrollOffset, scrollOffset + safePageSize);
|
|
279
|
+
for (const [modelId, stats] of visibleModels) {
|
|
197
280
|
const avgPerReq = stats.requests > 0 ? stats.cost / stats.requests : 0;
|
|
198
281
|
content +=
|
|
199
282
|
truncate(modelId, modelCol - 1).padEnd(modelCol) +
|
|
@@ -211,13 +294,16 @@ function renderByModel(box, data, width, note) {
|
|
|
211
294
|
formatNumber(grandTotal.requests).padStart(12) +
|
|
212
295
|
formatTokens(grandTotal.tokens).padStart(12) +
|
|
213
296
|
'{/bold}\n';
|
|
297
|
+
if (sorted.length > safePageSize) {
|
|
298
|
+
content += `\n{gray-fg}Showing ${scrollOffset + 1}-${Math.min(scrollOffset + safePageSize, sorted.length)} of ${sorted.length} models (↑↓ to scroll){/gray-fg}`;
|
|
299
|
+
}
|
|
214
300
|
if (note) {
|
|
215
|
-
content += `\n{gray-fg}备注:${note}{/gray-fg}\n`;
|
|
301
|
+
content += `\n\n{gray-fg}备注:${note}{/gray-fg}\n`;
|
|
216
302
|
}
|
|
217
303
|
box.setContent(content);
|
|
218
304
|
}
|
|
219
305
|
// 渲染 By Project 视图
|
|
220
|
-
function renderByProject(box, data, width, note) {
|
|
306
|
+
function renderByProject(box, data, scrollOffset = 0, width, note, pageSize) {
|
|
221
307
|
const { projectTotals, grandTotal } = data;
|
|
222
308
|
const sorted = Object.entries(projectTotals).sort((a, b) => b[1].cost - a[1].cost);
|
|
223
309
|
// 根据宽度计算列宽
|
|
@@ -233,7 +319,9 @@ function renderByProject(box, data, width, note) {
|
|
|
233
319
|
'Requests'.padStart(12) +
|
|
234
320
|
'Tokens'.padStart(12) +
|
|
235
321
|
'{/underline}\n';
|
|
236
|
-
|
|
322
|
+
const safePageSize = Math.max(1, Math.floor(pageSize || 1));
|
|
323
|
+
const visibleProjects = sorted.slice(scrollOffset, scrollOffset + safePageSize);
|
|
324
|
+
for (const [projectName, stats] of visibleProjects) {
|
|
237
325
|
// 简化项目名
|
|
238
326
|
const shortName = resolveProjectName(projectName, data.workspaceMappings);
|
|
239
327
|
content +=
|
|
@@ -251,13 +339,16 @@ function renderByProject(box, data, width, note) {
|
|
|
251
339
|
formatNumber(grandTotal.requests).padStart(12) +
|
|
252
340
|
formatTokens(grandTotal.tokens).padStart(12) +
|
|
253
341
|
'{/bold}\n';
|
|
342
|
+
if (sorted.length > safePageSize) {
|
|
343
|
+
content += `\n{gray-fg}Showing ${scrollOffset + 1}-${Math.min(scrollOffset + safePageSize, sorted.length)} of ${sorted.length} projects (↑↓ to scroll){/gray-fg}`;
|
|
344
|
+
}
|
|
254
345
|
if (note) {
|
|
255
|
-
content += `\n{gray-fg}备注:${note}{/gray-fg}\n`;
|
|
346
|
+
content += `\n\n{gray-fg}备注:${note}{/gray-fg}\n`;
|
|
256
347
|
}
|
|
257
348
|
box.setContent(content);
|
|
258
349
|
}
|
|
259
350
|
// 渲染 Daily 视图
|
|
260
|
-
function renderDaily(box, data, scrollOffset = 0, width, note) {
|
|
351
|
+
function renderDaily(box, data, scrollOffset = 0, selectedIndex = 0, width, note, pageSize) {
|
|
261
352
|
const { dailySummary, dailyData } = data;
|
|
262
353
|
const sortedDates = Object.keys(dailySummary).sort().reverse();
|
|
263
354
|
// 根据宽度计算列宽
|
|
@@ -280,8 +371,10 @@ function renderDaily(box, data, scrollOffset = 0, width, note) {
|
|
|
280
371
|
'Top Model'.padStart(modelCol) +
|
|
281
372
|
'Top Project'.padStart(projectCol) +
|
|
282
373
|
'{/underline}\n';
|
|
283
|
-
const
|
|
284
|
-
|
|
374
|
+
const safePageSize = Math.max(1, Math.floor(pageSize || 1));
|
|
375
|
+
const visibleDates = sortedDates.slice(scrollOffset, scrollOffset + safePageSize);
|
|
376
|
+
for (let i = 0; i < visibleDates.length; i++) {
|
|
377
|
+
const date = visibleDates[i];
|
|
285
378
|
const daySummary = dailySummary[date];
|
|
286
379
|
const dayData = dailyData[date];
|
|
287
380
|
if (!daySummary || !dayData)
|
|
@@ -303,27 +396,101 @@ function renderDaily(box, data, scrollOffset = 0, width, note) {
|
|
|
303
396
|
}
|
|
304
397
|
}
|
|
305
398
|
const shortProject = resolveProjectName(topProject.name, data.workspaceMappings);
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
399
|
+
const isSelected = scrollOffset + i === selectedIndex;
|
|
400
|
+
const rowContent = date.padEnd(dateCol) +
|
|
401
|
+
formatCost(daySummary.cost).padStart(costCol) +
|
|
402
|
+
formatTokens(daySummary.tokens).padStart(tokensCol) +
|
|
403
|
+
formatNumber(daySummary.requests).padStart(reqCol) +
|
|
404
|
+
truncate(topModel.id, modelCol - 1).padStart(modelCol) +
|
|
405
|
+
truncate(shortProject, projectCol - 1).padStart(projectCol);
|
|
406
|
+
if (isSelected) {
|
|
407
|
+
content += `{black-fg}{green-bg}${rowContent}{/green-bg}{/black-fg}\n`;
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
content += rowContent + '\n';
|
|
411
|
+
}
|
|
314
412
|
}
|
|
315
|
-
if (sortedDates.length >
|
|
316
|
-
content += `\n{gray-fg}Showing ${scrollOffset + 1}-${Math.min(scrollOffset +
|
|
413
|
+
if (sortedDates.length > safePageSize) {
|
|
414
|
+
content += `\n{gray-fg}Showing ${scrollOffset + 1}-${Math.min(scrollOffset + safePageSize, sortedDates.length)} of ${sortedDates.length} days (↑↓ scroll, Enter detail){/gray-fg}`;
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
content += `\n{gray-fg}(↑↓ select, Enter detail){/gray-fg}`;
|
|
317
418
|
}
|
|
318
419
|
if (note) {
|
|
319
420
|
content += `\n\n{gray-fg}备注:${note}{/gray-fg}\n`;
|
|
320
421
|
}
|
|
321
422
|
box.setContent(content);
|
|
322
423
|
}
|
|
424
|
+
// 渲染 Daily Detail 视图(某一天的详细数据)
|
|
425
|
+
function renderDailyDetail(box, data, date, scrollOffset = 0, width, pageSize) {
|
|
426
|
+
const { dailySummary, dailyData } = data;
|
|
427
|
+
const daySummary = dailySummary[date];
|
|
428
|
+
const dayData = dailyData[date];
|
|
429
|
+
if (!daySummary || !dayData) {
|
|
430
|
+
box.setContent(`{bold}${date}{/bold}\n\nNo data available for this date.`);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
// 汇总当天所有模型的用量
|
|
434
|
+
const modelStats = {};
|
|
435
|
+
for (const [, models] of Object.entries(dayData)) {
|
|
436
|
+
for (const [model, stats] of Object.entries(models)) {
|
|
437
|
+
const s = stats;
|
|
438
|
+
if (!modelStats[model]) {
|
|
439
|
+
modelStats[model] = { cost: 0, tokens: 0, requests: 0 };
|
|
440
|
+
}
|
|
441
|
+
modelStats[model].cost += Number(s.cost ?? 0);
|
|
442
|
+
modelStats[model].tokens += Number(s.totalTokens ?? 0);
|
|
443
|
+
modelStats[model].requests += Number(s.requests ?? 0);
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
const sortedModels = Object.entries(modelStats).sort((a, b) => b[1].cost - a[1].cost);
|
|
447
|
+
// 根据宽度计算列宽
|
|
448
|
+
const availableWidth = width - 6; // padding
|
|
449
|
+
const fixedCols = 12 + 12 + 12; // Cost + Requests + Tokens
|
|
450
|
+
const modelCol = Math.max(25, availableWidth - fixedCols);
|
|
451
|
+
const totalWidth = modelCol + fixedCols;
|
|
452
|
+
let content = `{bold}${date} - Model Usage Details{/bold}\n\n`;
|
|
453
|
+
// 当天汇总
|
|
454
|
+
content += `{green-fg}Total cost:{/green-fg} ${formatCost(daySummary.cost)} `;
|
|
455
|
+
content += `{green-fg}Tokens:{/green-fg} ${formatTokens(daySummary.tokens)} `;
|
|
456
|
+
content += `{green-fg}Requests:{/green-fg} ${formatNumber(daySummary.requests)}\n\n`;
|
|
457
|
+
content +=
|
|
458
|
+
'{underline}' +
|
|
459
|
+
'Model'.padEnd(modelCol) +
|
|
460
|
+
'~Cost'.padStart(12) +
|
|
461
|
+
'Requests'.padStart(12) +
|
|
462
|
+
'Tokens'.padStart(12) +
|
|
463
|
+
'{/underline}\n';
|
|
464
|
+
const safePageSize = Math.max(1, Math.floor(pageSize || 1));
|
|
465
|
+
const visibleModels = sortedModels.slice(scrollOffset, scrollOffset + safePageSize);
|
|
466
|
+
for (const [modelId, stats] of visibleModels) {
|
|
467
|
+
content +=
|
|
468
|
+
truncate(modelId, modelCol - 1).padEnd(modelCol) +
|
|
469
|
+
formatCost(stats.cost).padStart(12) +
|
|
470
|
+
formatNumber(stats.requests).padStart(12) +
|
|
471
|
+
formatTokens(stats.tokens).padStart(12) +
|
|
472
|
+
'\n';
|
|
473
|
+
}
|
|
474
|
+
content += '─'.repeat(totalWidth) + '\n';
|
|
475
|
+
content +=
|
|
476
|
+
'{bold}' +
|
|
477
|
+
`Total (${sortedModels.length} models)`.padEnd(modelCol) +
|
|
478
|
+
formatCost(daySummary.cost).padStart(12) +
|
|
479
|
+
formatNumber(daySummary.requests).padStart(12) +
|
|
480
|
+
formatTokens(daySummary.tokens).padStart(12) +
|
|
481
|
+
'{/bold}\n';
|
|
482
|
+
if (sortedModels.length > safePageSize) {
|
|
483
|
+
content += `\n{gray-fg}Showing ${scrollOffset + 1}-${Math.min(scrollOffset + safePageSize, sortedModels.length)} of ${sortedModels.length} models (↑↓ scroll, Esc back){/gray-fg}`;
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
content += `\n{gray-fg}(Esc back to Daily list){/gray-fg}`;
|
|
487
|
+
}
|
|
488
|
+
box.setContent(content);
|
|
489
|
+
}
|
|
323
490
|
// 纯文本输出模式
|
|
324
491
|
function printTextReport(data) {
|
|
325
492
|
const { modelTotals, projectTotals, grandTotal, topModel, topProject, cacheHitRate, activeDays } = data;
|
|
326
|
-
console.log('\n🤖 CodeBuddy
|
|
493
|
+
console.log('\n🤖 CodeBuddy Stats Report');
|
|
327
494
|
console.log('='.repeat(50));
|
|
328
495
|
console.log(`\nTotal cost: ${formatCost(grandTotal.cost)}`);
|
|
329
496
|
console.log(`Total tokens: ${formatTokens(grandTotal.tokens)}`);
|
|
@@ -366,14 +533,23 @@ async function main() {
|
|
|
366
533
|
// 创建 TUI
|
|
367
534
|
const screen = blessed.screen({
|
|
368
535
|
smartCSR: true,
|
|
369
|
-
title: 'CodeBuddy
|
|
536
|
+
title: 'CodeBuddy Stats',
|
|
370
537
|
forceUnicode: true,
|
|
371
538
|
fullUnicode: true,
|
|
372
539
|
});
|
|
373
540
|
// Tab 状态
|
|
374
541
|
const tabs = ['Overview', 'By Model', 'By Project', 'Daily'];
|
|
375
542
|
let currentTab = 0;
|
|
543
|
+
let modelScrollOffset = 0;
|
|
544
|
+
let projectScrollOffset = 0;
|
|
376
545
|
let dailyScrollOffset = 0;
|
|
546
|
+
let dailySelectedIndex = 0;
|
|
547
|
+
let dailyDetailDate = null; // 当前查看详情的日期,null 表示在列表视图
|
|
548
|
+
let dailyDetailScrollOffset = 0;
|
|
549
|
+
let modelPageSize = 10;
|
|
550
|
+
let projectPageSize = 10;
|
|
551
|
+
let dailyPageSize = 20;
|
|
552
|
+
let dailyDetailPageSize = 10;
|
|
377
553
|
// Tab 栏
|
|
378
554
|
const tabBar = blessed.box({
|
|
379
555
|
top: 0,
|
|
@@ -424,7 +600,7 @@ async function main() {
|
|
|
424
600
|
screen.append(statusBar);
|
|
425
601
|
// 更新 Tab 栏
|
|
426
602
|
function updateTabBar() {
|
|
427
|
-
let content = '
|
|
603
|
+
let content = ' CodeBuddy Stats ';
|
|
428
604
|
content += '{gray-fg}Source:{/gray-fg} ';
|
|
429
605
|
if (currentSource === 'code') {
|
|
430
606
|
content += '{black-fg}{green-bg} Code {/green-bg}{/black-fg} ';
|
|
@@ -452,18 +628,51 @@ async function main() {
|
|
|
452
628
|
const note = currentSource === 'code'
|
|
453
629
|
? `针对 CodeBuddy Code < 2.20.0 版本产生的数据,由于没有请求级别的 model ID,用量是基于当前 CodeBuddy Code 设置的 model ID(${data.defaultModelId})计算价格的`
|
|
454
630
|
: 'IDE 的 usage 不包含缓存命中/写入 tokens,无法计算缓存相关价格与命中率;成本按 input/output tokens 估算';
|
|
631
|
+
const screenHeight = Number(screen.height) || 24;
|
|
632
|
+
const contentBoxHeight = Math.max(1, screenHeight - 5); // 对应 contentBox: height = '100%-5'
|
|
633
|
+
const paddingTop = Number(contentBox.padding?.top ?? 0);
|
|
634
|
+
const paddingBottom = Number(contentBox.padding?.bottom ?? 0);
|
|
635
|
+
const innerHeight = Math.max(1, contentBoxHeight - paddingTop - paddingBottom);
|
|
636
|
+
// 根据当前可用高度动态调整每页行数(By Model / By Project / Daily),避免 resize 后内容溢出
|
|
637
|
+
const baseLines = 3; // title + blank + header
|
|
638
|
+
const hintLines = 2; // blank + hint line(最坏情况)
|
|
639
|
+
const availableTextWidth = Math.max(20, width - 8);
|
|
640
|
+
const estimatedNoteLines = note ? Math.max(1, Math.ceil(`备注:${note}`.length / availableTextWidth)) : 0;
|
|
641
|
+
const noteLines = note ? 2 + estimatedNoteLines : 0; // 两行空行 + 备注文本
|
|
642
|
+
// By Model / By Project:表格尾部还有 total 两行
|
|
643
|
+
const listReservedLines = baseLines + 2 + hintLines + noteLines + 1; // separator + total + safety
|
|
644
|
+
modelPageSize = Math.max(1, innerHeight - listReservedLines);
|
|
645
|
+
projectPageSize = Math.max(1, innerHeight - listReservedLines);
|
|
646
|
+
// Daily:无 total 行
|
|
647
|
+
const dailyReservedLines = baseLines + hintLines + noteLines + 1; // safety
|
|
648
|
+
dailyPageSize = Math.max(1, innerHeight - dailyReservedLines);
|
|
649
|
+
// Daily Detail:有 summary + total 行
|
|
650
|
+
const dailyDetailReservedLines = baseLines + 3 + 2 + hintLines + 1; // summary(3) + separator + total + safety
|
|
651
|
+
dailyDetailPageSize = Math.max(1, innerHeight - dailyDetailReservedLines);
|
|
652
|
+
const modelMaxOffset = Math.max(0, Object.keys(data.modelTotals).length - modelPageSize);
|
|
653
|
+
modelScrollOffset = Math.min(modelScrollOffset, modelMaxOffset);
|
|
654
|
+
const projectMaxOffset = Math.max(0, Object.keys(data.projectTotals).length - projectPageSize);
|
|
655
|
+
projectScrollOffset = Math.min(projectScrollOffset, projectMaxOffset);
|
|
656
|
+
const dailyMaxOffset = Math.max(0, Object.keys(data.dailySummary).length - dailyPageSize);
|
|
657
|
+
dailyScrollOffset = Math.min(dailyScrollOffset, dailyMaxOffset);
|
|
658
|
+
dailySelectedIndex = Math.min(dailySelectedIndex, Math.max(0, Object.keys(data.dailySummary).length - 1));
|
|
455
659
|
switch (currentTab) {
|
|
456
660
|
case 0:
|
|
457
|
-
renderOverview(contentBox, data, width, note);
|
|
661
|
+
renderOverview(contentBox, data, width, innerHeight, note);
|
|
458
662
|
break;
|
|
459
663
|
case 1:
|
|
460
|
-
renderByModel(contentBox, data, width, note);
|
|
664
|
+
renderByModel(contentBox, data, modelScrollOffset, width, note, modelPageSize);
|
|
461
665
|
break;
|
|
462
666
|
case 2:
|
|
463
|
-
renderByProject(contentBox, data, width, note);
|
|
667
|
+
renderByProject(contentBox, data, projectScrollOffset, width, note, projectPageSize);
|
|
464
668
|
break;
|
|
465
669
|
case 3:
|
|
466
|
-
|
|
670
|
+
if (dailyDetailDate) {
|
|
671
|
+
renderDailyDetail(contentBox, data, dailyDetailDate, dailyDetailScrollOffset, width, dailyDetailPageSize);
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
renderDaily(contentBox, data, dailyScrollOffset, dailySelectedIndex, width, note, dailyPageSize);
|
|
675
|
+
}
|
|
467
676
|
break;
|
|
468
677
|
}
|
|
469
678
|
}
|
|
@@ -498,30 +707,126 @@ async function main() {
|
|
|
498
707
|
}
|
|
499
708
|
// 键盘事件
|
|
500
709
|
screen.key(['tab'], () => {
|
|
710
|
+
if (dailyDetailDate)
|
|
711
|
+
return; // 在 detail 视图时禁用 tab 切换
|
|
501
712
|
currentTab = (currentTab + 1) % tabs.length;
|
|
713
|
+
modelScrollOffset = 0;
|
|
714
|
+
projectScrollOffset = 0;
|
|
502
715
|
dailyScrollOffset = 0;
|
|
716
|
+
dailySelectedIndex = 0;
|
|
717
|
+
contentBox.scrollTo(0);
|
|
503
718
|
updateTabBar();
|
|
504
719
|
updateContent();
|
|
505
720
|
screen.render();
|
|
506
721
|
});
|
|
507
722
|
screen.key(['S-tab'], () => {
|
|
723
|
+
if (dailyDetailDate)
|
|
724
|
+
return; // 在 detail 视图时禁用 tab 切换
|
|
508
725
|
currentTab = (currentTab - 1 + tabs.length) % tabs.length;
|
|
726
|
+
modelScrollOffset = 0;
|
|
727
|
+
projectScrollOffset = 0;
|
|
509
728
|
dailyScrollOffset = 0;
|
|
729
|
+
dailySelectedIndex = 0;
|
|
730
|
+
contentBox.scrollTo(0);
|
|
510
731
|
updateTabBar();
|
|
511
732
|
updateContent();
|
|
512
733
|
screen.render();
|
|
513
734
|
});
|
|
514
735
|
screen.key(['up', 'k'], () => {
|
|
736
|
+
if (currentTab === 1) {
|
|
737
|
+
modelScrollOffset = Math.max(0, modelScrollOffset - 1);
|
|
738
|
+
updateContent();
|
|
739
|
+
screen.render();
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
if (currentTab === 2) {
|
|
743
|
+
projectScrollOffset = Math.max(0, projectScrollOffset - 1);
|
|
744
|
+
updateContent();
|
|
745
|
+
screen.render();
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
515
748
|
if (currentTab === 3) {
|
|
516
|
-
|
|
749
|
+
if (dailyDetailDate) {
|
|
750
|
+
// 在 detail 视图中滚动
|
|
751
|
+
dailyDetailScrollOffset = Math.max(0, dailyDetailScrollOffset - 1);
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
// 在列表视图中移动选中项
|
|
755
|
+
if (dailySelectedIndex > 0) {
|
|
756
|
+
dailySelectedIndex--;
|
|
757
|
+
// 如果选中项在当前页之上,滚动页面
|
|
758
|
+
if (dailySelectedIndex < dailyScrollOffset) {
|
|
759
|
+
dailyScrollOffset = dailySelectedIndex;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
517
763
|
updateContent();
|
|
518
764
|
screen.render();
|
|
765
|
+
return;
|
|
519
766
|
}
|
|
767
|
+
contentBox.scroll(-1);
|
|
768
|
+
screen.render();
|
|
520
769
|
});
|
|
521
770
|
screen.key(['down', 'j'], () => {
|
|
771
|
+
if (currentTab === 1) {
|
|
772
|
+
const maxOffset = Math.max(0, Object.keys(data.modelTotals).length - modelPageSize);
|
|
773
|
+
modelScrollOffset = Math.min(maxOffset, modelScrollOffset + 1);
|
|
774
|
+
updateContent();
|
|
775
|
+
screen.render();
|
|
776
|
+
return;
|
|
777
|
+
}
|
|
778
|
+
if (currentTab === 2) {
|
|
779
|
+
const maxOffset = Math.max(0, Object.keys(data.projectTotals).length - projectPageSize);
|
|
780
|
+
projectScrollOffset = Math.min(maxOffset, projectScrollOffset + 1);
|
|
781
|
+
updateContent();
|
|
782
|
+
screen.render();
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
522
785
|
if (currentTab === 3) {
|
|
523
|
-
|
|
524
|
-
|
|
786
|
+
if (dailyDetailDate) {
|
|
787
|
+
// 在 detail 视图中滚动(需要计算当天的模型数量)
|
|
788
|
+
const dayData = data.dailyData[dailyDetailDate];
|
|
789
|
+
if (dayData) {
|
|
790
|
+
const modelCount = new Set(Object.values(dayData).flatMap(models => Object.keys(models))).size;
|
|
791
|
+
const maxOffset = Math.max(0, modelCount - dailyDetailPageSize);
|
|
792
|
+
dailyDetailScrollOffset = Math.min(maxOffset, dailyDetailScrollOffset + 1);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
else {
|
|
796
|
+
// 在列表视图中移动选中项
|
|
797
|
+
const totalDays = Object.keys(data.dailySummary).length;
|
|
798
|
+
if (dailySelectedIndex < totalDays - 1) {
|
|
799
|
+
dailySelectedIndex++;
|
|
800
|
+
// 如果选中项超出当前页,滚动页面
|
|
801
|
+
if (dailySelectedIndex >= dailyScrollOffset + dailyPageSize) {
|
|
802
|
+
dailyScrollOffset = dailySelectedIndex - dailyPageSize + 1;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
updateContent();
|
|
807
|
+
screen.render();
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
contentBox.scroll(1);
|
|
811
|
+
screen.render();
|
|
812
|
+
});
|
|
813
|
+
screen.key(['enter'], () => {
|
|
814
|
+
if (currentTab === 3 && !dailyDetailDate) {
|
|
815
|
+
// 进入 detail 视图
|
|
816
|
+
const sortedDates = Object.keys(data.dailySummary).sort().reverse();
|
|
817
|
+
if (sortedDates[dailySelectedIndex]) {
|
|
818
|
+
dailyDetailDate = sortedDates[dailySelectedIndex];
|
|
819
|
+
dailyDetailScrollOffset = 0;
|
|
820
|
+
updateContent();
|
|
821
|
+
screen.render();
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
});
|
|
825
|
+
screen.key(['escape', 'backspace'], () => {
|
|
826
|
+
if (currentTab === 3 && dailyDetailDate) {
|
|
827
|
+
// 返回列表视图
|
|
828
|
+
dailyDetailDate = null;
|
|
829
|
+
dailyDetailScrollOffset = 0;
|
|
525
830
|
updateContent();
|
|
526
831
|
screen.render();
|
|
527
832
|
}
|
|
@@ -535,7 +840,13 @@ async function main() {
|
|
|
535
840
|
screen.render();
|
|
536
841
|
try {
|
|
537
842
|
data = await loadUsageData({ days: options.days, source: currentSource });
|
|
843
|
+
modelScrollOffset = 0;
|
|
844
|
+
projectScrollOffset = 0;
|
|
538
845
|
dailyScrollOffset = 0;
|
|
846
|
+
dailySelectedIndex = 0;
|
|
847
|
+
dailyDetailDate = null;
|
|
848
|
+
dailyDetailScrollOffset = 0;
|
|
849
|
+
contentBox.scrollTo(0);
|
|
539
850
|
updateTabBar();
|
|
540
851
|
updateContent();
|
|
541
852
|
updateStatusBar();
|
|
@@ -551,7 +862,13 @@ async function main() {
|
|
|
551
862
|
try {
|
|
552
863
|
currentSource = currentSource === 'code' ? 'ide' : 'code';
|
|
553
864
|
data = await loadUsageData({ days: options.days, source: currentSource });
|
|
865
|
+
modelScrollOffset = 0;
|
|
866
|
+
projectScrollOffset = 0;
|
|
554
867
|
dailyScrollOffset = 0;
|
|
868
|
+
dailySelectedIndex = 0;
|
|
869
|
+
dailyDetailDate = null;
|
|
870
|
+
dailyDetailScrollOffset = 0;
|
|
871
|
+
contentBox.scrollTo(0);
|
|
555
872
|
updateTabBar();
|
|
556
873
|
updateContent();
|
|
557
874
|
updateStatusBar();
|
|
@@ -480,8 +480,8 @@ function pathExistsSync(p) {
|
|
|
480
480
|
* 尝试将 CodeBuddy Code 的项目名(路径中 / 替换为 -)还原为真实路径
|
|
481
481
|
* 使用回溯搜索,因为目录名本身可能包含 -
|
|
482
482
|
*
|
|
483
|
-
* 例如: "Users-foo-Documents-project-
|
|
484
|
-
* -> "/Users/foo/Documents/project/
|
|
483
|
+
* 例如: "Users-foo-Documents-project-codebuddy-stats"
|
|
484
|
+
* -> "/Users/foo/Documents/project/codebuddy-stats"
|
|
485
485
|
*/
|
|
486
486
|
function tryResolveCodePath(name) {
|
|
487
487
|
// 检查缓存
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "codebuddy-stats",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"author": "AnotiaWang",
|
|
38
38
|
"license": "ISC",
|
|
39
39
|
"packageManager": "pnpm@9.15.1",
|
|
40
|
-
"description": "CodeBuddy AI
|
|
40
|
+
"description": "CodeBuddy AI usage statistics with terminal UI",
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"blessed": "^0.1.81"
|
|
43
43
|
},
|