ccus-cli 0.1.26 → 0.2.3
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 +51 -4
- package/dist/cli.js +211 -6
- package/dist/lib/aggregate-dashboard.js +1 -1
- package/dist/lib/aggregate.js +69 -37
- package/dist/lib/codex-fetcher.js +339 -0
- package/dist/lib/codex-install.js +228 -0
- package/dist/lib/codex-sessions.js +197 -0
- package/dist/lib/dashboard.js +26 -15
- package/dist/lib/export.js +2 -0
- package/dist/lib/paths.js +20 -0
- package/dist/lib/payload.js +10 -0
- package/package.json +3 -3
|
@@ -0,0 +1,197 @@
|
|
|
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.summarizeCodexSessionUsage = summarizeCodexSessionUsage;
|
|
7
|
+
exports.summarizeCodexSessionUsageByDay = summarizeCodexSessionUsageByDay;
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const paths_1 = require("./paths");
|
|
11
|
+
const time_1 = require("./time");
|
|
12
|
+
function isRecord(value) {
|
|
13
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
14
|
+
}
|
|
15
|
+
function getNumber(value) {
|
|
16
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
17
|
+
}
|
|
18
|
+
function getString(value) {
|
|
19
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
20
|
+
}
|
|
21
|
+
async function collectRolloutFiles(directoryPath) {
|
|
22
|
+
try {
|
|
23
|
+
const entries = await promises_1.default.readdir(directoryPath, { withFileTypes: true });
|
|
24
|
+
const nested = await Promise.all(entries.map(async (entry) => {
|
|
25
|
+
const fullPath = node_path_1.default.join(directoryPath, entry.name);
|
|
26
|
+
if (entry.isDirectory()) {
|
|
27
|
+
return collectRolloutFiles(fullPath);
|
|
28
|
+
}
|
|
29
|
+
return entry.isFile() && entry.name.endsWith(".jsonl") ? [fullPath] : [];
|
|
30
|
+
}));
|
|
31
|
+
return nested.flat();
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
if (error.code === "ENOENT") {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function timestampInRange(timestamp, start, end) {
|
|
41
|
+
if (!timestamp) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
const value = new Date(timestamp).getTime();
|
|
45
|
+
return Number.isFinite(value) && value >= start.getTime() && value <= end.getTime();
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 从 `token_count` 事件的 `info.last_token_usage` 取 token 用量对象(缺失返回 null)。
|
|
49
|
+
* 必须用 last_token_usage(本次增量),不能用 total_token_usage(会话累计,会重复计)。
|
|
50
|
+
*/
|
|
51
|
+
function extractLastTokenUsage(payload) {
|
|
52
|
+
if (!isRecord(payload.info)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
return isRecord(payload.info.last_token_usage) ? payload.info.last_token_usage : null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 统计单个 Codex rollout 文件内的 token / 消息。
|
|
59
|
+
*
|
|
60
|
+
* Codex rollout 每行一个事件,timestamp 在 top-level:
|
|
61
|
+
* - `event_msg` + `payload.type=="user_message"`:一次用户消息(工具结果是 `function_call_output`,不命中,无需过滤)。
|
|
62
|
+
* - `event_msg` + `payload.type=="token_count"`:一次模型请求,token 取 `info.last_token_usage` 增量。
|
|
63
|
+
*/
|
|
64
|
+
function summarizeRollout(content, start, end) {
|
|
65
|
+
const lines = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
66
|
+
const summary = {
|
|
67
|
+
userMessageCount: 0,
|
|
68
|
+
apiRequestCount: 0,
|
|
69
|
+
inputTokens: 0,
|
|
70
|
+
outputTokens: 0,
|
|
71
|
+
cacheReadInputTokens: 0,
|
|
72
|
+
};
|
|
73
|
+
for (const line of lines) {
|
|
74
|
+
try {
|
|
75
|
+
const record = JSON.parse(line);
|
|
76
|
+
if (!isRecord(record) || !timestampInRange(getString(record.timestamp), start, end)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (record.type !== "event_msg" || !isRecord(record.payload)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
const payload = record.payload;
|
|
83
|
+
if (payload.type === "user_message") {
|
|
84
|
+
summary.userMessageCount += 1;
|
|
85
|
+
}
|
|
86
|
+
if (payload.type === "token_count") {
|
|
87
|
+
const usage = extractLastTokenUsage(payload);
|
|
88
|
+
if (usage) {
|
|
89
|
+
summary.apiRequestCount += 1;
|
|
90
|
+
summary.inputTokens += getNumber(usage.input_tokens);
|
|
91
|
+
summary.outputTokens += getNumber(usage.output_tokens);
|
|
92
|
+
summary.cacheReadInputTokens += getNumber(usage.cached_input_tokens);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return summary;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* 从 Codex 本地 session rollout(<CODEX_HOME>/sessions 下递归的 .jsonl)统计消息数、请求数和 token 用量。
|
|
104
|
+
*/
|
|
105
|
+
async function summarizeCodexSessionUsage(start, end) {
|
|
106
|
+
const codexDataDir = (0, paths_1.getCodexHome)();
|
|
107
|
+
const sessionsDir = node_path_1.default.join(codexDataDir, "sessions");
|
|
108
|
+
const files = await collectRolloutFiles(sessionsDir);
|
|
109
|
+
const totals = {
|
|
110
|
+
userMessageCount: 0,
|
|
111
|
+
apiRequestCount: 0,
|
|
112
|
+
inputTokens: 0,
|
|
113
|
+
outputTokens: 0,
|
|
114
|
+
cacheReadInputTokens: 0,
|
|
115
|
+
matchedFileCount: files.length,
|
|
116
|
+
codexDataDir,
|
|
117
|
+
};
|
|
118
|
+
for (const filePath of files) {
|
|
119
|
+
try {
|
|
120
|
+
const content = await promises_1.default.readFile(filePath, "utf8");
|
|
121
|
+
const summary = summarizeRollout(content, start, end);
|
|
122
|
+
totals.userMessageCount += summary.userMessageCount;
|
|
123
|
+
totals.apiRequestCount += summary.apiRequestCount;
|
|
124
|
+
totals.inputTokens += summary.inputTokens;
|
|
125
|
+
totals.outputTokens += summary.outputTokens;
|
|
126
|
+
totals.cacheReadInputTokens += summary.cacheReadInputTokens;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return totals;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 按天汇总 Codex session rollout 中的消息数、请求数和 token 用量。
|
|
136
|
+
*/
|
|
137
|
+
async function summarizeCodexSessionUsageByDay(start, end) {
|
|
138
|
+
const codexDataDir = (0, paths_1.getCodexHome)();
|
|
139
|
+
const sessionsDir = node_path_1.default.join(codexDataDir, "sessions");
|
|
140
|
+
const files = await collectRolloutFiles(sessionsDir);
|
|
141
|
+
const daily = new Map();
|
|
142
|
+
for (const filePath of files) {
|
|
143
|
+
let content = "";
|
|
144
|
+
try {
|
|
145
|
+
content = await promises_1.default.readFile(filePath, "utf8");
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
const lines = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
try {
|
|
153
|
+
const record = JSON.parse(line);
|
|
154
|
+
if (!isRecord(record)) {
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
const timestamp = getString(record.timestamp);
|
|
158
|
+
if (!timestampInRange(timestamp, start, end)) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (record.type !== "event_msg" || !isRecord(record.payload)) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const payload = record.payload;
|
|
165
|
+
const isUserMessage = payload.type === "user_message";
|
|
166
|
+
const usage = payload.type === "token_count" ? extractLastTokenUsage(payload) : null;
|
|
167
|
+
if (!isUserMessage && !usage) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const date = (0, time_1.localDateKey)(new Date(timestamp));
|
|
171
|
+
const current = daily.get(date) ?? {
|
|
172
|
+
date,
|
|
173
|
+
userMessageCount: 0,
|
|
174
|
+
apiRequestCount: 0,
|
|
175
|
+
inputTokens: 0,
|
|
176
|
+
outputTokens: 0,
|
|
177
|
+
cacheReadInputTokens: 0,
|
|
178
|
+
};
|
|
179
|
+
if (isUserMessage) {
|
|
180
|
+
current.userMessageCount += 1;
|
|
181
|
+
}
|
|
182
|
+
if (usage) {
|
|
183
|
+
current.apiRequestCount += 1;
|
|
184
|
+
current.inputTokens += getNumber(usage.input_tokens);
|
|
185
|
+
current.outputTokens += getNumber(usage.output_tokens);
|
|
186
|
+
current.cacheReadInputTokens += getNumber(usage.cached_input_tokens);
|
|
187
|
+
}
|
|
188
|
+
daily.set(date, current);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return daily;
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=codex-sessions.js.map
|
package/dist/lib/dashboard.js
CHANGED
|
@@ -5,6 +5,7 @@ exports.bucketizeEvents = bucketizeEvents;
|
|
|
5
5
|
exports.buildDashboardHtml = buildDashboardHtml;
|
|
6
6
|
const aggregate_1 = require("./aggregate");
|
|
7
7
|
const chart_assets_1 = require("./chart-assets");
|
|
8
|
+
const payload_1 = require("./payload");
|
|
8
9
|
const time_1 = require("./time");
|
|
9
10
|
/** 所有插入到 HTML/SVG 的动态文本都先转义,避免本地页面被注入内容。 */
|
|
10
11
|
function escapeHtml(value) {
|
|
@@ -25,14 +26,14 @@ function average(values) {
|
|
|
25
26
|
* 这里的 usage 指 Claude 的 5 小时额度使用率,而不是 context window 百分比。
|
|
26
27
|
*/
|
|
27
28
|
function summarizeEvents(events) {
|
|
29
|
+
// 额度叠加 Codex(与 aggregate 同口径):peak 取所有读数 max、latest 按 source 分流后相加、7d 累计走合并曲线。
|
|
28
30
|
const usages = events.map((event) => event.usagePct).filter((value) => value !== null);
|
|
29
31
|
const sevenDayUsages = events.map((event) => event.sevenDayUsagePct).filter((value) => value !== null);
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
.find((event) => event.sevenDayUsagePct !== null)?.sevenDayUsagePct ?? null;
|
|
32
|
+
const byDesc = (left, right) => new Date(right.timestamp).getTime() - new Date(left.timestamp).getTime();
|
|
33
|
+
const claudeByDesc = events.filter((event) => !(0, payload_1.isCodexSourceEvent)(event)).sort(byDesc);
|
|
34
|
+
const codexByDesc = events.filter(payload_1.isCodexSourceEvent).sort(byDesc);
|
|
35
|
+
const latestUsagePct = (0, aggregate_1.addNullable)(claudeByDesc.find((event) => event.usagePct !== null)?.usagePct ?? null, codexByDesc.find((event) => event.usagePct !== null)?.usagePct ?? null);
|
|
36
|
+
const latestSevenDayUsagePct = (0, aggregate_1.addNullable)(claudeByDesc.find((event) => event.sevenDayUsagePct !== null)?.sevenDayUsagePct ?? null, codexByDesc.find((event) => event.sevenDayUsagePct !== null)?.sevenDayUsagePct ?? null);
|
|
36
37
|
// 7d 分区叠加累计:与 aggregate 同一套「去毛刺 + 分段峰谷和」,口径与团队看板一致。
|
|
37
38
|
const sevenDayCumulativeUsagePct = (0, aggregate_1.computeCumulativeSevenDay)((0, aggregate_1.buildSevenDayCurveFromEvents)(events));
|
|
38
39
|
return {
|
|
@@ -170,10 +171,12 @@ function renderDailyMessages(points) {
|
|
|
170
171
|
return "";
|
|
171
172
|
}
|
|
172
173
|
const total = points.reduce((sum, point) => sum + point.userMessageCount, 0);
|
|
173
|
-
|
|
174
|
+
const codexTotal = points.reduce((sum, point) => sum + (point.codexUserMessageCount ?? 0), 0);
|
|
175
|
+
// 离散「天」用 category x(索引 0..n-1 + 月-日标签),双系列纵向柱(Claude / Codex);uPlot.paths.bars
|
|
174
176
|
// 画柱、draw-hook 在柱顶标数值,cursor 悬停某天在 legend 显示当日计数。
|
|
175
177
|
const xs = points.map((_, index) => index);
|
|
176
178
|
const counts = points.map((point) => point.userMessageCount);
|
|
179
|
+
const codexCounts = points.map((point) => point.codexUserMessageCount ?? 0);
|
|
177
180
|
const xLabels = points.map((point) => point.date.slice(5));
|
|
178
181
|
const spec = {
|
|
179
182
|
height: 280,
|
|
@@ -181,12 +184,18 @@ function renderDailyMessages(points) {
|
|
|
181
184
|
xLabels,
|
|
182
185
|
yMin0: true,
|
|
183
186
|
bars: true,
|
|
184
|
-
series: [
|
|
185
|
-
|
|
187
|
+
series: [
|
|
188
|
+
{ label: "Claude 消息数", stroke: "#5eead4", fill: "rgba(94, 234, 212, 0.55)", width: 1 },
|
|
189
|
+
{ label: "Codex 消息数", stroke: "#f59e0b", fill: "rgba(245, 158, 11, 0.55)", width: 1 },
|
|
190
|
+
],
|
|
191
|
+
legendGroups: [
|
|
192
|
+
{ label: "Claude 消息数", color: "#5eead4", seriesIdx: [1] },
|
|
193
|
+
{ label: "Codex 消息数", color: "#f59e0b", seriesIdx: [2] },
|
|
194
|
+
],
|
|
186
195
|
};
|
|
187
|
-
const chart = (0, chart_assets_1.renderUplotChart)("chart-daily-messages", spec, [xs, counts]);
|
|
188
|
-
const noData = total === 0
|
|
189
|
-
? `<div class="empty-state">当前时间窗口里还没有统计到 Claude 用户消息。</div>`
|
|
196
|
+
const chart = (0, chart_assets_1.renderUplotChart)("chart-daily-messages", spec, [xs, counts, codexCounts]);
|
|
197
|
+
const noData = total === 0 && codexTotal === 0
|
|
198
|
+
? `<div class="empty-state">当前时间窗口里还没有统计到 Claude / Codex 用户消息。</div>`
|
|
190
199
|
: "";
|
|
191
200
|
return `
|
|
192
201
|
<section class="panel chart-panel">
|
|
@@ -195,7 +204,7 @@ function renderDailyMessages(points) {
|
|
|
195
204
|
<p class="eyebrow">Daily Messages</p>
|
|
196
205
|
<h2>每日用户消息数</h2>
|
|
197
206
|
</div>
|
|
198
|
-
<p class="muted">按自然日统计的真实用户请求数(口径同导出 userMessageCount),共 ${total}
|
|
207
|
+
<p class="muted">按自然日统计的真实用户请求数(口径同导出 userMessageCount),共 ${total} 条 Claude · ${codexTotal} 条 Codex</p>
|
|
199
208
|
</div>
|
|
200
209
|
${noData}
|
|
201
210
|
${chart}
|
|
@@ -257,6 +266,8 @@ function buildDashboardHtml(events, rangeLabel, start, end, dailyUserMessages =
|
|
|
257
266
|
const summary = summarizeEvents(events);
|
|
258
267
|
const buckets = bucketizeEvents(events, start, end, pickBucketMinutes(start, end));
|
|
259
268
|
const totalUserMessages = dailyUserMessages.reduce((sum, point) => sum + point.userMessageCount, 0);
|
|
269
|
+
const codexTotalUserMessages = dailyUserMessages.reduce((sum, point) => sum + (point.codexUserMessageCount ?? 0), 0);
|
|
270
|
+
const combinedUserMessages = totalUserMessages + codexTotalUserMessages;
|
|
260
271
|
return `<!doctype html>
|
|
261
272
|
<html lang="zh-CN">
|
|
262
273
|
<head>
|
|
@@ -449,8 +460,8 @@ function buildDashboardHtml(events, rangeLabel, start, end, dailyUserMessages =
|
|
|
449
460
|
</article>
|
|
450
461
|
<article class="panel stat-card">
|
|
451
462
|
<h2>用户消息数</h2>
|
|
452
|
-
<p class="stat-value">${
|
|
453
|
-
<p class="stat-note"
|
|
463
|
+
<p class="stat-value">${combinedUserMessages}</p>
|
|
464
|
+
<p class="stat-note">窗口内每日真实用户请求数合计(Claude ${totalUserMessages} · Codex ${codexTotalUserMessages})</p>
|
|
454
465
|
</article>
|
|
455
466
|
</section>
|
|
456
467
|
${renderChart(buckets)}
|
package/dist/lib/export.js
CHANGED
|
@@ -98,6 +98,7 @@ function buildAggregatedDetailCsv(events) {
|
|
|
98
98
|
"sessionId",
|
|
99
99
|
"workspaceName",
|
|
100
100
|
"modelName",
|
|
101
|
+
"source",
|
|
101
102
|
"fiveHourUsagePct",
|
|
102
103
|
"contextWindowPct",
|
|
103
104
|
"contextUsedM",
|
|
@@ -114,6 +115,7 @@ function buildAggregatedDetailCsv(events) {
|
|
|
114
115
|
event.sessionId,
|
|
115
116
|
event.workspaceName,
|
|
116
117
|
event.modelName,
|
|
118
|
+
event.source,
|
|
117
119
|
event.usagePct,
|
|
118
120
|
event.contextWindowPct,
|
|
119
121
|
toMillions(event.contextUsed),
|
package/dist/lib/paths.js
CHANGED
|
@@ -11,6 +11,10 @@ exports.getSyncConfigPath = getSyncConfigPath;
|
|
|
11
11
|
exports.getSyncStatePath = getSyncStatePath;
|
|
12
12
|
exports.getApiConfigPath = getApiConfigPath;
|
|
13
13
|
exports.getApiQuotaCachePath = getApiQuotaCachePath;
|
|
14
|
+
exports.getCodexQuotaCachePath = getCodexQuotaCachePath;
|
|
15
|
+
exports.getCodexHome = getCodexHome;
|
|
16
|
+
exports.getCodexConfigPath = getCodexConfigPath;
|
|
17
|
+
exports.getCodexHooksPath = getCodexHooksPath;
|
|
14
18
|
exports.getClaudeDataDir = getClaudeDataDir;
|
|
15
19
|
exports.getClaudeSettingsPath = getClaudeSettingsPath;
|
|
16
20
|
const node_os_1 = __importDefault(require("node:os"));
|
|
@@ -66,6 +70,22 @@ function getApiConfigPath(dataDir) {
|
|
|
66
70
|
function getApiQuotaCachePath(dataDir) {
|
|
67
71
|
return node_path_1.default.join(dataDir, "api-quota-cache.json");
|
|
68
72
|
}
|
|
73
|
+
/** Codex 额度缓存文件,记录上次 app-server 拉取的额度与时间。 */
|
|
74
|
+
function getCodexQuotaCachePath(dataDir) {
|
|
75
|
+
return node_path_1.default.join(dataDir, "codex-quota-cache.json");
|
|
76
|
+
}
|
|
77
|
+
/** Codex CLI 默认本地数据目录(`CODEX_HOME` 环境变量可覆盖,默认 `~/.codex`)。 */
|
|
78
|
+
function getCodexHome() {
|
|
79
|
+
return process.env.CODEX_HOME ?? node_path_1.default.join(node_os_1.default.homedir(), ".codex");
|
|
80
|
+
}
|
|
81
|
+
/** Codex CLI 用户级 config.toml 路径,`ccus install --codex` 往里写 notify。 */
|
|
82
|
+
function getCodexConfigPath() {
|
|
83
|
+
return node_path_1.default.join(getCodexHome(), "config.toml");
|
|
84
|
+
}
|
|
85
|
+
/** Codex CLI 用户级 hooks.json 路径,`ccus install --codex` 往 Stop 事件挂 hook(orca 等 hook-only 环境用)。 */
|
|
86
|
+
function getCodexHooksPath() {
|
|
87
|
+
return node_path_1.default.join(getCodexHome(), "hooks.json");
|
|
88
|
+
}
|
|
69
89
|
/** Claude Code 默认本地数据目录。 */
|
|
70
90
|
function getClaudeDataDir() {
|
|
71
91
|
const configured = process.env.CCUS_CLAUDE_DATA_DIR;
|
package/dist/lib/payload.js
CHANGED
|
@@ -10,6 +10,8 @@ exports.resolveCtxTier = resolveCtxTier;
|
|
|
10
10
|
exports.resolveCtxRedThresholds = resolveCtxRedThresholds;
|
|
11
11
|
exports.formatStatusLine = formatStatusLine;
|
|
12
12
|
exports.createPersistedStatuslineEvent = createPersistedStatuslineEvent;
|
|
13
|
+
exports.isCodexSourceRecord = isCodexSourceRecord;
|
|
14
|
+
exports.isCodexSourceEvent = isCodexSourceEvent;
|
|
13
15
|
exports.computeStatuslineEvent = computeStatuslineEvent;
|
|
14
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
15
17
|
const time_1 = require("./time");
|
|
@@ -271,6 +273,14 @@ function createPersistedStatuslineEvent(payload, now = new Date()) {
|
|
|
271
273
|
rawPayload: payload,
|
|
272
274
|
};
|
|
273
275
|
}
|
|
276
|
+
/** 判断事件是否来自 Codex(rawPayload.source === "codex");Claude statusline 事件无该字段,视为 claude。 */
|
|
277
|
+
function isCodexSourceRecord(record) {
|
|
278
|
+
return record.rawPayload?.source === "codex";
|
|
279
|
+
}
|
|
280
|
+
/** StatuslineEvent 视图版的 Codex 来源判断(event.rawPayload 透传自 record)。 */
|
|
281
|
+
function isCodexSourceEvent(event) {
|
|
282
|
+
return event.rawPayload?.source === "codex";
|
|
283
|
+
}
|
|
274
284
|
/**
|
|
275
285
|
* 从持久化事件计算出 dashboard/export/statusline 使用的完整视图。
|
|
276
286
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccus-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Claude Code statusline usage logger and dashboard CLI",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "tsc -p tsconfig.json && node scripts/copy-vendor.js",
|
|
17
17
|
"prepublishOnly": "npm run build",
|
|
18
|
-
"test": "node --test dist/test",
|
|
19
|
-
"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 src/test/sync.test.ts src/test/scheduler.test.ts src/test/api-mode.test.ts",
|
|
18
|
+
"test": "node --test --test-concurrency 1 \"dist/test/**/*.test.js\"",
|
|
19
|
+
"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/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 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/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
20
|
"smoke:api": "node scripts/smoke-api.mjs"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|