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 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` 为 `10`。顶层 `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` 单列
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`,导出 schemaVersion 仍为 10
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: 10,
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: 10,
527
+ schemaVersion: 11,
528
528
  generatedAt: new Date().toISOString(),
529
529
  range: {
530
530
  label: window.label,
@@ -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 !== 10) {
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 === 10) {
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 { estimatedUsd: null, pricedApiRequestCount: 0, unpricedApiRequestCount: 1 };
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);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccus-cli",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
4
4
  "description": "Claude Code statusline usage logger and dashboard CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {