ccus-cli 0.2.23 → 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 +8 -0
- package/dist/lib/aggregate-dashboard.js +18 -7
- package/dist/lib/dashboard.js +33 -10
- package/dist/lib/weekly-score.js +42 -0
- package/package.json +2 -2
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` 的启动开销):
|
|
@@ -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"
|
|
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)}
|
package/dist/lib/dashboard.js
CHANGED
|
@@ -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
|
|
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
|
|
482
|
-
<p class="stat-value">${
|
|
483
|
-
<p class="stat-note"
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">")
|
|
26
|
+
.replaceAll('"', """).replaceAll("'", "'");
|
|
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.
|
|
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": [
|