ccus-cli 0.2.16 → 0.2.17
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 +4 -3
- package/dist/cli.js +2 -2
- package/dist/lib/aggregate.js +10 -5
- package/dist/lib/api-equivalent-cost.js +9 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -167,10 +167,11 @@ ccus aggregate serve --input-dir ./team-exports
|
|
|
167
167
|
- 默认输出一个 `json` 数据包,里面同时包含 `rawEvents`、`weeklySummary`、`dailySummaries`
|
|
168
168
|
- 导出文件内容为**紧凑 JSON**(无缩进),并默认 **gzip 压缩**后写成 `.json.gz`;gzip 与紧凑化都只是存储/展示层变化,解压后的字段集合与 `schemaVersion` 不变
|
|
169
169
|
- 默认写 `.json.gz`;若用 `--out` 指定一个非 `.gz` 结尾的路径,则按明文 JSON 写出(不压缩)
|
|
170
|
-
- 当前导出 bundle / weeklySummary 的 `schemaVersion` 为 `
|
|
170
|
+
- 当前导出 bundle / weeklySummary 的 `schemaVersion` 为 `11`。顶层 `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` 单列
|
|
171
|
+
- v11 在有未定价请求的成本结果中增加 `unpricedModels: [{ model, requestCount }]`,按原始模型名累计请求数,模型缺失记为 `null`,无未定价请求时省略。该明细仅写入 JSON,不增加页面或 CSV 字段;v10 导出仍可读取,但没有模型明细。
|
|
171
172
|
- 等效 API 成本按每次请求的模型、发生时间和 token 分类套用内置标准同步 API 价格。Claude 区分输入、输出、缓存读取及 5 分钟/1 小时缓存写入;Codex 区分净输入、缓存输入和输出。它不是 Claude/Codex 订阅账单,也不含税费、折扣、批处理、工具附加费或区域溢价
|
|
172
173
|
- 未知模型不会丢弃:有部分请求可定价时金额是已知小计,页面显示“至少为”;全部请求均无法定价时金额为不可用。个人 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 按事件时间区分活动价与标准价
|
|
173
|
-
- 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-14
|
|
174
|
+
- 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-14`
|
|
174
175
|
- `gpt-5.3-codex-spark` 自 2026-02-12 起按 `gpt-5.3-codex` 价格估算:每百万 token 输入 $1.75、输出 $14、缓存读取 $0.175。这是本项目采用的估算单价,并非 Spark 官方公开 API 定价。
|
|
175
176
|
- 默认文件名会带 git email 的帐号名前缀和起止日期,例如:`alice_export_2026-05-26_to_2026-06-01.json.gz`
|
|
176
177
|
- `userMessageCount` 来自 `~/.claude/projects/**/*.jsonl` 的非 meta `type:user` 事件
|
|
@@ -188,7 +189,7 @@ ccus aggregate serve --input-dir ./team-exports
|
|
|
188
189
|
## 多人汇总
|
|
189
190
|
|
|
190
191
|
- 输入目录放很多通过 `ccus export` 导出的 bundle 文件,`.json.gz`(gzip 压缩)与明文 `.json` 都能识别,gzip 文件读取时自动解压
|
|
191
|
-
- `aggregate` 接受 `schemaVersion: 6/7/8/9/10`。v6–v9 继续聚合原统计;存在 API 请求时成本为空并全部计入未定价,没有请求时成本为 0。旧版与 v10 或不同价格目录混用时,`pricingCatalogVersion` 按规则标记为 `mixed`
|
|
192
|
+
- `aggregate` 接受 `schemaVersion: 6/7/8/9/10/11`。v6–v9 继续聚合原统计;存在 API 请求时成本为空并全部计入未定价,没有请求时成本为 0。旧版与 v10 或不同价格目录混用时,`pricingCatalogVersion` 按规则标记为 `mixed`
|
|
192
193
|
- 同一个人在多台电脑上各自导出 bundle 时会自动合并去重:去重以**天**为粒度,对每个「同人同天」取 `generatedAt` 最新的那份导出,避免同一台机器重复导出或周与周重叠造成翻倍;**周汇总不取整周单份,而是把按天去重后的各天数据上卷累加**,所以多台电脑在不同天产生的用量会正确合进同一周。usage(5h / 7d)从选中事件按真实时间戳重算,Claude+Codex 合并:peak 取两源 max、latest 两源相加
|
|
193
194
|
- `ccus aggregate --input-dir DIR --out-dir DIR`
|
|
194
195
|
- 输出三个文件:
|
package/dist/cli.js
CHANGED
|
@@ -428,7 +428,7 @@ async function runExport(options) {
|
|
|
428
428
|
exportUserName = gitIdentity.userName;
|
|
429
429
|
}
|
|
430
430
|
const weeklySummary = {
|
|
431
|
-
schemaVersion:
|
|
431
|
+
schemaVersion: 11,
|
|
432
432
|
generatedAt: new Date().toISOString(),
|
|
433
433
|
range: {
|
|
434
434
|
label: window.label,
|
|
@@ -524,7 +524,7 @@ async function runExport(options) {
|
|
|
524
524
|
};
|
|
525
525
|
});
|
|
526
526
|
const bundle = {
|
|
527
|
-
schemaVersion:
|
|
527
|
+
schemaVersion: 11,
|
|
528
528
|
generatedAt: new Date().toISOString(),
|
|
529
529
|
range: {
|
|
530
530
|
label: window.label,
|
package/dist/lib/aggregate.js
CHANGED
|
@@ -55,7 +55,12 @@ function hasApiEquivalentCostResultShape(value) {
|
|
|
55
55
|
value.pricedApiRequestCount >= 0 &&
|
|
56
56
|
typeof value.unpricedApiRequestCount === "number" &&
|
|
57
57
|
Number.isInteger(value.unpricedApiRequestCount) &&
|
|
58
|
-
value.unpricedApiRequestCount >= 0
|
|
58
|
+
value.unpricedApiRequestCount >= 0 &&
|
|
59
|
+
(value.unpricedModels === undefined || (Array.isArray(value.unpricedModels) &&
|
|
60
|
+
value.unpricedModels.every((item) => isRecord(item) &&
|
|
61
|
+
(typeof item.model === "string" || item.model === null) &&
|
|
62
|
+
typeof item.requestCount === "number" && Number.isSafeInteger(item.requestCount) && item.requestCount > 0) &&
|
|
63
|
+
value.unpricedModels.reduce((sum, item) => sum + item.requestCount, 0) === value.unpricedApiRequestCount)));
|
|
59
64
|
}
|
|
60
65
|
function hasApiEquivalentCostBreakdownShape(value) {
|
|
61
66
|
return (isRecord(value) &&
|
|
@@ -74,7 +79,7 @@ function isWeeklyExportBundle(value) {
|
|
|
74
79
|
if (!isRecord(value)) {
|
|
75
80
|
return false;
|
|
76
81
|
}
|
|
77
|
-
if (typeof value.schemaVersion !== "number" || ![6, 7, 8, 9, 10].includes(value.schemaVersion)) {
|
|
82
|
+
if (typeof value.schemaVersion !== "number" || ![6, 7, 8, 9, 10, 11].includes(value.schemaVersion)) {
|
|
78
83
|
return false;
|
|
79
84
|
}
|
|
80
85
|
if (!Array.isArray(value.rawEvents) || !Array.isArray(value.dailySummaries) || !isRecord(value.weeklySummary) || !isRecord(value.identity) || !isRecord(value.range)) {
|
|
@@ -83,7 +88,7 @@ function isWeeklyExportBundle(value) {
|
|
|
83
88
|
if (!hasWeeklyStatuslineShape(value.weeklySummary.statusline)) {
|
|
84
89
|
return false;
|
|
85
90
|
}
|
|
86
|
-
if (value.schemaVersion
|
|
91
|
+
if (value.schemaVersion < 10) {
|
|
87
92
|
return true;
|
|
88
93
|
}
|
|
89
94
|
return (hasPricingMetadataShape(value.pricing) &&
|
|
@@ -163,7 +168,7 @@ async function loadWeeklyExportBundles(inputDir) {
|
|
|
163
168
|
}
|
|
164
169
|
}
|
|
165
170
|
if (invalidFiles.length > 0) {
|
|
166
|
-
throw new Error(`Unsupported export bundle schema in files: ${invalidFiles.join(", ")}. Re-export with current ccus so aggregate receives schemaVersion 6/7/8/9/10 bundles.`);
|
|
171
|
+
throw new Error(`Unsupported export bundle schema in files: ${invalidFiles.join(", ")}. Re-export with current ccus so aggregate receives schemaVersion 6/7/8/9/10/11 bundles.`);
|
|
167
172
|
}
|
|
168
173
|
return bundles;
|
|
169
174
|
}
|
|
@@ -563,7 +568,7 @@ function codexOf(day) {
|
|
|
563
568
|
* 把代表日转换为成本贡献。v10 使用按请求计价后导出的日汇总成本;旧版无法补算,全部请求视为未定价。
|
|
564
569
|
*/
|
|
565
570
|
function apiCostContribution(rep) {
|
|
566
|
-
if (rep.bundle.schemaVersion
|
|
571
|
+
if (rep.bundle.schemaVersion >= 10) {
|
|
567
572
|
return {
|
|
568
573
|
result: rep.day.apiEquivalentCost.total,
|
|
569
574
|
catalogVersion: rep.bundle.pricing.catalogVersion,
|
|
@@ -162,7 +162,10 @@ function emptyApiEquivalentCost() {
|
|
|
162
162
|
function priceApiRequest(request, catalog = exports.API_PRICING_CATALOG) {
|
|
163
163
|
const price = findApiModelPrice(request, catalog);
|
|
164
164
|
if (!price) {
|
|
165
|
-
return {
|
|
165
|
+
return {
|
|
166
|
+
estimatedUsd: null, pricedApiRequestCount: 0, unpricedApiRequestCount: 1,
|
|
167
|
+
unpricedModels: [{ model: request.model, requestCount: 1 }],
|
|
168
|
+
};
|
|
166
169
|
}
|
|
167
170
|
return {
|
|
168
171
|
estimatedUsd: computeRequestUsd(request, price),
|
|
@@ -175,7 +178,11 @@ function mergeApiEquivalentCosts(results) {
|
|
|
175
178
|
let estimatedUsd = 0;
|
|
176
179
|
let pricedApiRequestCount = 0;
|
|
177
180
|
let unpricedApiRequestCount = 0;
|
|
181
|
+
const models = new Map();
|
|
178
182
|
for (const result of results) {
|
|
183
|
+
for (const { model, requestCount } of result.unpricedModels ?? []) {
|
|
184
|
+
models.set(model, (models.get(model) ?? 0) + requestCount);
|
|
185
|
+
}
|
|
179
186
|
pricedApiRequestCount += result.pricedApiRequestCount;
|
|
180
187
|
unpricedApiRequestCount += result.unpricedApiRequestCount;
|
|
181
188
|
if (result.estimatedUsd !== null) {
|
|
@@ -186,6 +193,7 @@ function mergeApiEquivalentCosts(results) {
|
|
|
186
193
|
estimatedUsd: pricedApiRequestCount > 0 || unpricedApiRequestCount === 0 ? estimatedUsd : null,
|
|
187
194
|
pricedApiRequestCount,
|
|
188
195
|
unpricedApiRequestCount,
|
|
196
|
+
...(models.size > 0 ? { unpricedModels: [...models].sort(([a], [b]) => a === b ? 0 : a === null ? -1 : b === null ? 1 : a < b ? -1 : 1).map(([model, requestCount]) => ({ model, requestCount })) } : {}),
|
|
189
197
|
};
|
|
190
198
|
}
|
|
191
199
|
validatePricingCatalog(exports.API_PRICING_CATALOG);
|