codebuddy-stats 1.2.1 → 1.2.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 CHANGED
@@ -421,7 +421,7 @@ function renderDaily(box, data, scrollOffset = 0, selectedIndex = 0, width, note
421
421
  }
422
422
  box.setContent(content);
423
423
  }
424
- // 渲染 Daily Detail 视图(某一天的详细数据)
424
+ // 渲染 Daily Detail 视图(某一天的详细数据,按 project 分组显示所有 model 用量)
425
425
  function renderDailyDetail(box, data, date, scrollOffset = 0, width, pageSize) {
426
426
  const { dailySummary, dailyData } = data;
427
427
  const daySummary = dailySummary[date];
@@ -430,57 +430,94 @@ function renderDailyDetail(box, data, date, scrollOffset = 0, width, pageSize) {
430
430
  box.setContent(`{bold}${date}{/bold}\n\nNo data available for this date.`);
431
431
  return;
432
432
  }
433
- // 汇总当天所有模型的用量
434
- const modelStats = {};
435
- for (const [, models] of Object.entries(dayData)) {
436
- for (const [model, stats] of Object.entries(models)) {
433
+ const projectDetails = [];
434
+ for (const [projectName, models] of Object.entries(dayData)) {
435
+ const shortName = resolveProjectName(projectName, data.workspaceMappings);
436
+ const modelList = [];
437
+ let totalCost = 0;
438
+ let totalTokens = 0;
439
+ let totalRequests = 0;
440
+ for (const [modelId, stats] of Object.entries(models)) {
437
441
  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);
442
+ const cost = Number(s.cost ?? 0);
443
+ const tokens = Number(s.totalTokens ?? 0);
444
+ const requests = Number(s.requests ?? 0);
445
+ modelList.push({ id: modelId, cost, tokens, requests });
446
+ totalCost += cost;
447
+ totalTokens += tokens;
448
+ totalRequests += requests;
449
+ }
450
+ // 按 cost 降序排序 models
451
+ modelList.sort((a, b) => b.cost - a.cost);
452
+ projectDetails.push({
453
+ name: projectName,
454
+ shortName,
455
+ totalCost,
456
+ totalTokens,
457
+ totalRequests,
458
+ models: modelList,
459
+ });
460
+ }
461
+ // 按 project 总 cost 降序排序
462
+ projectDetails.sort((a, b) => b.totalCost - a.totalCost);
463
+ const displayLines = [];
464
+ for (const project of projectDetails) {
465
+ displayLines.push({ type: 'project', project });
466
+ for (const model of project.models) {
467
+ displayLines.push({ type: 'model', model });
444
468
  }
445
469
  }
446
- const sortedModels = Object.entries(modelStats).sort((a, b) => b[1].cost - a[1].cost);
447
470
  // 根据宽度计算列宽
448
471
  const availableWidth = width - 6; // padding
449
472
  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`;
473
+ const nameCol = Math.max(25, availableWidth - fixedCols);
474
+ const totalWidth = nameCol + fixedCols;
475
+ let content = `{bold}${date} - Project & Model Usage Details{/bold}\n\n`;
453
476
  // 当天汇总
454
477
  content += `{green-fg}Total cost:{/green-fg} ${formatCost(daySummary.cost)} `;
455
478
  content += `{green-fg}Tokens:{/green-fg} ${formatTokens(daySummary.tokens)} `;
456
- content += `{green-fg}Requests:{/green-fg} ${formatNumber(daySummary.requests)}\n\n`;
479
+ content += `{green-fg}Requests:{/green-fg} ${formatNumber(daySummary.requests)} `;
480
+ content += `{green-fg}Projects:{/green-fg} ${projectDetails.length}\n\n`;
457
481
  content +=
458
482
  '{underline}' +
459
- 'Model'.padEnd(modelCol) +
483
+ 'Project / Model'.padEnd(nameCol) +
460
484
  '~Cost'.padStart(12) +
461
485
  'Requests'.padStart(12) +
462
486
  'Tokens'.padStart(12) +
463
487
  '{/underline}\n';
464
488
  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';
489
+ const visibleLines = displayLines.slice(scrollOffset, scrollOffset + safePageSize);
490
+ for (const line of visibleLines) {
491
+ if (line.type === 'project') {
492
+ const p = line.project;
493
+ content +=
494
+ '{cyan-fg}' +
495
+ truncate(p.shortName, nameCol - 1).padEnd(nameCol) +
496
+ formatCost(p.totalCost).padStart(12) +
497
+ formatNumber(p.totalRequests).padStart(12) +
498
+ formatTokens(p.totalTokens).padStart(12) +
499
+ '{/cyan-fg}\n';
500
+ }
501
+ else {
502
+ const m = line.model;
503
+ content +=
504
+ (' ' + truncate(m.id, nameCol - 3)).padEnd(nameCol) +
505
+ formatCost(m.cost).padStart(12) +
506
+ formatNumber(m.requests).padStart(12) +
507
+ formatTokens(m.tokens).padStart(12) +
508
+ '\n';
509
+ }
473
510
  }
474
511
  content += '─'.repeat(totalWidth) + '\n';
475
512
  content +=
476
513
  '{bold}' +
477
- `Total (${sortedModels.length} models)`.padEnd(modelCol) +
514
+ `Total (${projectDetails.length} projects)`.padEnd(nameCol) +
478
515
  formatCost(daySummary.cost).padStart(12) +
479
516
  formatNumber(daySummary.requests).padStart(12) +
480
517
  formatTokens(daySummary.tokens).padStart(12) +
481
518
  '{/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}`;
519
+ if (displayLines.length > safePageSize) {
520
+ content += `\n{gray-fg}Showing ${scrollOffset + 1}-${Math.min(scrollOffset + safePageSize, displayLines.length)} of ${displayLines.length} rows (↑↓ scroll, Esc back){/gray-fg}`;
484
521
  }
485
522
  else {
486
523
  content += `\n{gray-fg}(Esc back to Daily list){/gray-fg}`;
@@ -784,11 +821,14 @@ async function main() {
784
821
  }
785
822
  if (currentTab === 3) {
786
823
  if (dailyDetailDate) {
787
- // 在 detail 视图中滚动(需要计算当天的模型数量)
824
+ // 在 detail 视图中滚动(计算总行数:project 数 + 每个 project 下的 model 数)
788
825
  const dayData = data.dailyData[dailyDetailDate];
789
826
  if (dayData) {
790
- const modelCount = new Set(Object.values(dayData).flatMap(models => Object.keys(models))).size;
791
- const maxOffset = Math.max(0, modelCount - dailyDetailPageSize);
827
+ let totalLines = 0;
828
+ for (const models of Object.values(dayData)) {
829
+ totalLines += 1 + Object.keys(models).length; // 1 for project header + model count
830
+ }
831
+ const maxOffset = Math.max(0, totalLines - dailyDetailPageSize);
792
832
  dailyDetailScrollOffset = Math.min(maxOffset, dailyDetailScrollOffset + 1);
793
833
  }
794
834
  }
@@ -2,25 +2,37 @@
2
2
  function createPricing(inputPrice, cachedInputPrice, outputPrice, cacheWritePrice) {
3
3
  return {
4
4
  prompt: [{ limit: Number.POSITIVE_INFINITY, pricePerMTok: inputPrice }],
5
- completion: [{ limit: Number.POSITIVE_INFINITY, pricePerMTok: outputPrice }],
6
- cacheRead: [{ limit: Number.POSITIVE_INFINITY, pricePerMTok: cachedInputPrice }],
5
+ completion: [
6
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: outputPrice },
7
+ ],
8
+ cacheRead: [
9
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: cachedInputPrice },
10
+ ],
7
11
  cacheWrite: [
8
- { limit: Number.POSITIVE_INFINITY, pricePerMTok: cacheWritePrice ?? inputPrice },
12
+ {
13
+ limit: Number.POSITIVE_INFINITY,
14
+ pricePerMTok: cacheWritePrice ?? inputPrice,
15
+ },
9
16
  ],
10
17
  };
11
18
  }
12
19
  export const MODEL_PRICING = {
13
- 'gpt-5.2': createPricing(1.75, 0.175, 14.0),
14
- 'gpt-5.1': createPricing(1.25, 0.125, 10.0),
15
- 'gpt-5': createPricing(1.25, 0.125, 10.0),
16
- 'gpt-5-mini': createPricing(0.25, 0.025, 2.0),
17
- 'gpt-5-nano': createPricing(0.05, 0.005, 0.4),
18
- 'gpt-5.1-chat-latest': createPricing(1.25, 0.125, 10.0),
19
- 'gpt-5-chat-latest': createPricing(1.25, 0.125, 10.0),
20
- 'gpt-5.1-codex': createPricing(1.25, 0.125, 10.0),
21
- 'gpt-5-codex': createPricing(1.25, 0.125, 10.0),
22
- 'claude-opus-4.5': createPricing(5.0, 0.5, 25.0, 10.0),
23
- 'claude-4.5': {
20
+ // GPT 系列
21
+ "gpt-5.2": createPricing(1.75, 0.175, 14.0),
22
+ "gpt-5.1": createPricing(1.25, 0.125, 10.0),
23
+ "gpt-5": createPricing(1.25, 0.125, 10.0),
24
+ "gpt-5-mini": createPricing(0.25, 0.025, 2.0),
25
+ "gpt-5-nano": createPricing(0.05, 0.005, 0.4),
26
+ "gpt-5.1-chat-latest": createPricing(1.25, 0.125, 10.0),
27
+ "gpt-5-chat-latest": createPricing(1.25, 0.125, 10.0),
28
+ "gpt-5.1-codex": createPricing(1.25, 0.125, 10.0),
29
+ "gpt-5.1-codex-max": createPricing(1.25, 0.125, 10.0),
30
+ "gpt-5.1-codex-mini": createPricing(0.25, 0.025, 2.0),
31
+ "gpt-5-codex": createPricing(1.25, 0.125, 10.0),
32
+ // Claude 系列
33
+ "claude-opus-4.5": createPricing(5.0, 0.5, 25.0, 10.0),
34
+ "claude-haiku-4.5": createPricing(1.0, 0.1, 5.0, 1.25),
35
+ "claude-4.5": {
24
36
  prompt: [
25
37
  { limit: 200_000, pricePerMTok: 3.0 },
26
38
  { limit: Number.POSITIVE_INFINITY, pricePerMTok: 6.0 },
@@ -38,7 +50,8 @@ export const MODEL_PRICING = {
38
50
  { limit: Number.POSITIVE_INFINITY, pricePerMTok: 12.0 },
39
51
  ],
40
52
  },
41
- 'gemini-3-pro': {
53
+ // Gemini 系列
54
+ "gemini-3.0-pro": {
42
55
  prompt: [
43
56
  { limit: 200_000, pricePerMTok: 2.0 },
44
57
  { limit: Number.POSITIVE_INFINITY, pricePerMTok: 4.0 },
@@ -56,7 +69,8 @@ export const MODEL_PRICING = {
56
69
  { limit: Number.POSITIVE_INFINITY, pricePerMTok: 0.4 },
57
70
  ],
58
71
  },
59
- 'gemini-2.5-pro': {
72
+ "gemini-3.0-flash": createPricing(0.5, 0.05, 3.0),
73
+ "gemini-2.5-pro": {
60
74
  prompt: [
61
75
  { limit: 200_000, pricePerMTok: 1.25 },
62
76
  { limit: Number.POSITIVE_INFINITY, pricePerMTok: 2.5 },
@@ -74,8 +88,48 @@ export const MODEL_PRICING = {
74
88
  { limit: Number.POSITIVE_INFINITY, pricePerMTok: 0.25 },
75
89
  ],
76
90
  },
91
+ // GLM 系列 (价格从人民币转换: 1 USD = 7 CNY)
92
+ // 按上下文长度分段定价:[0,32K), [32K,200K)
93
+ "glm-4.7": {
94
+ prompt: [
95
+ { limit: 32_000, pricePerMTok: 0.286 }, // 2元/M tokens
96
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 0.571 }, // 4元/M tokens
97
+ ],
98
+ completion: [
99
+ { limit: 32_000, pricePerMTok: 1.143 }, // 8元/M tokens
100
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 2.286 }, // 16元/M tokens
101
+ ],
102
+ cacheRead: [
103
+ { limit: 32_000, pricePerMTok: 0.057 }, // 0.4元/M tokens
104
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 0.114 }, // 0.8元/M tokens
105
+ ],
106
+ cacheWrite: [
107
+ { limit: 32_000, pricePerMTok: 0.286 }, // 2元/M tokens
108
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 0.571 }, // 4元/M tokens
109
+ ],
110
+ },
111
+ "glm-4.6": {
112
+ prompt: [
113
+ { limit: 32_000, pricePerMTok: 0.286 }, // 2元/M tokens
114
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 0.571 }, // 4元/M tokens
115
+ ],
116
+ completion: [
117
+ { limit: 32_000, pricePerMTok: 1.143 }, // 8元/M tokens
118
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 2.286 }, // 16元/M tokens
119
+ ],
120
+ cacheRead: [
121
+ { limit: 32_000, pricePerMTok: 0.057 }, // 0.4元/M tokens
122
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 0.114 }, // 0.8元/M tokens
123
+ ],
124
+ cacheWrite: [
125
+ { limit: 32_000, pricePerMTok: 0.286 }, // 2元/M tokens
126
+ { limit: Number.POSITIVE_INFINITY, pricePerMTok: 0.571 }, // 4元/M tokens
127
+ ],
128
+ },
129
+ // DeepSeek 系列
130
+ "deepseek-v3.1": createPricing(0.56, 0.056, 1.68),
77
131
  };
78
- export const DEFAULT_MODEL_ID = 'gpt-5.1';
132
+ export const DEFAULT_MODEL_ID = "gpt-5.1";
79
133
  export function selectTierPrice(tokens, tiers) {
80
134
  if (tokens <= 0)
81
135
  return tiers[0]?.pricePerMTok ?? 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebuddy-stats",
3
- "version": "1.2.1",
3
+ "version": "1.2.3",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [