ccus-cli 0.2.22 → 0.2.24

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 CHANGED
@@ -18,6 +18,14 @@
18
18
  >
19
19
  > Claude **桌面版** 和 **VS Code 插件** 都不支持 statusLine,因此不会调用 `ccus statusline emit`,也就采集不到使用率数据。
20
20
 
21
+ ## 面板周评分
22
+
23
+ 个人和聚合面板均提供「周评分」:默认 `50 × √(周消息数 / 200) + 50 × √(周累计额度百分点 / 70)`,消息和额度均合计 Claude + Codex。两项达到基准得 100 分,分数不封顶。
24
+
25
+ 面板只显示评分结果。权重和基准集中在 `src/lib/weekly-score.ts` 的 `DEFAULT_SCORE_SETTINGS` 修改:`messageWeight` 为消息占比(额度占比为 100 减去消息占比),`messageBaseline` 为周消息基准,`quotaBaseline` 为周额度基准。修改后重新构建并重启面板服务。
26
+
27
+ 评分按当前查询范围逐周计算,未满一周不折算;查看本周评分请使用 `ccus dashboard serve --range this-week`。聚合面板在「多人对比」中显示周评分,多周取逐周评分的平均值。额度缺失显示 `--`(消息占比为 100% 时可单独评分)。评分仅在页面展示,不改变 bundle 或 CSV。
28
+
21
29
  ## 安装
22
30
 
23
31
  全局安装(statusline 每次渲染都会调用,推荐全局装好,避免 `npx` 的启动开销):
@@ -176,10 +184,15 @@ ccus aggregate serve --input-dir ./team-exports
176
184
  - 当前导出 bundle / weeklySummary 的 `schemaVersion` 为 `12`。两处 `range.dayStart` 记录统计日开始时间(如 `07:00`),供聚合沿用导出口径。顶层 `pricing` 记录本地价格目录版本、USD 币种和 `event-time-standard-api` 基准;`weeklySummary.apiEquivalentCost` 与 `dailySummaries[].apiEquivalentCost` 分别包含 `claude`、`codex`、`total` 的金额及定价覆盖度。v10 延续 v9 的 Codex token 口径:`inputTokens = max(0, input_tokens - cached_input_tokens)`,缓存输入由 `cacheReadInputTokens` 单列
177
185
  - v11 在有未定价请求的成本结果中增加 `unpricedModels: [{ model, requestCount }]`,按原始模型名累计请求数,模型缺失记为 `null`,无未定价请求时省略。该明细仅写入 JSON,不增加页面或 CSV 字段;v10 导出仍可读取,但没有模型明细。
178
186
  - 等效 API 成本按每次请求的模型、发生时间和 token 分类套用内置标准同步 API 价格。Claude 区分输入、输出、缓存读取及 5 分钟/1 小时缓存写入;Codex 区分净输入、缓存输入和输出。它不是 Claude/Codex 订阅账单,也不含税费、折扣、批处理、工具附加费或区域溢价
179
- - 未知模型不会丢弃:有部分请求可定价时金额是已知小计,页面显示“至少为”;全部请求均无法定价时金额为不可用。个人 dashboard 在顶部第五张卡显示 Claude 与 Codex 的合计成本,不显示来源分项。全部模型价格集中维护在 `src/lib/api-pricing-catalog.json`,价格目录随 ccus 发布,统计过程不联网;个人与多人 dashboard 的合计成本卡下链接独立 `pricing.html` 并在新页面打开,价格页只保留一套标题,Codex 排在 Claude 前并按模型版本从新到旧排列。当前 Claude 目录包含 Opus 4.7、Opus 4.8、Opus 5、Sonnet 5 和 Fable 5;Sonnet 5 按事件时间区分活动价与标准价
180
- - GPT-6 使用 `gpt-6-astra` 定价,兼容 `gpt-6` 别名及推理强度后缀。自 2026-09-03 起,每百万 token 输入 / 缓存读取 / 输出为 $10 / $1 / $50;输入与缓存读取合计超过 272K 时为 $20 / $2 / $75。来源:[OpenAI 模型定价](https://developers.openai.com/api/docs/models/gpt-6-astra)。价格目录版本为 `2026-09-23`
187
+ - 未知模型不会丢弃:有部分请求可定价时金额是已知小计,页面显示“至少为”;全部请求均无法定价时金额为不可用。个人 dashboard 在顶部第五张卡显示 Claude 与 Codex 的合计成本,不显示来源分项。全部模型价格集中维护在 `src/lib/api-pricing-catalog.json`,价格目录随 ccus 发布,统计过程不联网;个人与多人 dashboard 的合计成本卡下链接独立 `pricing.html` 并在新页面打开,价格页只保留一套标题,Codex 排在 Claude 前并按模型版本从新到旧排列。当前 Claude 目录包含 Opus / Sonnet / Haiku 5.5、Fable 5.1 与 Mythos 5 / 5.1,并保留历史模型价格
188
+ - GPT-6 使用 `gpt-6-astra` 定价,兼容 `gpt-6` 别名及推理强度后缀。自 2026-09-03 起,每百万 token 输入 / 缓存读取 / 输出为 $10 / $1 / $50;输入与缓存读取合计超过 272K 时为 $20 / $2 / $75。来源:[OpenAI 模型定价](https://developers.openai.com/api/docs/models/gpt-6-astra)。价格目录版本为 `2026-10-08`
181
189
  - 自 2026-09-22 起,`gpt-6-sol` 每百万 token 输入 / 缓存读取 / 输出为 $2 / $0.20 / $10,`gpt-6-luna` 为 $0.10 / $0.01 / $0.50;输入与缓存读取合计超过 272K 时,分别为 $4 / $0.40 / $15 和 $0.20 / $0.02 / $0.75。均兼容推理强度后缀。来源:[OpenAI 价格表](https://developers.openai.com/api/docs/pricing)。
190
+ - 自 2026-09-29 起,`gpt-6.1-sol` 每百万 token 输入 / 缓存读取 / 输出为 $2 / $0.10 / $10;输入与缓存读取合计超过 272K 时为 $4 / $0.20 / $15。兼容推理强度后缀。来源:[OpenAI 模型定价](https://developers.openai.com/api/docs/models/gpt-6.1-sol)。
182
191
  - 自 2026-09-22 起,`claude-opus-5-5`(目录名 `claude-opus-5.5`)每百万 token 输入 / 输出 / 缓存读取 / 5 分钟缓存写入 / 1 小时缓存写入为 $4 / $20 / $0.20 / $5 / $8。来源:[官方发布公告](https://www.anthropic.com/claude-opus-5-5)、[缓存计价说明](https://claude.com/blog/what-a-task-costs-on-opus-5-5)。
192
+ - 自 2026-09-01 起,Fable 5.1 与 Mythos 5.1 每百万 token 输入 / 输出 / 缓存读取 / 5 分钟缓存写入 / 1 小时缓存写入为 $10 / $50 / $0.25 / $12.50 / $20;Mythos 5 自 2026-06-09 起为 $10 / $50 / $1 / $12.50 / $20。Mythos 访问受限,但公开价格可用于成本估算。来源:[Claude 价格表](https://platform.claude.com/docs/en/about-claude/pricing)。
193
+ - Sonnet 5.5 自 2026-09-28 起为 $2 / $10 / $0.20 / $2.50 / $4,2026-10-07 起缓存读取降为 $0.10,其余不变。Haiku 5.5 自 2026-10-07 起为 $0.10 / $0.50 / $0.01 / $0.125 / $0.20;全部输入类 token 合计超过 100K 时为 $0.50 / $2.50 / $0.05 / $0.625 / $1。上述金额依次为每百万 token 输入 / 输出 / 缓存读取 / 5 分钟缓存写入 / 1 小时缓存写入。来源:[Claude 价格表](https://platform.claude.com/docs/en/about-claude/pricing)、[发布记录](https://platform.claude.com/docs/en/release-notes/overview)。
194
+ - Sonnet 5 的 $2 / $10 输入 / 输出价格已转为长期标准价,原定 2026-09-01 涨价取消;Sonnet 4.6 自 2026-03-13 起取消长上下文加价,此前事件保留历史计价。来源:[Claude 发布记录](https://platform.claude.com/docs/en/release-notes/overview)。
195
+ - `gpt-5.4-mini` 自 2026-03-17 起,每百万 token 输入 / 缓存读取 / 输出为 $0.75 / $0.075 / $4.50,不设长上下文加价。来源:[OpenAI 模型定价](https://developers.openai.com/api/docs/models/gpt-5.4-mini)。
183
196
  - `gpt-5.3-codex-spark` 自 2026-02-12 起按 `gpt-5.3-codex` 价格估算:每百万 token 输入 $1.75、输出 $14、缓存读取 $0.175。这是本项目采用的估算单价,并非 Spark 官方公开 API 定价。
184
197
  - 默认文件名会带 git email 的帐号名前缀和起止日期,例如:`alice_export_2026-05-26_to_2026-06-01.json.gz`
185
198
  - `userMessageCount` 来自 `~/.claude/projects/**/*.jsonl` 的非 meta `type:user` 事件
@@ -8,6 +8,7 @@ const chart_assets_1 = require("./chart-assets");
8
8
  const time_1 = require("./time");
9
9
  const api_equivalent_cost_1 = require("./api-equivalent-cost");
10
10
  const api_pricing_table_1 = require("./api-pricing-table");
11
+ const weekly_score_1 = require("./weekly-score");
11
12
  /** 折线 / 图例统一的调色板,保证同一个人在不同图表里颜色一致。 */
12
13
  const CHART_PALETTE = ["#5eead4", "#f59e0b", "#a855f7", "#22c55e", "#f87171", "#60a5fa", "#fbbf24", "#34d399"];
13
14
  /**
@@ -415,7 +416,7 @@ function renderFiveHourUsageChart(people, detailRows) {
415
416
  `;
416
417
  }
417
418
  /** 按人渲染汇总表,给出消息、token、成本和 usage 核心指标。 */
418
- function renderPeopleLeaderboard(people) {
419
+ function renderPeopleLeaderboard(people, weeklyRows) {
419
420
  if (people.length === 0) {
420
421
  return `
421
422
  <section class="panel table-panel">
@@ -429,19 +430,30 @@ function renderPeopleLeaderboard(people) {
429
430
  </section>
430
431
  `;
431
432
  }
433
+ const scores = new Map();
434
+ for (const row of weeklyRows) {
435
+ const values = scores.get(row.personKey) ?? [];
436
+ values.push((0, weekly_score_1.calculateWeeklyScore)(row.userMessageCount, row.sevenDayCumulativeUsagePct, weekly_score_1.DEFAULT_SCORE_SETTINGS));
437
+ scores.set(row.personKey, values);
438
+ }
439
+ const scoreText = (personKey) => {
440
+ const values = scores.get(personKey) ?? [];
441
+ if (values.length === 0 || values.some(value => value === null))
442
+ return "--";
443
+ return (values.reduce((sum, value) => sum + (value ?? 0), 0) / values.length).toFixed(1);
444
+ };
432
445
  const rows = people
433
446
  .map((person, index) => `
434
447
  <tr>
435
448
  <td class="rank">${index + 1}</td>
436
449
  <td><strong>${escapeHtml(person.personKey)}</strong></td>
450
+ <td><strong>${scoreText(person.personKey)}</strong></td>
437
451
  <td>${formatNumber(person.userMessageCount)}</td>
438
452
  <td>${formatTokensM(person.inputTokens)}</td>
439
453
  <td>${formatTokensM(person.outputTokens)}</td>
440
454
  <td>${formatTokensM(person.cacheReadInputTokens)}</td>
441
455
  <td>${escapeHtml(costDisplay(person.estimatedApiEquivalentCostUsd, person.pricedApiRequestCount, person.unpricedApiRequestCount))}</td>
442
456
  <td>${escapeHtml(person.pricingCatalogVersion ?? "--")}</td>
443
- <td>${escapeHtml(statValue(person.fiveHourPeakUsagePct))}</td>
444
- <td>${escapeHtml(statValue(person.fiveHourLatestUsagePct))}</td>
445
457
  <td>${escapeHtml(statValue(person.sevenDayPeakUsagePct))}</td>
446
458
  <td>${escapeHtml(statValue(person.sevenDayLatestUsagePct))}</td>
447
459
  <td>${escapeHtml(statValue(person.sevenDayCumulativeUsagePct))}</td>
@@ -455,7 +467,7 @@ function renderPeopleLeaderboard(people) {
455
467
  <p class="eyebrow">People</p>
456
468
  <h2>多人对比</h2>
457
469
  </div>
458
- <p class="muted">按用户消息数降序排列,所有数字直接来自 daily/weekly 汇总。</p>
470
+ <p class="muted">按用户消息数降序排列;评分按周计算,多周取平均,缺失额度显示 --。</p>
459
471
  </div>
460
472
  <div class="table-wrap">
461
473
  <table>
@@ -463,14 +475,13 @@ function renderPeopleLeaderboard(people) {
463
475
  <tr>
464
476
  <th>#</th>
465
477
  <th>personKey</th>
478
+ <th>周评分</th>
466
479
  <th>消息</th>
467
480
  <th>Input tokens</th>
468
481
  <th>Output tokens</th>
469
482
  <th>Cache read tokens</th>
470
483
  <th>等效 API 成本</th>
471
484
  <th>价格目录</th>
472
- <th>5h Peak</th>
473
- <th>5h Latest</th>
474
485
  <th>7d Peak</th>
475
486
  <th>7d Latest</th>
476
487
  <th>7d 累计</th>
@@ -798,7 +809,7 @@ function buildAggregateDashboardHtml(detailRows, dailyRows, weeklyRows, generate
798
809
  <a class="pricing-link" href="${api_pricing_table_1.API_PRICING_PAGE_FILE}" target="_blank" rel="noopener noreferrer">查看当前价格表</a>
799
810
  </article>
800
811
  </section>
801
- ${renderPeopleLeaderboard(people)}
812
+ ${renderPeopleLeaderboard(people, weeklyRows)}
802
813
  ${renderSevenDayCumulativeChart(people)}
803
814
  ${renderFiveHourUsageChart(people, detailRows)}
804
815
  ${renderDailyUserRequestChart(people, dailyIndex, dateAxis)}
@@ -85,15 +85,15 @@ function normalizeClaudeModel(model) {
85
85
  .replaceAll("_", "-")
86
86
  .replace(/\[1m\]$/, "")
87
87
  .replace(/-thinking(?=-\d{8}$|$)/, "");
88
- const canonicalMinor = /^claude-(opus|sonnet|haiku|fable)-(\d+)\.(\d+)(?:-\d{8})?$/.exec(value);
88
+ const canonicalMinor = /^claude-(opus|sonnet|haiku|fable|mythos)-(\d+)\.(\d+)(?:-\d{8})?$/.exec(value);
89
89
  if (canonicalMinor) {
90
90
  return `claude-${canonicalMinor[1]}-${canonicalMinor[2]}.${canonicalMinor[3]}`;
91
91
  }
92
- const modern = /^claude-(opus|sonnet|haiku|fable)-(\d+)-(\d+)(?:-\d{8})?$/.exec(value);
92
+ const modern = /^claude-(opus|sonnet|haiku|fable|mythos)-(\d+)-(\d+)(?:-\d{8})?$/.exec(value);
93
93
  if (modern) {
94
94
  return `claude-${modern[1]}-${modern[2]}.${modern[3]}`;
95
95
  }
96
- const modernMajor = /^claude-(opus|sonnet|haiku|fable)-(\d+)(?:-\d{8})?$/.exec(value);
96
+ const modernMajor = /^claude-(opus|sonnet|haiku|fable|mythos)-(\d+)(?:-\d{8})?$/.exec(value);
97
97
  if (modernMajor) {
98
98
  return `claude-${modernMajor[1]}-${modernMajor[2]}`;
99
99
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "catalogVersion": "2026-09-23",
2
+ "catalogVersion": "2026-10-08",
3
3
  "currency": "USD",
4
4
  "basis": "event-time-standard-api",
5
5
  "entries": [
@@ -99,13 +99,20 @@
99
99
  "provider": "claude",
100
100
  "model": "claude-sonnet-4.6",
101
101
  "effectiveFrom": "2026-02-17T00:00:00.000Z",
102
- "effectiveTo": null,
102
+ "effectiveTo": "2026-03-13T00:00:00.000Z",
103
103
  "prices": { "inputUsdPerMillion": 3, "outputUsdPerMillion": 15, "cacheReadInputUsdPerMillion": 0.3, "cacheWrite5mInputUsdPerMillion": 3.75, "cacheWrite1hInputUsdPerMillion": 6 },
104
104
  "longContext": {
105
105
  "thresholdInputTokens": 200000,
106
106
  "prices": { "inputUsdPerMillion": 6, "outputUsdPerMillion": 22.5, "cacheReadInputUsdPerMillion": 0.6, "cacheWrite5mInputUsdPerMillion": 7.5, "cacheWrite1hInputUsdPerMillion": 12 }
107
107
  }
108
108
  },
109
+ {
110
+ "provider": "claude",
111
+ "model": "claude-sonnet-4.6",
112
+ "effectiveFrom": "2026-03-13T00:00:00.000Z",
113
+ "effectiveTo": null,
114
+ "prices": { "inputUsdPerMillion": 3, "outputUsdPerMillion": 15, "cacheReadInputUsdPerMillion": 0.3, "cacheWrite5mInputUsdPerMillion": 3.75, "cacheWrite1hInputUsdPerMillion": 6 }
115
+ },
109
116
  {
110
117
  "provider": "claude",
111
118
  "model": "claude-opus-4.7",
@@ -131,15 +138,29 @@
131
138
  "provider": "claude",
132
139
  "model": "claude-sonnet-5",
133
140
  "effectiveFrom": "2026-06-30T00:00:00.000Z",
134
- "effectiveTo": "2026-09-01T00:00:00.000Z",
141
+ "effectiveTo": null,
135
142
  "prices": { "inputUsdPerMillion": 2, "outputUsdPerMillion": 10, "cacheReadInputUsdPerMillion": 0.2, "cacheWrite5mInputUsdPerMillion": 2.5, "cacheWrite1hInputUsdPerMillion": 4 }
136
143
  },
137
144
  {
138
145
  "provider": "claude",
139
- "model": "claude-sonnet-5",
146
+ "model": "claude-fable-5.1",
140
147
  "effectiveFrom": "2026-09-01T00:00:00.000Z",
141
148
  "effectiveTo": null,
142
- "prices": { "inputUsdPerMillion": 3, "outputUsdPerMillion": 15, "cacheReadInputUsdPerMillion": 0.3, "cacheWrite5mInputUsdPerMillion": 3.75, "cacheWrite1hInputUsdPerMillion": 6 }
149
+ "prices": { "inputUsdPerMillion": 10, "outputUsdPerMillion": 50, "cacheReadInputUsdPerMillion": 0.25, "cacheWrite5mInputUsdPerMillion": 12.5, "cacheWrite1hInputUsdPerMillion": 20 }
150
+ },
151
+ {
152
+ "provider": "claude",
153
+ "model": "claude-mythos-5",
154
+ "effectiveFrom": "2026-06-09T00:00:00.000Z",
155
+ "effectiveTo": null,
156
+ "prices": { "inputUsdPerMillion": 10, "outputUsdPerMillion": 50, "cacheReadInputUsdPerMillion": 1, "cacheWrite5mInputUsdPerMillion": 12.5, "cacheWrite1hInputUsdPerMillion": 20 }
157
+ },
158
+ {
159
+ "provider": "claude",
160
+ "model": "claude-mythos-5.1",
161
+ "effectiveFrom": "2026-09-01T00:00:00.000Z",
162
+ "effectiveTo": null,
163
+ "prices": { "inputUsdPerMillion": 10, "outputUsdPerMillion": 50, "cacheReadInputUsdPerMillion": 0.25, "cacheWrite5mInputUsdPerMillion": 12.5, "cacheWrite1hInputUsdPerMillion": 20 }
143
164
  },
144
165
  {
145
166
  "provider": "claude",
@@ -155,6 +176,31 @@
155
176
  "effectiveTo": null,
156
177
  "prices": { "inputUsdPerMillion": 4, "outputUsdPerMillion": 20, "cacheReadInputUsdPerMillion": 0.2, "cacheWrite5mInputUsdPerMillion": 5, "cacheWrite1hInputUsdPerMillion": 8 }
157
178
  },
179
+ {
180
+ "provider": "claude",
181
+ "model": "claude-sonnet-5.5",
182
+ "effectiveFrom": "2026-09-28T00:00:00.000Z",
183
+ "effectiveTo": "2026-10-07T00:00:00.000Z",
184
+ "prices": { "inputUsdPerMillion": 2, "outputUsdPerMillion": 10, "cacheReadInputUsdPerMillion": 0.2, "cacheWrite5mInputUsdPerMillion": 2.5, "cacheWrite1hInputUsdPerMillion": 4 }
185
+ },
186
+ {
187
+ "provider": "claude",
188
+ "model": "claude-sonnet-5.5",
189
+ "effectiveFrom": "2026-10-07T00:00:00.000Z",
190
+ "effectiveTo": null,
191
+ "prices": { "inputUsdPerMillion": 2, "outputUsdPerMillion": 10, "cacheReadInputUsdPerMillion": 0.1, "cacheWrite5mInputUsdPerMillion": 2.5, "cacheWrite1hInputUsdPerMillion": 4 }
192
+ },
193
+ {
194
+ "provider": "claude",
195
+ "model": "claude-haiku-5.5",
196
+ "effectiveFrom": "2026-10-07T00:00:00.000Z",
197
+ "effectiveTo": null,
198
+ "prices": { "inputUsdPerMillion": 0.1, "outputUsdPerMillion": 0.5, "cacheReadInputUsdPerMillion": 0.01, "cacheWrite5mInputUsdPerMillion": 0.125, "cacheWrite1hInputUsdPerMillion": 0.2 },
199
+ "longContext": {
200
+ "thresholdInputTokens": 100000,
201
+ "prices": { "inputUsdPerMillion": 0.5, "outputUsdPerMillion": 2.5, "cacheReadInputUsdPerMillion": 0.05, "cacheWrite5mInputUsdPerMillion": 0.625, "cacheWrite1hInputUsdPerMillion": 1 }
202
+ }
203
+ },
158
204
  {
159
205
  "provider": "codex",
160
206
  "model": "gpt-5",
@@ -236,6 +282,13 @@
236
282
  "prices": { "inputUsdPerMillion": 5, "outputUsdPerMillion": 22.5, "cacheReadInputUsdPerMillion": 0.5 }
237
283
  }
238
284
  },
285
+ {
286
+ "provider": "codex",
287
+ "model": "gpt-5.4-mini",
288
+ "effectiveFrom": "2026-03-17T00:00:00.000Z",
289
+ "effectiveTo": null,
290
+ "prices": { "inputUsdPerMillion": 0.75, "outputUsdPerMillion": 4.5, "cacheReadInputUsdPerMillion": 0.075 }
291
+ },
239
292
  {
240
293
  "provider": "codex",
241
294
  "model": "gpt-5.5",
@@ -302,6 +355,17 @@
302
355
  "prices": { "inputUsdPerMillion": 4, "outputUsdPerMillion": 15, "cacheReadInputUsdPerMillion": 0.4 }
303
356
  }
304
357
  },
358
+ {
359
+ "provider": "codex",
360
+ "model": "gpt-6.1-sol",
361
+ "effectiveFrom": "2026-09-29T00:00:00.000Z",
362
+ "effectiveTo": null,
363
+ "prices": { "inputUsdPerMillion": 2, "outputUsdPerMillion": 10, "cacheReadInputUsdPerMillion": 0.1 },
364
+ "longContext": {
365
+ "thresholdInputTokens": 272000,
366
+ "prices": { "inputUsdPerMillion": 4, "outputUsdPerMillion": 15, "cacheReadInputUsdPerMillion": 0.2 }
367
+ }
368
+ },
305
369
  {
306
370
  "provider": "codex",
307
371
  "model": "gpt-6-luna",
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildPersonalWeeklyScores = buildPersonalWeeklyScores;
3
4
  exports.summarizeEvents = summarizeEvents;
4
5
  exports.bucketizeEvents = bucketizeEvents;
5
6
  exports.buildDashboardHtml = buildDashboardHtml;
@@ -8,6 +9,28 @@ const chart_assets_1 = require("./chart-assets");
8
9
  const api_pricing_table_1 = require("./api-pricing-table");
9
10
  const payload_1 = require("./payload");
10
11
  const time_1 = require("./time");
12
+ const weekly_score_1 = require("./weekly-score");
13
+ function buildPersonalWeeklyScores(events, messages, dayStart) {
14
+ const dayStartMinutes = (0, time_1.parseDayStart)(dayStart);
15
+ const weekOf = (date, boundary) => (0, time_1.localDateKey)((0, time_1.resolveRange)("this-week", date, boundary).start);
16
+ const weeks = new Map();
17
+ const getWeek = (week) => {
18
+ if (!weeks.has(week))
19
+ weeks.set(week, { week, personKey: "本人", userMessageCount: 0, sevenDayCumulativeUsagePct: null });
20
+ return weeks.get(week);
21
+ };
22
+ for (const point of messages) {
23
+ getWeek(weekOf(new Date(`${point.date}T12:00:00`), 0)).userMessageCount += point.userMessageCount + (point.codexUserMessageCount ?? 0);
24
+ }
25
+ for (const event of events)
26
+ getWeek(weekOf(new Date(event.timestamp), dayStartMinutes));
27
+ const curves = [false, true].map(codex => (0, aggregate_1.buildSevenDayCurveFromEvents)(events.filter(event => (0, payload_1.isCodexSourceEvent)(event) === codex)));
28
+ for (const row of weeks.values()) {
29
+ const totals = curves.map(curve => (0, aggregate_1.computeCumulativeSevenDay)(curve.filter(event => weekOf(new Date(event.timestamp), dayStartMinutes) === row.week)));
30
+ row.sevenDayCumulativeUsagePct = (0, aggregate_1.addNullable)(totals[0], totals[1]);
31
+ }
32
+ return [...weeks.values()];
33
+ }
11
34
  /** 所有插入到 HTML/SVG 的动态文本都先转义,避免本地页面被注入内容。 */
12
35
  function escapeHtml(value) {
13
36
  return value
@@ -142,7 +165,7 @@ function renderChart(buckets) {
142
165
  <div class="panel-header">
143
166
  <div>
144
167
  <p class="eyebrow">Recent Trend</p>
145
- <h2>Claude 使用率趋势</h2>
168
+ <h2>使用率趋势</h2>
146
169
  </div>
147
170
  <p class="muted">按采样时间聚合:5h(实线)来自 rate_limits.five_hour,7d(虚线)来自 rate_limits.seven_day;紫色「7d 分区叠加累计」走右侧 Y 轴,是把 7d 锯齿波还原成的累计真实使用量(口径同团队看板)</p>
148
171
  </div>
@@ -300,6 +323,11 @@ function buildDashboardHtml(events, rangeLabel, start, end, dailyUserMessages =
300
323
  const totalUserMessages = dailyUserMessages.reduce((sum, point) => sum + point.userMessageCount, 0);
301
324
  const codexTotalUserMessages = dailyUserMessages.reduce((sum, point) => sum + (point.codexUserMessageCount ?? 0), 0);
302
325
  const combinedUserMessages = totalUserMessages + codexTotalUserMessages;
326
+ const scoreWeek = (0, time_1.localDateKey)((0, time_1.resolveRange)("this-week", end, (0, time_1.parseDayStart)(dayStart)).start);
327
+ const scoreRow = buildPersonalWeeklyScores(events, dailyUserMessages, dayStart).find(row => row.week === scoreWeek);
328
+ const weeklyScore = scoreRow
329
+ ? (0, weekly_score_1.calculateWeeklyScore)(scoreRow.userMessageCount, scoreRow.sevenDayCumulativeUsagePct, weekly_score_1.DEFAULT_SCORE_SETTINGS)
330
+ : null;
303
331
  return `<!doctype html>
304
332
  <html lang="zh-CN">
305
333
  <head>
@@ -477,15 +505,10 @@ function buildDashboardHtml(events, rangeLabel, start, end, dailyUserMessages =
477
505
  </div>
478
506
  </section>
479
507
  <section class="stats">
480
- <article class="panel stat-card">
481
- <h2>Latest 5h usage</h2>
482
- <p class="stat-value">${escapeHtml(statValue(summary.fiveHourLatestUsagePct))}</p>
483
- <p class="stat-note">最后一条有效 5 小时 usage 样本</p>
484
- </article>
485
- <article class="panel stat-card">
486
- <h2>Peak 5h usage</h2>
487
- <p class="stat-value">${escapeHtml(statValue(summary.fiveHourPeakUsagePct))}</p>
488
- <p class="stat-note">窗口内观测到的 5 小时使用率峰值</p>
508
+ <article class="panel stat-card" id="weekly-score">
509
+ <h2>周评分</h2>
510
+ <p class="stat-value">${weeklyScore?.toFixed(1) ?? "--"}</p>
511
+ <p class="stat-note">${scoreWeek} 当周 · 当前查询范围内数据</p>
489
512
  </article>
490
513
  <article class="panel stat-card">
491
514
  <h2>7d 分区叠加累计</h2>
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_SCORE_SETTINGS = void 0;
4
+ exports.calculateWeeklyScore = calculateWeeklyScore;
5
+ exports.renderWeeklyScores = renderWeeklyScores;
6
+ exports.DEFAULT_SCORE_SETTINGS = {
7
+ messageWeight: 50,
8
+ messageBaseline: 200,
9
+ quotaBaseline: 70,
10
+ };
11
+ function calculateWeeklyScore(messages, quota, settings) {
12
+ const { messageWeight, messageBaseline, quotaBaseline } = settings;
13
+ if (!Number.isFinite(messageWeight) || messageWeight < 0 || messageWeight > 100
14
+ || !Number.isFinite(messageBaseline) || messageBaseline <= 0
15
+ || !Number.isFinite(quotaBaseline) || quotaBaseline <= 0
16
+ || !Number.isFinite(messages) || messages < 0
17
+ || (quota !== null && (!Number.isFinite(quota) || quota < 0)))
18
+ return null;
19
+ if (quota === null && messageWeight < 100)
20
+ return null;
21
+ return messageWeight * Math.sqrt(messages / messageBaseline)
22
+ + (100 - messageWeight) * Math.sqrt((quota ?? 0) / quotaBaseline);
23
+ }
24
+ function escapeHtml(value) {
25
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;")
26
+ .replaceAll('"', "&quot;").replaceAll("'", "&#39;");
27
+ }
28
+ /** 两类面板共用评分;权重和基准统一在 DEFAULT_SCORE_SETTINGS 修改。 */
29
+ function renderWeeklyScores(rows) {
30
+ const settings = exports.DEFAULT_SCORE_SETTINGS;
31
+ const body = [...rows].sort((a, b) => b.week.localeCompare(a.week) || a.personKey.localeCompare(b.personKey)).map((row) => {
32
+ const score = calculateWeeklyScore(row.userMessageCount, row.sevenDayCumulativeUsagePct, settings);
33
+ return `<tr><td>${escapeHtml(row.week)}</td><td>${escapeHtml(row.personKey)}</td>
34
+ <td><strong>${score?.toFixed(1) ?? "--"}</strong></td></tr>`;
35
+ }).join("");
36
+ return `<section class="panel table-panel" id="weekly-score">
37
+ <h2>周评分</h2>
38
+ <div class="table-wrap"><table><thead><tr><th>周起始</th><th>用户</th><th>评分</th></tr></thead>
39
+ <tbody>${body || '<tr><td colspan="3">暂无评分数据</td></tr>'}</tbody></table></div>
40
+ </section>`;
41
+ }
42
+ //# sourceMappingURL=weekly-score.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccus-cli",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
4
4
  "description": "Claude Code statusline usage logger and dashboard CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -17,7 +17,7 @@
17
17
  "build": "tsc -p tsconfig.json && node scripts/copy-vendor.js",
18
18
  "prepublishOnly": "npm run build",
19
19
  "test": "node --test --test-concurrency 1 \"dist/test/**/*.test.js\"",
20
- "test:src": "node --import tsx --test --test-concurrency 1 src/test/payload.test.ts src/test/dashboard.test.ts src/test/export.test.ts src/test/storage.test.ts src/test/api-equivalent-cost.test.ts src/test/claude.test.ts src/test/aggregate.test.ts src/test/aggregate-dashboard.test.ts src/test/install.test.ts src/test/debug.test.ts src/test/time.test.ts src/test/day-start.test.ts src/test/update-check.test.ts src/test/sync.test.ts src/test/scheduler.test.ts src/test/api-mode.test.ts src/test/codex-fetcher.test.ts src/test/codex-sessions.test.ts src/test/sessions.test.ts src/test/codex-install.test.ts src/test/cli-codex-notify.test.ts src/test/cli-codex-hook.test.ts src/test/cli-install-codex.test.ts",
20
+ "test:src": "node --import tsx --test --test-concurrency 1 src/test/payload.test.ts src/test/dashboard.test.ts src/test/weekly-score.test.ts src/test/export.test.ts src/test/storage.test.ts src/test/api-equivalent-cost.test.ts src/test/claude.test.ts src/test/aggregate.test.ts src/test/aggregate-dashboard.test.ts src/test/install.test.ts src/test/debug.test.ts src/test/time.test.ts src/test/day-start.test.ts src/test/update-check.test.ts src/test/sync.test.ts src/test/scheduler.test.ts src/test/api-mode.test.ts src/test/codex-fetcher.test.ts src/test/codex-sessions.test.ts src/test/sessions.test.ts src/test/codex-install.test.ts src/test/cli-codex-notify.test.ts src/test/cli-codex-hook.test.ts src/test/cli-install-codex.test.ts",
21
21
  "smoke:api": "node scripts/smoke-api.mjs"
22
22
  },
23
23
  "keywords": [