ccus-cli 0.1.4 → 0.1.5
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 +22 -0
- package/dist/cli.js +58 -2
- package/dist/lib/paths.js +5 -0
- package/dist/lib/update-check.js +180 -0
- package/dist/lib/version.js +62 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
- `ccus export`:默认导出当前周数据包,里面同时包含原始事件和按天维度的周汇总。
|
|
9
9
|
- `ccus aggregate`:读取一个目录里的多人 export bundle json,输出明细、按天、按周三个 CSV。
|
|
10
10
|
- `ccus aggregate serve`:同样以 bundle 目录为输入,启动本地多人 dashboard 页面,不落地任何文件。
|
|
11
|
+
- `ccus update`:主动检查 npm 上是否有新版本,有则提示手动升级命令;`ccus --version` 查看当前版本。
|
|
11
12
|
|
|
12
13
|
> **支持范围**:`ccus statusline emit` 依赖 Claude Code 的 statusLine 机制(从 `stdin` 读 JSON、向 `stdout` 回一行文本),**只在命令行版 Claude Code(CLI / 终端)里生效**。
|
|
13
14
|
>
|
|
@@ -23,6 +24,27 @@ npm install -g ccus-cli
|
|
|
23
24
|
|
|
24
25
|
要求 Node.js >= 20。
|
|
25
26
|
|
|
27
|
+
## 更新检查
|
|
28
|
+
|
|
29
|
+
ccus 通过 npm 全局安装,自带一个轻量的更新检查:
|
|
30
|
+
|
|
31
|
+
- statusline 渲染时会**异步、节流(每天最多一次)**地向 npm registry 查询最新版本,结果缓存到数据目录下的 `update-check.json`。检查在 detached 后台进程里完成,**不阻塞 statusline、不污染单行输出**。
|
|
32
|
+
- 一旦发现有更新,statusline 行尾会追加一个小标记,例如 `… | ⏱ 11:44 | ⬆ v0.1.5`。
|
|
33
|
+
- 看到标记后手动升级即可:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npm i -g ccus-cli@latest
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
也可以随时主动检查:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
ccus update # 立即查 registry,有新版本则打印升级命令
|
|
43
|
+
ccus --version # 查看当前安装的版本
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
> 出于稳妥考虑,ccus **只提示、不自动替你执行全局安装**。如需走私服或镜像(如 npmmirror),设置环境变量 `CCUS_REGISTRY` 指向对应 registry 即可。
|
|
47
|
+
|
|
26
48
|
## 快速开始
|
|
27
49
|
|
|
28
50
|
全局安装后,一条命令把 statusline 接进 Claude Code:
|
package/dist/cli.js
CHANGED
|
@@ -55,9 +55,11 @@ const payload_1 = require("./lib/payload");
|
|
|
55
55
|
const paths_1 = require("./lib/paths");
|
|
56
56
|
const storage_1 = require("./lib/storage");
|
|
57
57
|
const time_1 = require("./lib/time");
|
|
58
|
+
const update_check_1 = require("./lib/update-check");
|
|
59
|
+
const version_1 = require("./lib/version");
|
|
58
60
|
/** CLI 帮助信息保持简洁,方便直接挂到 README 或终端里查看。 */
|
|
59
61
|
function printHelp() {
|
|
60
|
-
process.stdout.write(`ccus\n\nCommands:\n ccus install [--settings PATH] [--command CMD] [--data-dir PATH]\n ccus statusline emit [--data-dir PATH] [--input FILE] [--no-store]\n ccus dashboard build [--range today|this-week|last-week|5h] [--out FILE] [--data-dir PATH]\n ccus dashboard open [--range today|this-week|last-week|5h] [--out FILE] [--data-dir PATH]\n ccus dashboard serve [--range today|this-week|last-week|5h] [--port 0] [--host 127.0.0.1] [--open] [--data-dir PATH]\n ccus export [RANGE] [--out FILE] [--data-dir PATH] (RANGE: this-week|tw, last-week|lw, today, 5h; e.g. ccus export lw)\n ccus aggregate --input-dir DIR [--out-dir DIR]\n ccus aggregate serve --input-dir DIR [--port 0] [--host 127.0.0.1]\n\nGlobal flags:\n --verbose | --debug | -v 输出详细调试日志到 stderr(等价于设置 CCUS_DEBUG=1),方便排查问题\n`);
|
|
62
|
+
process.stdout.write(`ccus\n\nCommands:\n ccus install [--settings PATH] [--command CMD] [--data-dir PATH]\n ccus statusline emit [--data-dir PATH] [--input FILE] [--no-store]\n ccus dashboard build [--range today|this-week|last-week|5h] [--out FILE] [--data-dir PATH]\n ccus dashboard open [--range today|this-week|last-week|5h] [--out FILE] [--data-dir PATH]\n ccus dashboard serve [--range today|this-week|last-week|5h] [--port 0] [--host 127.0.0.1] [--open] [--data-dir PATH]\n ccus export [RANGE] [--out FILE] [--data-dir PATH] (RANGE: this-week|tw, last-week|lw, today, 5h; e.g. ccus export lw)\n ccus aggregate --input-dir DIR [--out-dir DIR]\n ccus aggregate serve --input-dir DIR [--port 0] [--host 127.0.0.1]\n ccus update [--data-dir PATH]\n ccus --version\n\nGlobal flags:\n --verbose | --debug | -v 输出详细调试日志到 stderr(等价于设置 CCUS_DEBUG=1),方便排查问题\n`);
|
|
61
63
|
}
|
|
62
64
|
/** 一个轻量的参数解析器,当前命令面不复杂,没必要引入额外依赖。 */
|
|
63
65
|
function parseOptions(args) {
|
|
@@ -174,7 +176,12 @@ async function handleStatuslineEmit(options) {
|
|
|
174
176
|
gitUserAccount: event.gitUserAccount,
|
|
175
177
|
gitBranch,
|
|
176
178
|
});
|
|
177
|
-
|
|
179
|
+
// 更新检查必须对 statusline 完全无侵入:同步读旧缓存决定是否在行尾追加小标记,
|
|
180
|
+
// 把网络请求甩给 detached 后台进程,主进程不等待、失败静默。
|
|
181
|
+
(0, update_check_1.maybeSpawnBackgroundCheck)(dataDir);
|
|
182
|
+
const notice = (0, update_check_1.computeUpdateNotice)(dataDir);
|
|
183
|
+
const statusLine = notice ? `${event.statusLine} | ${notice}` : event.statusLine;
|
|
184
|
+
process.stdout.write(`${statusLine}\n`);
|
|
178
185
|
}
|
|
179
186
|
catch (error) {
|
|
180
187
|
// 正常路径不能因为采样失败把 statusline 弄挂,所以这里仍然降级输出兜底文本;
|
|
@@ -518,6 +525,43 @@ function resolveExportOptions(action, args, rest) {
|
|
|
518
525
|
}
|
|
519
526
|
return options;
|
|
520
527
|
}
|
|
528
|
+
/**
|
|
529
|
+
* `ccus update`:用户主动检查更新(绕过 24h 节流)。
|
|
530
|
+
*
|
|
531
|
+
* 根据当前的产品选择,这里只做「检查 + 提示」,不替用户执行全局安装。
|
|
532
|
+
* 有新版本时打印应当手动运行的 `npm i -g` 命令。
|
|
533
|
+
*/
|
|
534
|
+
async function handleUpdate(options) {
|
|
535
|
+
const dataDir = getDataDir(options);
|
|
536
|
+
const current = (0, version_1.getCurrentVersion)();
|
|
537
|
+
let latest;
|
|
538
|
+
try {
|
|
539
|
+
latest = await (0, update_check_1.fetchLatestVersion)();
|
|
540
|
+
}
|
|
541
|
+
catch (error) {
|
|
542
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
543
|
+
process.stdout.write(`无法检查更新:${message}\n当前版本 v${current}\n`);
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
// 顺手刷新缓存,让 statusline 标记和这次检查结果保持一致。
|
|
547
|
+
await (0, update_check_1.performUpdateCheck)(dataDir);
|
|
548
|
+
if ((0, version_1.isNewerVersion)(latest, current)) {
|
|
549
|
+
process.stdout.write(`发现新版本:v${current} -> v${latest}\n运行以下命令升级:\n npm i -g ccus-cli@latest\n`);
|
|
550
|
+
}
|
|
551
|
+
else {
|
|
552
|
+
process.stdout.write(`已是最新版本 v${current}\n`);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* 隐藏命令 `__check-update`:由 statusline 路径以 detached 后台进程触发。
|
|
557
|
+
*
|
|
558
|
+
* 只做一件事:查 registry 并写缓存。不输出任何东西到 stdout(它不是被人看的),
|
|
559
|
+
* 失败也静默,绝不影响触发它的 statusline 主进程。
|
|
560
|
+
*/
|
|
561
|
+
async function handleBackgroundCheckUpdate(options) {
|
|
562
|
+
const dataDir = getDataDir(options);
|
|
563
|
+
await (0, update_check_1.performUpdateCheck)(dataDir);
|
|
564
|
+
}
|
|
521
565
|
/** 顶层命令分发入口。 */
|
|
522
566
|
async function main(args = process.argv.slice(2)) {
|
|
523
567
|
(0, debug_1.setDebugEnabled)((0, debug_1.resolveDebugEnabled)(args));
|
|
@@ -528,6 +572,18 @@ async function main(args = process.argv.slice(2)) {
|
|
|
528
572
|
printHelp();
|
|
529
573
|
return;
|
|
530
574
|
}
|
|
575
|
+
if (group === "--version" || group === "-V" || group === "version") {
|
|
576
|
+
process.stdout.write(`${(0, version_1.getCurrentVersion)()}\n`);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
if (group === "update") {
|
|
580
|
+
await handleUpdate(parseOptions(args.slice(1)));
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
if (group === "__check-update") {
|
|
584
|
+
await handleBackgroundCheckUpdate(parseOptions(args.slice(1)));
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
531
587
|
if (group === "install") {
|
|
532
588
|
await handleInstall(parseOptions(args.slice(1)));
|
|
533
589
|
return;
|
package/dist/lib/paths.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.getDefaultDataDir = getDefaultDataDir;
|
|
7
7
|
exports.getEventsDir = getEventsDir;
|
|
8
8
|
exports.getDashboardDir = getDashboardDir;
|
|
9
|
+
exports.getUpdateCachePath = getUpdateCachePath;
|
|
9
10
|
exports.getClaudeDataDir = getClaudeDataDir;
|
|
10
11
|
exports.getClaudeSettingsPath = getClaudeSettingsPath;
|
|
11
12
|
const node_os_1 = __importDefault(require("node:os"));
|
|
@@ -41,6 +42,10 @@ function getEventsDir(dataDir) {
|
|
|
41
42
|
function getDashboardDir(dataDir) {
|
|
42
43
|
return node_path_1.default.join(dataDir, "dashboard");
|
|
43
44
|
}
|
|
45
|
+
/** 版本更新检查的本地缓存文件,记录上次检查时间与拿到的最新版本。 */
|
|
46
|
+
function getUpdateCachePath(dataDir) {
|
|
47
|
+
return node_path_1.default.join(dataDir, "update-check.json");
|
|
48
|
+
}
|
|
44
49
|
/** Claude Code 默认本地数据目录。 */
|
|
45
50
|
function getClaudeDataDir() {
|
|
46
51
|
const configured = process.env.CCUS_CLAUDE_DATA_DIR;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readUpdateCacheSync = readUpdateCacheSync;
|
|
7
|
+
exports.fetchLatestVersion = fetchLatestVersion;
|
|
8
|
+
exports.performUpdateCheck = performUpdateCheck;
|
|
9
|
+
exports.maybeSpawnBackgroundCheck = maybeSpawnBackgroundCheck;
|
|
10
|
+
exports.computeUpdateNotice = computeUpdateNotice;
|
|
11
|
+
const node_child_process_1 = require("node:child_process");
|
|
12
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
13
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
14
|
+
const node_https_1 = __importDefault(require("node:https"));
|
|
15
|
+
const debug_1 = require("./debug");
|
|
16
|
+
const paths_1 = require("./paths");
|
|
17
|
+
const version_1 = require("./version");
|
|
18
|
+
/** npm 包名,更新检查向 registry 查询它的最新版本。 */
|
|
19
|
+
const PACKAGE_NAME = "ccus-cli";
|
|
20
|
+
/** 后台检查节流窗口:同一数据目录每天最多向 registry 查一次。 */
|
|
21
|
+
const CHECK_TTL_MS = 24 * 60 * 60 * 1000;
|
|
22
|
+
/** 网络请求超时,避免后台进程长时间挂着;statusline 主进程本身不等它。 */
|
|
23
|
+
const FETCH_TIMEOUT_MS = 4000;
|
|
24
|
+
/**
|
|
25
|
+
* 同步读取更新缓存。
|
|
26
|
+
*
|
|
27
|
+
* statusline 是高频短命进程,这里只读一个很小的 JSON,用同步 IO 换取最简单、最快的路径;
|
|
28
|
+
* 任何异常都静默返回 null,绝不影响状态行输出。
|
|
29
|
+
*/
|
|
30
|
+
function readUpdateCacheSync(dataDir) {
|
|
31
|
+
try {
|
|
32
|
+
const raw = node_fs_1.default.readFileSync((0, paths_1.getUpdateCachePath)(dataDir), "utf8");
|
|
33
|
+
const parsed = JSON.parse(raw);
|
|
34
|
+
if (typeof parsed.lastCheckedAt !== "string") {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
lastCheckedAt: parsed.lastCheckedAt,
|
|
39
|
+
latestVersion: typeof parsed.latestVersion === "string" ? parsed.latestVersion : null,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** 写入更新缓存;目录可能尚未创建,所以先 mkdir。失败静默。 */
|
|
47
|
+
async function writeUpdateCache(dataDir, cache) {
|
|
48
|
+
try {
|
|
49
|
+
await promises_1.default.mkdir(dataDir, { recursive: true });
|
|
50
|
+
await promises_1.default.writeFile((0, paths_1.getUpdateCachePath)(dataDir), `${JSON.stringify(cache, null, 2)}\n`, "utf8");
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
(0, debug_1.debugLog)("update", "failed to write cache", error instanceof Error ? error.message : String(error));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/** registry 基地址,允许用 CCUS_REGISTRY 指向私服或镜像(如 npmmirror)。 */
|
|
57
|
+
function getRegistryBase(env = process.env) {
|
|
58
|
+
const configured = (env.CCUS_REGISTRY ?? "").trim();
|
|
59
|
+
const base = configured || "https://registry.npmjs.org";
|
|
60
|
+
return base.replace(/\/+$/, "");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 向 npm registry 查询最新发布版本。
|
|
64
|
+
*
|
|
65
|
+
* 走 `/<pkg>/latest` 这个轻量端点,只取 `version` 字段;超时或任何错误都 reject,
|
|
66
|
+
* 由调用方决定是否静默。
|
|
67
|
+
*/
|
|
68
|
+
function fetchLatestVersion(env = process.env) {
|
|
69
|
+
const url = `${getRegistryBase(env)}/${PACKAGE_NAME}/latest`;
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const request = node_https_1.default.get(url, { headers: { accept: "application/json" } }, (response) => {
|
|
72
|
+
const status = response.statusCode ?? 0;
|
|
73
|
+
if (status < 200 || status >= 300) {
|
|
74
|
+
response.resume();
|
|
75
|
+
reject(new Error(`registry responded ${status}`));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
let body = "";
|
|
79
|
+
response.setEncoding("utf8");
|
|
80
|
+
response.on("data", (chunk) => {
|
|
81
|
+
body += chunk;
|
|
82
|
+
});
|
|
83
|
+
response.on("end", () => {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(body);
|
|
86
|
+
if (typeof parsed.version === "string") {
|
|
87
|
+
resolve(parsed.version);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
reject(new Error("registry payload missing version"));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
request.setTimeout(FETCH_TIMEOUT_MS, () => {
|
|
99
|
+
request.destroy(new Error("registry request timed out"));
|
|
100
|
+
});
|
|
101
|
+
request.on("error", reject);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* 真正执行一次检查并把结果写进缓存。
|
|
106
|
+
*
|
|
107
|
+
* 供隐藏命令 `__check-update`(statusline 后台触发)和 `ccus update`(用户主动)复用。
|
|
108
|
+
* 即使 registry 失败也会更新 lastCheckedAt,避免反复重试打爆 registry。
|
|
109
|
+
*/
|
|
110
|
+
async function performUpdateCheck(dataDir) {
|
|
111
|
+
let latestVersion = null;
|
|
112
|
+
try {
|
|
113
|
+
latestVersion = await fetchLatestVersion();
|
|
114
|
+
(0, debug_1.debugLog)("update", "fetched latest version", latestVersion);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
(0, debug_1.debugLog)("update", "fetch failed", error instanceof Error ? error.message : String(error));
|
|
118
|
+
}
|
|
119
|
+
const cache = {
|
|
120
|
+
lastCheckedAt: new Date().toISOString(),
|
|
121
|
+
latestVersion,
|
|
122
|
+
};
|
|
123
|
+
await writeUpdateCache(dataDir, cache);
|
|
124
|
+
return cache;
|
|
125
|
+
}
|
|
126
|
+
/** 缓存是否已过 TTL(或根本不存在),需要重新向 registry 查询。 */
|
|
127
|
+
function isCacheStale(cache, now) {
|
|
128
|
+
if (!cache) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
const last = Date.parse(cache.lastCheckedAt);
|
|
132
|
+
if (!Number.isFinite(last)) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return now.getTime() - last >= CHECK_TTL_MS;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* 如缓存过期,spawn 一个 detached 后台进程刷新它,主进程不等待。
|
|
139
|
+
*
|
|
140
|
+
* 这是 statusline 路径能用的唯一安全方式:主进程同步读旧缓存决定是否提示,
|
|
141
|
+
* 把网络请求甩给后台子进程,自己立刻输出单行状态文本并退出,绝不阻塞。
|
|
142
|
+
*/
|
|
143
|
+
function maybeSpawnBackgroundCheck(dataDir, now = new Date()) {
|
|
144
|
+
try {
|
|
145
|
+
if (!isCacheStale(readUpdateCacheSync(dataDir), now)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
const scriptPath = process.argv[1];
|
|
149
|
+
if (!scriptPath) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const child = (0, node_child_process_1.spawn)(process.execPath, [scriptPath, "__check-update", "--data-dir", dataDir], {
|
|
153
|
+
detached: true,
|
|
154
|
+
stdio: "ignore",
|
|
155
|
+
windowsHide: true,
|
|
156
|
+
});
|
|
157
|
+
child.on("error", () => { });
|
|
158
|
+
child.unref();
|
|
159
|
+
(0, debug_1.debugLog)("update", "spawned background check");
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
(0, debug_1.debugLog)("update", "failed to spawn background check", error instanceof Error ? error.message : String(error));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* 根据缓存计算 statusline 末尾要追加的更新标记。
|
|
167
|
+
*
|
|
168
|
+
* 有严格新于当前版本的发布时返回形如 `⬆ v0.1.5` 的短标记,否则返回 null。
|
|
169
|
+
*/
|
|
170
|
+
function computeUpdateNotice(dataDir, currentVersion = (0, version_1.getCurrentVersion)()) {
|
|
171
|
+
const cache = readUpdateCacheSync(dataDir);
|
|
172
|
+
if (!cache || !cache.latestVersion) {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
if (!(0, version_1.isNewerVersion)(cache.latestVersion, currentVersion)) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return `⬆ v${cache.latestVersion}`;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=update-check.js.map
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getCurrentVersion = getCurrentVersion;
|
|
7
|
+
exports.isNewerVersion = isNewerVersion;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
/**
|
|
11
|
+
* 读取 ccus 自身的版本号(package.json 的 version)。
|
|
12
|
+
*
|
|
13
|
+
* dist/lib/version.js 与 src/lib/version.ts 距离包根都是 `../..`,
|
|
14
|
+
* 所以编译产物和 tsx 直跑源码时都能定位到同一个 package.json。
|
|
15
|
+
* 读不到时回退 "0.0.0",让上层逻辑(更新检查)安全降级而不是抛错。
|
|
16
|
+
*/
|
|
17
|
+
let cachedVersion = null;
|
|
18
|
+
function getCurrentVersion() {
|
|
19
|
+
if (cachedVersion !== null) {
|
|
20
|
+
return cachedVersion;
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const packageJsonPath = node_path_1.default.join(__dirname, "..", "..", "package.json");
|
|
24
|
+
const raw = node_fs_1.default.readFileSync(packageJsonPath, "utf8");
|
|
25
|
+
const parsed = JSON.parse(raw);
|
|
26
|
+
cachedVersion = typeof parsed.version === "string" ? parsed.version : "0.0.0";
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
cachedVersion = "0.0.0";
|
|
30
|
+
}
|
|
31
|
+
return cachedVersion;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 语义化版本比较:判断 latest 是否严格新于 current。
|
|
35
|
+
*
|
|
36
|
+
* 只比较 `major.minor.patch` 三段数字,忽略预发布/构建元数据,
|
|
37
|
+
* 对当前发布节奏足够;任意一段解析失败按 0 处理,避免误报更新。
|
|
38
|
+
*/
|
|
39
|
+
function isNewerVersion(latest, current) {
|
|
40
|
+
const parse = (value) => value
|
|
41
|
+
.trim()
|
|
42
|
+
.replace(/^v/, "")
|
|
43
|
+
.split("-", 1)[0]
|
|
44
|
+
.split(".")
|
|
45
|
+
.map((part) => Number.parseInt(part, 10))
|
|
46
|
+
.map((part) => (Number.isFinite(part) ? part : 0));
|
|
47
|
+
const a = parse(latest);
|
|
48
|
+
const b = parse(current);
|
|
49
|
+
const length = Math.max(a.length, b.length);
|
|
50
|
+
for (let index = 0; index < length; index += 1) {
|
|
51
|
+
const left = a[index] ?? 0;
|
|
52
|
+
const right = b[index] ?? 0;
|
|
53
|
+
if (left > right) {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
if (left < right) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=version.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccus-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "Claude Code statusline usage logger and dashboard CLI",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"build": "tsc -p tsconfig.json",
|
|
16
16
|
"prepublishOnly": "npm run build",
|
|
17
17
|
"test": "node --test dist/test",
|
|
18
|
-
"test:src": "node --import tsx --test src/test/payload.test.ts src/test/dashboard.test.ts src/test/export.test.ts src/test/storage.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"
|
|
18
|
+
"test:src": "node --import tsx --test src/test/payload.test.ts src/test/dashboard.test.ts src/test/export.test.ts src/test/storage.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/update-check.test.ts"
|
|
19
19
|
},
|
|
20
20
|
"keywords": [
|
|
21
21
|
"claude-code",
|