ccus-cli 0.2.10 → 0.2.11
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 +6 -3
- package/dist/cli.js +54 -16
- package/dist/lib/aggregate-dashboard.js +67 -8
- package/dist/lib/aggregate.js +113 -23
- package/dist/lib/api-equivalent-cost.js +189 -0
- package/dist/lib/api-pricing-catalog.json +270 -0
- package/dist/lib/api-pricing-table.js +163 -0
- package/dist/lib/claude.js +52 -9
- package/dist/lib/codex-sessions.js +70 -21
- package/dist/lib/dashboard-pages.js +15 -0
- package/dist/lib/dashboard.js +35 -1
- package/dist/lib/export.js +12 -0
- package/package.json +3 -2
|
@@ -8,6 +8,7 @@ exports.summarizeCodexSessionUsage = summarizeCodexSessionUsage;
|
|
|
8
8
|
exports.summarizeCodexSessionUsageByDay = summarizeCodexSessionUsageByDay;
|
|
9
9
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
10
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const api_equivalent_cost_1 = require("./api-equivalent-cost");
|
|
11
12
|
const paths_1 = require("./paths");
|
|
12
13
|
const time_1 = require("./time");
|
|
13
14
|
function isRecord(value) {
|
|
@@ -19,6 +20,13 @@ function getNumber(value) {
|
|
|
19
20
|
function getString(value) {
|
|
20
21
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
21
22
|
}
|
|
23
|
+
function splitInputTokens(usage) {
|
|
24
|
+
const cacheReadInputTokens = getNumber(usage.cached_input_tokens);
|
|
25
|
+
return {
|
|
26
|
+
inputTokens: Math.max(0, getNumber(usage.input_tokens) - cacheReadInputTokens),
|
|
27
|
+
cacheReadInputTokens,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
22
30
|
/** Guardian 是 Codex Desktop 的动作安全审查子代理,不代表用户请求或业务模型调用。 */
|
|
23
31
|
function isGuardianRollout(content) {
|
|
24
32
|
let offset = 0;
|
|
@@ -178,8 +186,7 @@ async function findActiveCodexSessionFiles(start, end) {
|
|
|
178
186
|
/**
|
|
179
187
|
* 从 `token_count` 事件的 `info.last_token_usage` 取 token 用量对象(缺失返回 null)。
|
|
180
188
|
* 必须用 last_token_usage(本次增量),不能用 total_token_usage(会话累计,会重复计)。
|
|
181
|
-
*
|
|
182
|
-
* 累加 inputTokens 时要减去 cached 得到净输入,对齐 Claude 的 `input_tokens` 口径。
|
|
189
|
+
* `cached_input_tokens` 是 `input_tokens` 的子集;普通输入需扣除缓存输入并钳制为非负数。
|
|
183
190
|
*/
|
|
184
191
|
function extractLastTokenUsage(payload) {
|
|
185
192
|
if (!isRecord(payload.info)) {
|
|
@@ -204,19 +211,28 @@ function summarizeRollout(content, start, end) {
|
|
|
204
211
|
inputTokens: 0,
|
|
205
212
|
outputTokens: 0,
|
|
206
213
|
cacheReadInputTokens: 0,
|
|
214
|
+
apiEquivalentCost: (0, api_equivalent_cost_1.emptyApiEquivalentCost)(),
|
|
207
215
|
};
|
|
216
|
+
let currentModel = null;
|
|
208
217
|
for (const line of lines) {
|
|
209
218
|
try {
|
|
210
219
|
const record = JSON.parse(line);
|
|
211
|
-
if (!isRecord(record) || !
|
|
220
|
+
if (!isRecord(record) || !isRecord(record.payload)) {
|
|
212
221
|
continue;
|
|
213
222
|
}
|
|
214
|
-
|
|
223
|
+
const payload = record.payload;
|
|
224
|
+
if (record.type === "turn_context") {
|
|
225
|
+
if (typeof payload.model === "string") {
|
|
226
|
+
currentModel = payload.model;
|
|
227
|
+
}
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
const timestamp = getString(record.timestamp);
|
|
231
|
+
if (!timestampInRange(timestamp, start, end) || record.type !== "event_msg") {
|
|
215
232
|
continue;
|
|
216
233
|
}
|
|
217
|
-
const payload = record.payload;
|
|
218
234
|
if (payload.type === "task_started" && typeof payload.turn_id === "string") {
|
|
219
|
-
const ms = new Date(
|
|
235
|
+
const ms = new Date(timestamp ?? "").getTime();
|
|
220
236
|
if (Number.isFinite(ms)) {
|
|
221
237
|
result.turns.push({ turnId: payload.turn_id, ms });
|
|
222
238
|
}
|
|
@@ -224,12 +240,23 @@ function summarizeRollout(content, start, end) {
|
|
|
224
240
|
if (payload.type === "token_count") {
|
|
225
241
|
const usage = extractLastTokenUsage(payload);
|
|
226
242
|
if (usage) {
|
|
227
|
-
|
|
228
|
-
const
|
|
243
|
+
const { inputTokens, cacheReadInputTokens } = splitInputTokens(usage);
|
|
244
|
+
const outputTokens = getNumber(usage.output_tokens);
|
|
229
245
|
result.apiRequestCount += 1;
|
|
230
|
-
result.inputTokens +=
|
|
231
|
-
result.outputTokens +=
|
|
232
|
-
result.cacheReadInputTokens +=
|
|
246
|
+
result.inputTokens += inputTokens;
|
|
247
|
+
result.outputTokens += outputTokens;
|
|
248
|
+
result.cacheReadInputTokens += cacheReadInputTokens;
|
|
249
|
+
result.apiEquivalentCost = (0, api_equivalent_cost_1.mergeApiEquivalentCosts)([
|
|
250
|
+
result.apiEquivalentCost,
|
|
251
|
+
(0, api_equivalent_cost_1.priceApiRequest)({
|
|
252
|
+
provider: "codex",
|
|
253
|
+
timestamp: timestamp,
|
|
254
|
+
model: currentModel,
|
|
255
|
+
inputTokens,
|
|
256
|
+
outputTokens,
|
|
257
|
+
cacheReadInputTokens,
|
|
258
|
+
}),
|
|
259
|
+
]);
|
|
233
260
|
}
|
|
234
261
|
}
|
|
235
262
|
}
|
|
@@ -240,7 +267,7 @@ function summarizeRollout(content, start, end) {
|
|
|
240
267
|
return result;
|
|
241
268
|
}
|
|
242
269
|
/**
|
|
243
|
-
* 从标准 Codex 与 Orca 本地 session rollout
|
|
270
|
+
* 从标准 Codex 与 Orca 本地 session rollout 统计消息数、请求数、token 用量和标准 API 等效成本。
|
|
244
271
|
* Codex Desktop 的 guardian 安全审查 rollout 整体排除,避免把内部审批轮次计为用户使用量。
|
|
245
272
|
*
|
|
246
273
|
* 消息数 = task_started 的 distinct turn_id(跨文件去重)。重放副本会让同一 turn_id 出现在多个文件,
|
|
@@ -254,6 +281,7 @@ async function summarizeCodexSessionUsage(start, end) {
|
|
|
254
281
|
let inputTokens = 0;
|
|
255
282
|
let outputTokens = 0;
|
|
256
283
|
let cacheReadInputTokens = 0;
|
|
284
|
+
let apiEquivalentCost = (0, api_equivalent_cost_1.emptyApiEquivalentCost)();
|
|
257
285
|
for (const group of groups) {
|
|
258
286
|
try {
|
|
259
287
|
const content = await readMergedRollout(group);
|
|
@@ -271,6 +299,7 @@ async function summarizeCodexSessionUsage(start, end) {
|
|
|
271
299
|
inputTokens += parsed.inputTokens;
|
|
272
300
|
outputTokens += parsed.outputTokens;
|
|
273
301
|
cacheReadInputTokens += parsed.cacheReadInputTokens;
|
|
302
|
+
apiEquivalentCost = (0, api_equivalent_cost_1.mergeApiEquivalentCosts)([apiEquivalentCost, parsed.apiEquivalentCost]);
|
|
274
303
|
}
|
|
275
304
|
catch {
|
|
276
305
|
continue;
|
|
@@ -282,12 +311,13 @@ async function summarizeCodexSessionUsage(start, end) {
|
|
|
282
311
|
inputTokens,
|
|
283
312
|
outputTokens,
|
|
284
313
|
cacheReadInputTokens,
|
|
314
|
+
apiEquivalentCost,
|
|
285
315
|
matchedFileCount: groups.length,
|
|
286
316
|
codexDataDir,
|
|
287
317
|
};
|
|
288
318
|
}
|
|
289
319
|
/**
|
|
290
|
-
* 按天汇总 Codex session rollout
|
|
320
|
+
* 按天汇总 Codex session rollout 中的消息数、请求数、token 用量和标准 API 等效成本。
|
|
291
321
|
* 与周汇总一致,排除 Codex Desktop 的 guardian 安全审查 rollout。
|
|
292
322
|
*
|
|
293
323
|
* 消息数同 weekly:先全局 Map<turn_id, minMs> 去重,再按 minMs 的本地日归桶(保证 weekly = Σ daily、
|
|
@@ -307,6 +337,7 @@ async function summarizeCodexSessionUsageByDay(start, end) {
|
|
|
307
337
|
inputTokens: 0,
|
|
308
338
|
outputTokens: 0,
|
|
309
339
|
cacheReadInputTokens: 0,
|
|
340
|
+
apiEquivalentCost: (0, api_equivalent_cost_1.emptyApiEquivalentCost)(),
|
|
310
341
|
};
|
|
311
342
|
daily.set(date, current);
|
|
312
343
|
}
|
|
@@ -318,20 +349,27 @@ async function summarizeCodexSessionUsageByDay(start, end) {
|
|
|
318
349
|
continue;
|
|
319
350
|
}
|
|
320
351
|
const lines = content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0);
|
|
352
|
+
let currentModel = null;
|
|
321
353
|
for (const line of lines) {
|
|
322
354
|
try {
|
|
323
355
|
const record = JSON.parse(line);
|
|
324
|
-
if (!isRecord(record)) {
|
|
356
|
+
if (!isRecord(record) || !isRecord(record.payload)) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const payload = record.payload;
|
|
360
|
+
if (record.type === "turn_context") {
|
|
361
|
+
if (typeof payload.model === "string") {
|
|
362
|
+
currentModel = payload.model;
|
|
363
|
+
}
|
|
325
364
|
continue;
|
|
326
365
|
}
|
|
327
366
|
const timestamp = getString(record.timestamp);
|
|
328
367
|
if (!timestampInRange(timestamp, start, end)) {
|
|
329
368
|
continue;
|
|
330
369
|
}
|
|
331
|
-
if (record.type !== "event_msg"
|
|
370
|
+
if (record.type !== "event_msg") {
|
|
332
371
|
continue;
|
|
333
372
|
}
|
|
334
|
-
const payload = record.payload;
|
|
335
373
|
const ms = new Date(timestamp).getTime();
|
|
336
374
|
if (payload.type === "task_started" && typeof payload.turn_id === "string") {
|
|
337
375
|
if (Number.isFinite(ms)) {
|
|
@@ -345,12 +383,23 @@ async function summarizeCodexSessionUsageByDay(start, end) {
|
|
|
345
383
|
const usage = extractLastTokenUsage(payload);
|
|
346
384
|
if (usage) {
|
|
347
385
|
const current = ensureDay((0, time_1.localDateKey)(new Date(ms)));
|
|
348
|
-
|
|
349
|
-
const
|
|
386
|
+
const { inputTokens, cacheReadInputTokens } = splitInputTokens(usage);
|
|
387
|
+
const outputTokens = getNumber(usage.output_tokens);
|
|
350
388
|
current.apiRequestCount += 1;
|
|
351
|
-
current.inputTokens +=
|
|
352
|
-
current.outputTokens +=
|
|
353
|
-
current.cacheReadInputTokens +=
|
|
389
|
+
current.inputTokens += inputTokens;
|
|
390
|
+
current.outputTokens += outputTokens;
|
|
391
|
+
current.cacheReadInputTokens += cacheReadInputTokens;
|
|
392
|
+
current.apiEquivalentCost = (0, api_equivalent_cost_1.mergeApiEquivalentCosts)([
|
|
393
|
+
current.apiEquivalentCost,
|
|
394
|
+
(0, api_equivalent_cost_1.priceApiRequest)({
|
|
395
|
+
provider: "codex",
|
|
396
|
+
timestamp: timestamp,
|
|
397
|
+
model: currentModel,
|
|
398
|
+
inputTokens,
|
|
399
|
+
outputTokens,
|
|
400
|
+
cacheReadInputTokens,
|
|
401
|
+
}),
|
|
402
|
+
]);
|
|
354
403
|
}
|
|
355
404
|
}
|
|
356
405
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderDashboardPage = renderDashboardPage;
|
|
4
|
+
const api_pricing_table_1 = require("./api-pricing-table");
|
|
5
|
+
/** 两类 dashboard serve 共用的页面路由;null 表示 404。 */
|
|
6
|
+
async function renderDashboardPage(pathname, renderDashboard, generatedAt = new Date()) {
|
|
7
|
+
if (pathname === "/") {
|
|
8
|
+
return renderDashboard();
|
|
9
|
+
}
|
|
10
|
+
if (pathname === api_pricing_table_1.API_PRICING_PAGE_PATH) {
|
|
11
|
+
return (0, api_pricing_table_1.buildApiPricingPage)(generatedAt);
|
|
12
|
+
}
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=dashboard-pages.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 api_pricing_table_1 = require("./api-pricing-table");
|
|
8
9
|
const payload_1 = require("./payload");
|
|
9
10
|
const time_1 = require("./time");
|
|
10
11
|
/** 所有插入到 HTML/SVG 的动态文本都先转义,避免本地页面被注入内容。 */
|
|
@@ -216,6 +217,36 @@ function renderDailyMessages(points) {
|
|
|
216
217
|
function statValue(value, suffix = "%") {
|
|
217
218
|
return value === null ? "--" : `${value.toFixed(1)}${suffix}`;
|
|
218
219
|
}
|
|
220
|
+
function formatCostUsd(value) {
|
|
221
|
+
return `$${value < 0.01 ? value.toFixed(4) : value.toFixed(2)}`;
|
|
222
|
+
}
|
|
223
|
+
function costDisplay(result) {
|
|
224
|
+
if (result.pricedApiRequestCount === 0 && result.unpricedApiRequestCount > 0) {
|
|
225
|
+
return { value: "不可用", note: `${result.unpricedApiRequestCount} 个请求未定价` };
|
|
226
|
+
}
|
|
227
|
+
const amount = formatCostUsd(result.estimatedUsd ?? 0);
|
|
228
|
+
if (result.unpricedApiRequestCount > 0) {
|
|
229
|
+
return { value: `≥ ${amount}`, note: `${result.unpricedApiRequestCount} 个请求未定价,结果不完整` };
|
|
230
|
+
}
|
|
231
|
+
return { value: amount, note: `${result.pricedApiRequestCount} 个请求已定价` };
|
|
232
|
+
}
|
|
233
|
+
function renderApiEquivalentCostCard(costs) {
|
|
234
|
+
if (!costs) {
|
|
235
|
+
return "";
|
|
236
|
+
}
|
|
237
|
+
const display = costDisplay(costs.total);
|
|
238
|
+
return `<article class="panel stat-card">
|
|
239
|
+
<h2>合计等效 API 成本</h2>
|
|
240
|
+
<p class="stat-value">${escapeHtml(display.value)}</p>
|
|
241
|
+
<p class="stat-note">${escapeHtml(display.note)}</p>
|
|
242
|
+
<a class="pricing-link" href="${api_pricing_table_1.API_PRICING_PAGE_FILE}" target="_blank" rel="noopener noreferrer">查看当前价格表</a>
|
|
243
|
+
</article>`;
|
|
244
|
+
}
|
|
245
|
+
function renderApiEquivalentCostNote(costs, pricingCatalogVersion) {
|
|
246
|
+
return costs
|
|
247
|
+
? `<p class="muted">标准同步 API 等效成本,不是订阅或实际账单 · 事件时间计价 · 目录 ${escapeHtml(pricingCatalogVersion ?? "不可用")}</p>`
|
|
248
|
+
: "";
|
|
249
|
+
}
|
|
219
250
|
/** 最近事件表帮助回看 statusline 在某一时刻到底输出了什么。 */
|
|
220
251
|
function renderRecentEvents(events) {
|
|
221
252
|
const rows = [...events]
|
|
@@ -263,7 +294,7 @@ function renderRecentEvents(events) {
|
|
|
263
294
|
*
|
|
264
295
|
* 整个 dashboard 保持“单文件可打开”,方便导出和直接分享本地结果。
|
|
265
296
|
*/
|
|
266
|
-
function buildDashboardHtml(events, rangeLabel, start, end, dailyUserMessages = []) {
|
|
297
|
+
function buildDashboardHtml(events, rangeLabel, start, end, dailyUserMessages = [], apiEquivalentCost = null, pricingCatalogVersion = null) {
|
|
267
298
|
const summary = summarizeEvents(events);
|
|
268
299
|
const buckets = bucketizeEvents(events, start, end, pickBucketMinutes(start, end));
|
|
269
300
|
const totalUserMessages = dailyUserMessages.reduce((sum, point) => sum + point.userMessageCount, 0);
|
|
@@ -373,6 +404,7 @@ function buildDashboardHtml(events, rangeLabel, start, end, dailyUserMessages =
|
|
|
373
404
|
color: var(--muted);
|
|
374
405
|
font-size: 14px;
|
|
375
406
|
}
|
|
407
|
+
.pricing-link { display: inline-block; margin-top: 12px; color: var(--accent); font-size: 13px; }
|
|
376
408
|
.panel-header {
|
|
377
409
|
display: flex;
|
|
378
410
|
align-items: end;
|
|
@@ -464,7 +496,9 @@ function buildDashboardHtml(events, rangeLabel, start, end, dailyUserMessages =
|
|
|
464
496
|
<p class="stat-value">${combinedUserMessages}</p>
|
|
465
497
|
<p class="stat-note">窗口内每日真实用户请求数合计(Claude ${totalUserMessages} · Codex ${codexTotalUserMessages})</p>
|
|
466
498
|
</article>
|
|
499
|
+
${renderApiEquivalentCostCard(apiEquivalentCost)}
|
|
467
500
|
</section>
|
|
501
|
+
${renderApiEquivalentCostNote(apiEquivalentCost, pricingCatalogVersion)}
|
|
468
502
|
${renderChart(buckets)}
|
|
469
503
|
${renderDailyMessages(dailyUserMessages)}
|
|
470
504
|
${renderRecentEvents(events)}
|
package/dist/lib/export.js
CHANGED
|
@@ -28,6 +28,10 @@ function quoteCsv(value) {
|
|
|
28
28
|
function toMillions(value) {
|
|
29
29
|
return value === null ? null : (0, time_1.roundNumber)(value / 1_000_000, 6);
|
|
30
30
|
}
|
|
31
|
+
/** 等效 API 成本以美元输出,最多保留 6 位小数;不可用时留空。 */
|
|
32
|
+
function formatApiEquivalentCost(value) {
|
|
33
|
+
return value === null ? null : (0, time_1.roundNumber)(value, 6);
|
|
34
|
+
}
|
|
31
35
|
/**
|
|
32
36
|
* 清理控制字符,并防止 Excel/Sheets 把值解释成公式。
|
|
33
37
|
*
|
|
@@ -219,6 +223,8 @@ function buildAggregatedDailyCsv(rows) {
|
|
|
219
223
|
"sevenDayCumulativeUsagePct",
|
|
220
224
|
"uniqueSessions",
|
|
221
225
|
"uniqueWorkspaces",
|
|
226
|
+
"estimatedApiEquivalentCostUsd",
|
|
227
|
+
"pricingCatalogVersion",
|
|
222
228
|
];
|
|
223
229
|
const lines = rows.map((row) => toCsvLine([
|
|
224
230
|
row.personKey,
|
|
@@ -236,6 +242,8 @@ function buildAggregatedDailyCsv(rows) {
|
|
|
236
242
|
row.sevenDayCumulativeUsagePct,
|
|
237
243
|
row.uniqueSessions,
|
|
238
244
|
row.uniqueWorkspaces,
|
|
245
|
+
formatApiEquivalentCost(row.estimatedApiEquivalentCostUsd),
|
|
246
|
+
row.pricingCatalogVersion,
|
|
239
247
|
]));
|
|
240
248
|
return [header.join(","), ...lines].join("\n");
|
|
241
249
|
}
|
|
@@ -257,6 +265,8 @@ function buildAggregatedWeeklyCsv(rows) {
|
|
|
257
265
|
"sevenDayCumulativeUsagePct",
|
|
258
266
|
"uniqueSessions",
|
|
259
267
|
"uniqueWorkspaces",
|
|
268
|
+
"estimatedApiEquivalentCostUsd",
|
|
269
|
+
"pricingCatalogVersion",
|
|
260
270
|
];
|
|
261
271
|
const lines = rows.map((row) => toCsvLine([
|
|
262
272
|
row.personKey,
|
|
@@ -274,6 +284,8 @@ function buildAggregatedWeeklyCsv(rows) {
|
|
|
274
284
|
row.sevenDayCumulativeUsagePct,
|
|
275
285
|
row.uniqueSessions,
|
|
276
286
|
row.uniqueWorkspaces,
|
|
287
|
+
formatApiEquivalentCost(row.estimatedApiEquivalentCostUsd),
|
|
288
|
+
row.pricingCatalogVersion,
|
|
277
289
|
]));
|
|
278
290
|
return [header.join(","), ...lines].join("\n");
|
|
279
291
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ccus-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.11",
|
|
4
4
|
"description": "Claude Code statusline usage logger and dashboard CLI",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist/cli.js",
|
|
11
11
|
"dist/lib/**/*.js",
|
|
12
|
+
"dist/lib/api-pricing-catalog.json",
|
|
12
13
|
"dist/types.js",
|
|
13
14
|
"dist/vendor/**"
|
|
14
15
|
],
|
|
@@ -16,7 +17,7 @@
|
|
|
16
17
|
"build": "tsc -p tsconfig.json && node scripts/copy-vendor.js",
|
|
17
18
|
"prepublishOnly": "npm run build",
|
|
18
19
|
"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/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
|
+
"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/api-equivalent-cost.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/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
21
|
"smoke:api": "node scripts/smoke-api.mjs"
|
|
21
22
|
},
|
|
22
23
|
"keywords": [
|