ccus-cli 0.1.5 → 0.1.6

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
@@ -5,8 +5,8 @@
5
5
  - `ccus install`:自动把 statusLine 命令写进 Claude Code 的 `settings.json`,省去手动改配置。
6
6
  - `ccus statusline emit`:读取 Claude Code statusline 通过 `stdin` 传入的 JSON,输出 statusline 文本,并写入本地日志(加 `--no-store` / `--no-log` 则只输出、不落盘)。
7
7
  - `ccus dashboard serve`:直接启动本地 Web 页面,不用先手动生成 HTML 文件。
8
- - `ccus export`:默认导出当前周数据包,里面同时包含原始事件和按天维度的周汇总。
9
- - `ccus aggregate`:读取一个目录里的多人 export bundle json,输出明细、按天、按周三个 CSV。
8
+ - `ccus export`:默认导出当前周数据包(gzip 压缩的 `.json.gz`),里面同时包含原始事件和按天维度的周汇总。
9
+ - `ccus aggregate`:读取一个目录里的多人 export bundle(`.json.gz` 或 `.json`),输出明细、按天、按周三个 CSV。
10
10
  - `ccus aggregate serve`:同样以 bundle 目录为输入,启动本地多人 dashboard 页面,不落地任何文件。
11
11
  - `ccus update`:主动检查 npm 上是否有新版本,有则提示手动升级命令;`ccus --version` 查看当前版本。
12
12
 
@@ -113,7 +113,7 @@ ccus export --range today
113
113
  ccus export --range last-week
114
114
  ccus export lw # 位置参数简写,等价于 --range last-week
115
115
  ccus export tw # 等价于 --range this-week
116
- ccus export --out ./alice_export_2026-05-26_to_2026-06-01.json
116
+ ccus export --out ./alice_export_2026-05-26_to_2026-06-01.json # --out 指定非 .gz 路径时写明文 JSON
117
117
  ccus aggregate --input-dir ./team-exports --out-dir ./team-report
118
118
  ccus aggregate serve --input-dir ./team-exports
119
119
  ```
@@ -135,8 +135,10 @@ ccus aggregate serve --input-dir ./team-exports
135
135
  - 默认导出 `this-week`;如需导出上一个完整周(周一到周日),用 `--range last-week`,或位置参数简写 `ccus export lw`(`tw` = 本周)
136
136
  - 周度导出固定覆盖**完整一周(周一到周日)**:`this-week` 即使本周还没过完、后面几天还没有任何数据,文件名的起止日期也会补齐到本周日,`dailySummaries` 同样按整周 7 天逐日输出
137
137
  - 默认输出一个 `json` 数据包,里面同时包含 `rawEvents`、`weeklySummary`、`dailySummaries`
138
+ - 导出文件内容为**紧凑 JSON**(无缩进),并默认 **gzip 压缩**后写成 `.json.gz`;gzip 与紧凑化都只是存储/展示层变化,解压后的字段集合与 `schemaVersion` 不变
139
+ - 默认写 `.json.gz`;若用 `--out` 指定一个非 `.gz` 结尾的路径,则按明文 JSON 写出(不压缩)
138
140
  - 当前导出 bundle / weeklySummary 的 `schemaVersion` 为 `6`,用于标识已使用 `fiveHourLatestUsagePct`、`fiveHourPeakUsagePct`、`sevenDayLatestUsagePct`、`sevenDayPeakUsagePct` 字段的新导出契约
139
- - 默认文件名会带 git email 的帐号名前缀和起止日期,例如:`alice_export_2026-05-26_to_2026-06-01.json`
141
+ - 默认文件名会带 git email 的帐号名前缀和起止日期,例如:`alice_export_2026-05-26_to_2026-06-01.json.gz`
140
142
  - `userMessageCount` 来自 `~/.claude/projects/**/*.jsonl` 的非 meta `type:user` 事件
141
143
  - `apiRequestCount` 与 token 指标来自 `~/.claude/projects/**/*.jsonl` 中带 `message.usage` 的 `type:assistant` 事件
142
144
  - `dailySummaries` 会按每天输出消息数、请求数、token 和当天 statusline usage 摘要
@@ -144,7 +146,7 @@ ccus aggregate serve --input-dir ./team-exports
144
146
 
145
147
  ## 多人汇总
146
148
 
147
- - 输入目录放很多通过 `ccus export` 导出的 bundle `.json` 文件
149
+ - 输入目录放很多通过 `ccus export` 导出的 bundle 文件,`.json.gz`(gzip 压缩)与明文 `.json` 都能识别,gzip 文件读取时自动解压
148
150
  - `aggregate` 目前只接受 `schemaVersion: 6` 的 bundle;旧导出请先用当前版本重新 `ccus export`
149
151
  - `ccus aggregate --input-dir DIR --out-dir DIR`
150
152
  - 输出三个文件:
package/dist/cli.js CHANGED
@@ -406,10 +406,17 @@ async function handleExport(options) {
406
406
  const content = (0, export_1.buildWeeklyExportBundleJson)(bundle);
407
407
  const fileLabel = (0, time_1.formatRangeFileLabel)(window.start, window.end);
408
408
  const gitEmailPrefix = (0, time_1.formatGitEmailFilePrefix)(exportUserEmail);
409
- const defaultFileName = gitEmailPrefix ? `${gitEmailPrefix}_export_${fileLabel}.json` : `export_${fileLabel}.json`;
409
+ // 默认导出 gzip 压缩的 bundle(.json.gz)以缩减体积;用户用 --out 指定非 .gz 路径时仍写明文 JSON。
410
+ const defaultFileName = gitEmailPrefix ? `${gitEmailPrefix}_export_${fileLabel}.json.gz` : `export_${fileLabel}.json.gz`;
410
411
  const outputPath = node_path_1.default.resolve(output ?? node_path_1.default.join(dataDir, "exports", defaultFileName));
411
- await (0, export_1.writeTextFile)(outputPath, content);
412
- (0, debug_1.debugLog)("export", "bundle written", { outputPath, bytes: content.length });
412
+ const compressed = outputPath.endsWith(".gz");
413
+ if (compressed) {
414
+ await (0, export_1.writeGzipFile)(outputPath, content);
415
+ }
416
+ else {
417
+ await (0, export_1.writeTextFile)(outputPath, content);
418
+ }
419
+ (0, debug_1.debugLog)("export", "bundle written", { outputPath, rawBytes: content.length, compressed });
413
420
  process.stdout.write(`${outputPath}\n`);
414
421
  }
415
422
  /**
@@ -9,8 +9,11 @@ exports.buildAggregatedDailyRows = buildAggregatedDailyRows;
9
9
  exports.buildAggregatedWeeklyRows = buildAggregatedWeeklyRows;
10
10
  const promises_1 = __importDefault(require("node:fs/promises"));
11
11
  const node_path_1 = __importDefault(require("node:path"));
12
+ const node_util_1 = require("node:util");
13
+ const node_zlib_1 = require("node:zlib");
12
14
  const payload_1 = require("./payload");
13
15
  const time_1 = require("./time");
16
+ const gunzipAsync = (0, node_util_1.promisify)(node_zlib_1.gunzip);
14
17
  function isRecord(value) {
15
18
  return typeof value === "object" && value !== null && !Array.isArray(value);
16
19
  }
@@ -59,6 +62,10 @@ function weekKey(date) {
59
62
  function toPersonKey(gitUserEmail, gitUserName) {
60
63
  return (0, time_1.extractGitEmailAccount)(gitUserEmail) ?? gitUserName ?? "unknown";
61
64
  }
65
+ /** bundle 文件可以是明文 `.json`,也可以是 `ccus export` 默认输出的 gzip 压缩 `.json.gz`。 */
66
+ function isBundleFileName(name) {
67
+ return name.endsWith(".json") || name.endsWith(".json.gz");
68
+ }
62
69
  async function collectBundleJsonFiles(directoryPath) {
63
70
  const entries = await promises_1.default.readdir(directoryPath, { withFileTypes: true });
64
71
  const nested = await Promise.all(entries.map(async (entry) => {
@@ -66,10 +73,19 @@ async function collectBundleJsonFiles(directoryPath) {
66
73
  if (entry.isDirectory()) {
67
74
  return collectBundleJsonFiles(fullPath);
68
75
  }
69
- return entry.isFile() && entry.name.endsWith(".json") ? [fullPath] : [];
76
+ return entry.isFile() && isBundleFileName(entry.name) ? [fullPath] : [];
70
77
  }));
71
78
  return nested.flat();
72
79
  }
80
+ /** 读取 bundle 文件内容:`.gz` 结尾的先 gunzip 再按 UTF-8 解码,其它按明文读取。 */
81
+ async function readBundleFileContent(filePath) {
82
+ if (filePath.endsWith(".gz")) {
83
+ const compressed = await promises_1.default.readFile(filePath);
84
+ const decompressed = await gunzipAsync(compressed);
85
+ return decompressed.toString("utf8");
86
+ }
87
+ return promises_1.default.readFile(filePath, "utf8");
88
+ }
73
89
  /** 读取目录里的 export bundle json 文件。 */
74
90
  async function loadWeeklyExportBundles(inputDir) {
75
91
  const files = await collectBundleJsonFiles(inputDir);
@@ -77,7 +93,7 @@ async function loadWeeklyExportBundles(inputDir) {
77
93
  const invalidFiles = [];
78
94
  for (const filePath of files) {
79
95
  try {
80
- const content = await promises_1.default.readFile(filePath, "utf8");
96
+ const content = await readBundleFileContent(filePath);
81
97
  const parsed = JSON.parse(content);
82
98
  if (isWeeklyExportBundle(parsed)) {
83
99
  bundles.push({ filePath, bundle: parsed });
@@ -13,9 +13,13 @@ exports.buildSummaryCsv = buildSummaryCsv;
13
13
  exports.buildAggregatedDailyCsv = buildAggregatedDailyCsv;
14
14
  exports.buildAggregatedWeeklyCsv = buildAggregatedWeeklyCsv;
15
15
  exports.writeTextFile = writeTextFile;
16
+ exports.writeGzipFile = writeGzipFile;
16
17
  const promises_1 = __importDefault(require("node:fs/promises"));
17
18
  const node_path_1 = __importDefault(require("node:path"));
19
+ const node_util_1 = require("node:util");
20
+ const node_zlib_1 = require("node:zlib");
18
21
  const time_1 = require("./time");
22
+ const gzipAsync = (0, node_util_1.promisify)(node_zlib_1.gzip);
19
23
  /** CSV 字符串字段统一做转义,避免逗号和引号破坏列结构。 */
20
24
  function quoteCsv(value) {
21
25
  return `"${value.replaceAll("\"", "\"\"")}"`;
@@ -128,9 +132,9 @@ function buildRawJsonl(events) {
128
132
  function buildWeeklySummaryJson(summary) {
129
133
  return `${JSON.stringify(summary, null, 2)}\n`;
130
134
  }
131
- /** 默认导出把原始事件与周汇总一起打包成一个 JSON 文件。 */
135
+ /** 默认导出把原始事件与周汇总一起打包成一个 JSON 文件,用紧凑序列化避免缩进空白撑大体积。 */
132
136
  function buildWeeklyExportBundleJson(bundle) {
133
- return `${JSON.stringify(bundle, null, 2)}\n`;
137
+ return `${JSON.stringify(bundle)}\n`;
134
138
  }
135
139
  /** 按天汇总 usage 数据,生成 summary 模式的中间结果。 */
136
140
  function buildSummaryRows(events) {
@@ -272,4 +276,15 @@ async function writeTextFile(outputPath, content) {
272
276
  await promises_1.default.mkdir(node_path_1.default.dirname(outputPath), { recursive: true });
273
277
  await promises_1.default.writeFile(outputPath, content, "utf8");
274
278
  }
279
+ /**
280
+ * 把内容 gzip 压缩后写文件,父目录不存在时自动创建。
281
+ *
282
+ * 仅压缩存储/传输层,写入的字节解压后与 `writeTextFile` 完全一致,
283
+ * 不改变 bundle 字段集合,也不影响 schemaVersion。
284
+ */
285
+ async function writeGzipFile(outputPath, content) {
286
+ await promises_1.default.mkdir(node_path_1.default.dirname(outputPath), { recursive: true });
287
+ const compressed = await gzipAsync(content);
288
+ await promises_1.default.writeFile(outputPath, compressed);
289
+ }
275
290
  //# sourceMappingURL=export.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccus-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Claude Code statusline usage logger and dashboard CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {