storyforge 0.4.17 → 0.4.18
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/dist/cli-usage-EC5NOILQ.js +208 -0
- package/dist/index.js +1 -1
- package/package.json +5 -2
- package/dist/cli-usage-LUATWFYP.js +0 -245
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli-usage.ts
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
import * as os from "os";
|
|
7
|
+
import { loadSessionBlockData } from "ccusage/data-loader";
|
|
8
|
+
var CODEX_PRICES = {
|
|
9
|
+
"gpt-5.5": { input: 5, output: 30 },
|
|
10
|
+
"gpt-5.4": { input: 2.5, output: 15 },
|
|
11
|
+
"gpt-5.4-mini": { input: 0.75, output: 4.5 },
|
|
12
|
+
"gpt-5.4-nano": { input: 0.2, output: 1.25 },
|
|
13
|
+
"gpt-5": { input: 2.5, output: 15 },
|
|
14
|
+
"gpt-5-codex": { input: 2.5, output: 15 },
|
|
15
|
+
"gpt-4o": { input: 2.5, output: 10 }
|
|
16
|
+
};
|
|
17
|
+
function codexPriceFor(model) {
|
|
18
|
+
const lower = model.toLowerCase();
|
|
19
|
+
if (CODEX_PRICES[lower]) return CODEX_PRICES[lower];
|
|
20
|
+
for (const key of Object.keys(CODEX_PRICES)) {
|
|
21
|
+
if (lower.startsWith(key)) return CODEX_PRICES[key];
|
|
22
|
+
}
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
var CLI_USAGE_PARSER_VERSION = 3;
|
|
26
|
+
function emptyModelUsage(model) {
|
|
27
|
+
return { model, inputTokens: 0, cachedReadTokens: 0, cacheCreateTokens: 0, outputTokens: 0, costUsd: 0 };
|
|
28
|
+
}
|
|
29
|
+
function addToBucket(bucket, model, u) {
|
|
30
|
+
const row = bucket[model] ??= emptyModelUsage(model);
|
|
31
|
+
row.inputTokens += u.input;
|
|
32
|
+
row.cachedReadTokens += u.cacheRead;
|
|
33
|
+
row.cacheCreateTokens += u.cacheCreate;
|
|
34
|
+
row.outputTokens += u.output;
|
|
35
|
+
if (typeof u.costUSD === "number") row.costUsd += u.costUSD;
|
|
36
|
+
}
|
|
37
|
+
function startOfTodayMs(now = Date.now()) {
|
|
38
|
+
const d = new Date(now);
|
|
39
|
+
d.setHours(0, 0, 0, 0);
|
|
40
|
+
return d.getTime();
|
|
41
|
+
}
|
|
42
|
+
function listJsonlFiles(rootDir) {
|
|
43
|
+
const out = [];
|
|
44
|
+
if (!fs.existsSync(rootDir)) return out;
|
|
45
|
+
const stack = [rootDir];
|
|
46
|
+
while (stack.length > 0) {
|
|
47
|
+
const cur = stack.pop();
|
|
48
|
+
let entries;
|
|
49
|
+
try {
|
|
50
|
+
entries = fs.readdirSync(cur, { withFileTypes: true });
|
|
51
|
+
} catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
for (const e of entries) {
|
|
55
|
+
const full = path.join(cur, e.name);
|
|
56
|
+
if (e.isDirectory()) stack.push(full);
|
|
57
|
+
else if (e.isFile() && e.name.endsWith(".jsonl")) out.push(full);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
function parseCodexFile(file) {
|
|
63
|
+
let content;
|
|
64
|
+
try {
|
|
65
|
+
content = fs.readFileSync(file, "utf-8");
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
const out = [];
|
|
70
|
+
let prev = { input: 0, cachedInput: 0, output: 0 };
|
|
71
|
+
let model = "gpt-5";
|
|
72
|
+
for (const raw of content.split(/\r?\n/)) {
|
|
73
|
+
if (!raw.trim()) continue;
|
|
74
|
+
let obj;
|
|
75
|
+
try {
|
|
76
|
+
obj = JSON.parse(raw);
|
|
77
|
+
} catch {
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const modelHint = obj?.event_msg?.payload?.turn_context?.model ?? obj?.payload?.turn_context?.model ?? obj?.message?.model;
|
|
81
|
+
if (typeof modelHint === "string" && modelHint) model = modelHint.toLowerCase();
|
|
82
|
+
const payload = obj?.event_msg?.payload ?? obj?.payload;
|
|
83
|
+
if (!payload || payload.type !== "token_count") continue;
|
|
84
|
+
const t = payload;
|
|
85
|
+
const cur = {
|
|
86
|
+
input: t.input_tokens ?? 0,
|
|
87
|
+
cachedInput: t.cached_input_tokens ?? 0,
|
|
88
|
+
output: t.output_tokens ?? 0
|
|
89
|
+
};
|
|
90
|
+
const dInput = Math.max(0, cur.input - prev.input);
|
|
91
|
+
const dCacheRead = Math.max(0, cur.cachedInput - prev.cachedInput);
|
|
92
|
+
const dOutput = Math.max(0, cur.output - prev.output);
|
|
93
|
+
prev = cur;
|
|
94
|
+
const tsRaw = obj?.timestamp ?? obj?.event_msg?.timestamp ?? obj?.event_msg?.event_ts;
|
|
95
|
+
const ts = tsRaw ? new Date(tsRaw).getTime() : Date.now();
|
|
96
|
+
if (!Number.isFinite(ts)) continue;
|
|
97
|
+
const price = codexPriceFor(model);
|
|
98
|
+
const costUSD = price ? dInput / 1e6 * price.input + dOutput / 1e6 * price.output : void 0;
|
|
99
|
+
out.push({ ts, model, usage: { input: dInput, cacheRead: dCacheRead, cacheCreate: 0, output: dOutput, costUSD } });
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
}
|
|
103
|
+
async function gatherCliUsage() {
|
|
104
|
+
const claudeEntries = [];
|
|
105
|
+
let claudeFileCount = 0;
|
|
106
|
+
try {
|
|
107
|
+
const blocks = await loadSessionBlockData();
|
|
108
|
+
for (const block of blocks) {
|
|
109
|
+
const entries = block.entries ?? [];
|
|
110
|
+
for (const e of entries) {
|
|
111
|
+
const ts = e.timestamp ? new Date(e.timestamp).getTime() : NaN;
|
|
112
|
+
if (!Number.isFinite(ts)) continue;
|
|
113
|
+
const model = (e.model ?? "").toLowerCase();
|
|
114
|
+
if (!model) continue;
|
|
115
|
+
const u = e.usage ?? {};
|
|
116
|
+
claudeEntries.push({
|
|
117
|
+
ts,
|
|
118
|
+
model,
|
|
119
|
+
usage: {
|
|
120
|
+
input: u.inputTokens ?? 0,
|
|
121
|
+
cacheRead: u.cacheReadInputTokens ?? 0,
|
|
122
|
+
cacheCreate: u.cacheCreationInputTokens ?? 0,
|
|
123
|
+
output: u.outputTokens ?? 0,
|
|
124
|
+
costUSD: e.costUSD
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
claudeFileCount = listJsonlFiles(path.join(os.homedir(), ".claude", "projects")).length;
|
|
130
|
+
} catch (err) {
|
|
131
|
+
void err;
|
|
132
|
+
}
|
|
133
|
+
const codexDir = path.join(os.homedir(), ".codex", "sessions");
|
|
134
|
+
const codexFiles = listJsonlFiles(codexDir);
|
|
135
|
+
const codexEntries = [];
|
|
136
|
+
for (const f of codexFiles) {
|
|
137
|
+
codexEntries.push(...parseCodexFile(f));
|
|
138
|
+
}
|
|
139
|
+
const all = [...claudeEntries, ...codexEntries];
|
|
140
|
+
const now = Date.now();
|
|
141
|
+
const cutoff5h = now - 5 * 60 * 60 * 1e3;
|
|
142
|
+
const cutoff24h = now - 24 * 60 * 60 * 1e3;
|
|
143
|
+
const cutoff7d = now - 7 * 24 * 60 * 60 * 1e3;
|
|
144
|
+
const startToday = startOfTodayMs(now);
|
|
145
|
+
const messageCounts = { last5h: 0, last24h: 0, last7d: 0, today: 0, lifetime: 0 };
|
|
146
|
+
const buckets = {
|
|
147
|
+
last5h: {},
|
|
148
|
+
last24h: {},
|
|
149
|
+
last7d: {},
|
|
150
|
+
today: {},
|
|
151
|
+
lifetime: {}
|
|
152
|
+
};
|
|
153
|
+
for (const e of all) {
|
|
154
|
+
addToBucket(buckets.lifetime, e.model, e.usage);
|
|
155
|
+
messageCounts.lifetime++;
|
|
156
|
+
if (e.ts >= cutoff7d) {
|
|
157
|
+
addToBucket(buckets.last7d, e.model, e.usage);
|
|
158
|
+
messageCounts.last7d++;
|
|
159
|
+
}
|
|
160
|
+
if (e.ts >= cutoff24h) {
|
|
161
|
+
addToBucket(buckets.last24h, e.model, e.usage);
|
|
162
|
+
messageCounts.last24h++;
|
|
163
|
+
}
|
|
164
|
+
if (e.ts >= cutoff5h) {
|
|
165
|
+
addToBucket(buckets.last5h, e.model, e.usage);
|
|
166
|
+
messageCounts.last5h++;
|
|
167
|
+
}
|
|
168
|
+
if (e.ts >= startToday) {
|
|
169
|
+
addToBucket(buckets.today, e.model, e.usage);
|
|
170
|
+
messageCounts.today++;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
function finalise(rec) {
|
|
174
|
+
return Object.values(rec).sort((a, b) => b.costUsd - a.costUsd);
|
|
175
|
+
}
|
|
176
|
+
function sum(rows, messages) {
|
|
177
|
+
return {
|
|
178
|
+
tokens: rows.reduce((s, r) => s + r.inputTokens + r.cachedReadTokens + r.cacheCreateTokens + r.outputTokens, 0),
|
|
179
|
+
costUsd: rows.reduce((s, r) => s + r.costUsd, 0),
|
|
180
|
+
messages
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
const last5h = finalise(buckets.last5h);
|
|
184
|
+
const last24h = finalise(buckets.last24h);
|
|
185
|
+
const last7d = finalise(buckets.last7d);
|
|
186
|
+
const today = finalise(buckets.today);
|
|
187
|
+
const lifetime = finalise(buckets.lifetime);
|
|
188
|
+
return {
|
|
189
|
+
generatedAt: new Date(now).toISOString(),
|
|
190
|
+
windows: { last5h, last24h, last7d, today, lifetime },
|
|
191
|
+
totals: {
|
|
192
|
+
last5h: sum(last5h, messageCounts.last5h),
|
|
193
|
+
last24h: sum(last24h, messageCounts.last24h),
|
|
194
|
+
last7d: sum(last7d, messageCounts.last7d),
|
|
195
|
+
today: sum(today, messageCounts.today),
|
|
196
|
+
lifetime: sum(lifetime, messageCounts.lifetime)
|
|
197
|
+
},
|
|
198
|
+
sources: {
|
|
199
|
+
claudeFiles: claudeFileCount,
|
|
200
|
+
codexFiles: codexFiles.length,
|
|
201
|
+
skipped: 0
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
export {
|
|
206
|
+
CLI_USAGE_PARSER_VERSION,
|
|
207
|
+
gatherCliUsage
|
|
208
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -866,7 +866,7 @@ async function devCommand(options) {
|
|
|
866
866
|
const pathname = url.pathname;
|
|
867
867
|
if (pathname === "/api/cli-usage") {
|
|
868
868
|
try {
|
|
869
|
-
const { gatherCliUsage, CLI_USAGE_PARSER_VERSION } = await import("./cli-usage-
|
|
869
|
+
const { gatherCliUsage, CLI_USAGE_PARSER_VERSION } = await import("./cli-usage-EC5NOILQ.js");
|
|
870
870
|
const g = global;
|
|
871
871
|
const now = Date.now();
|
|
872
872
|
if (g.__forgeCliUsageCache && g.__forgeCliUsageCache.ver === CLI_USAGE_PARSER_VERSION && now - g.__forgeCliUsageCache.at < 3e4) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "storyforge",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.18",
|
|
4
4
|
"description": "StoryForge — local bridge for the Forge video production web app. Zero runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -15,7 +15,10 @@
|
|
|
15
15
|
"lint": "tsc --noEmit",
|
|
16
16
|
"prepublishOnly": "npm run build"
|
|
17
17
|
},
|
|
18
|
-
"dependencies": {
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@ccusage/codex": "18.0.11",
|
|
20
|
+
"ccusage": "18.0.11"
|
|
21
|
+
},
|
|
19
22
|
"devDependencies": {
|
|
20
23
|
"tsup": "8.5.0",
|
|
21
24
|
"typescript": "5.8.3"
|
|
@@ -1,245 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
// src/cli-usage.ts
|
|
4
|
-
import * as fs from "fs";
|
|
5
|
-
import * as path from "path";
|
|
6
|
-
import * as os from "os";
|
|
7
|
-
var PRICES = {
|
|
8
|
-
// Anthropic — per claude.com/pricing (April 2026)
|
|
9
|
-
"claude-opus-4-7": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
10
|
-
"claude-opus-4-6": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
11
|
-
"claude-opus-4-5": { input: 5, output: 25, cacheRead: 0.5, cacheWrite: 6.25 },
|
|
12
|
-
"claude-sonnet-4-6": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
13
|
-
"claude-sonnet-4-5": { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
|
|
14
|
-
"claude-haiku-4-5": { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
|
|
15
|
-
// OpenAI — per platform.openai.com/pricing (April 2026)
|
|
16
|
-
"gpt-5.5": { input: 5, output: 30 },
|
|
17
|
-
"gpt-5.4": { input: 2.5, output: 15 },
|
|
18
|
-
"gpt-5.4-mini": { input: 0.75, output: 4.5 },
|
|
19
|
-
"gpt-5.4-nano": { input: 0.2, output: 1.25 },
|
|
20
|
-
"gpt-5": { input: 2.5, output: 15 },
|
|
21
|
-
"gpt-5-codex": { input: 2.5, output: 15 },
|
|
22
|
-
// alias of gpt-5
|
|
23
|
-
"gpt-4o": { input: 2.5, output: 10 }
|
|
24
|
-
};
|
|
25
|
-
function priceFor(model) {
|
|
26
|
-
const lower = model.toLowerCase();
|
|
27
|
-
if (PRICES[lower]) return PRICES[lower];
|
|
28
|
-
for (const key of Object.keys(PRICES)) {
|
|
29
|
-
if (lower.startsWith(key)) return PRICES[key];
|
|
30
|
-
}
|
|
31
|
-
return null;
|
|
32
|
-
}
|
|
33
|
-
function emptyModelUsage(model) {
|
|
34
|
-
return { model, inputTokens: 0, cachedReadTokens: 0, cacheCreateTokens: 0, outputTokens: 0, costUsd: 0 };
|
|
35
|
-
}
|
|
36
|
-
function addUsage(target, model, delta) {
|
|
37
|
-
const row = target[model] ??= emptyModelUsage(model);
|
|
38
|
-
if (delta.inputTokens) row.inputTokens += delta.inputTokens;
|
|
39
|
-
if (delta.cachedReadTokens) row.cachedReadTokens += delta.cachedReadTokens;
|
|
40
|
-
if (delta.cacheCreateTokens) row.cacheCreateTokens += delta.cacheCreateTokens;
|
|
41
|
-
if (delta.outputTokens) row.outputTokens += delta.outputTokens;
|
|
42
|
-
}
|
|
43
|
-
function recompute(usage) {
|
|
44
|
-
const price = priceFor(usage.model);
|
|
45
|
-
if (!price) return { ...usage, costUsd: 0 };
|
|
46
|
-
const inputCost = usage.inputTokens / 1e6 * price.input;
|
|
47
|
-
const cacheReadCost = price.cacheRead != null ? usage.cachedReadTokens / 1e6 * price.cacheRead : 0;
|
|
48
|
-
const cacheWriteCost = price.cacheWrite != null ? usage.cacheCreateTokens / 1e6 * price.cacheWrite : 0;
|
|
49
|
-
const outputCost = usage.outputTokens / 1e6 * price.output;
|
|
50
|
-
return { ...usage, costUsd: inputCost + cacheReadCost + cacheWriteCost + outputCost };
|
|
51
|
-
}
|
|
52
|
-
function listJsonlFiles(rootDir) {
|
|
53
|
-
const out = [];
|
|
54
|
-
if (!fs.existsSync(rootDir)) return out;
|
|
55
|
-
const stack = [rootDir];
|
|
56
|
-
while (stack.length > 0) {
|
|
57
|
-
const cur = stack.pop();
|
|
58
|
-
let entries;
|
|
59
|
-
try {
|
|
60
|
-
entries = fs.readdirSync(cur, { withFileTypes: true });
|
|
61
|
-
} catch {
|
|
62
|
-
continue;
|
|
63
|
-
}
|
|
64
|
-
for (const e of entries) {
|
|
65
|
-
const full = path.join(cur, e.name);
|
|
66
|
-
if (e.isDirectory()) stack.push(full);
|
|
67
|
-
else if (e.isFile() && e.name.endsWith(".jsonl")) out.push(full);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
return out;
|
|
71
|
-
}
|
|
72
|
-
function parseClaudeFile(file, seenMessageIds) {
|
|
73
|
-
let content;
|
|
74
|
-
try {
|
|
75
|
-
content = fs.readFileSync(file, "utf-8");
|
|
76
|
-
} catch {
|
|
77
|
-
return [];
|
|
78
|
-
}
|
|
79
|
-
const out = [];
|
|
80
|
-
for (const raw of content.split(/\r?\n/)) {
|
|
81
|
-
if (!raw.trim()) continue;
|
|
82
|
-
let obj;
|
|
83
|
-
try {
|
|
84
|
-
obj = JSON.parse(raw);
|
|
85
|
-
} catch {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const usage = obj?.message?.usage;
|
|
89
|
-
if (!usage) continue;
|
|
90
|
-
const messageId = obj?.message?.id;
|
|
91
|
-
if (messageId) {
|
|
92
|
-
if (seenMessageIds.has(messageId)) continue;
|
|
93
|
-
seenMessageIds.add(messageId);
|
|
94
|
-
}
|
|
95
|
-
const model = String(obj?.message?.model ?? "").toLowerCase();
|
|
96
|
-
if (!model) continue;
|
|
97
|
-
const tsRaw = obj?.timestamp ?? obj?.ts;
|
|
98
|
-
const ts = tsRaw ? new Date(tsRaw).getTime() : Date.now();
|
|
99
|
-
if (!Number.isFinite(ts)) continue;
|
|
100
|
-
out.push({
|
|
101
|
-
ts,
|
|
102
|
-
model,
|
|
103
|
-
delta: {
|
|
104
|
-
inputTokens: usage.input_tokens ?? 0,
|
|
105
|
-
cachedReadTokens: usage.cache_read_input_tokens ?? 0,
|
|
106
|
-
cacheCreateTokens: usage.cache_creation_input_tokens ?? 0,
|
|
107
|
-
outputTokens: usage.output_tokens ?? 0
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
return out;
|
|
112
|
-
}
|
|
113
|
-
function parseCodexFile(file) {
|
|
114
|
-
let content;
|
|
115
|
-
try {
|
|
116
|
-
content = fs.readFileSync(file, "utf-8");
|
|
117
|
-
} catch {
|
|
118
|
-
return [];
|
|
119
|
-
}
|
|
120
|
-
const out = [];
|
|
121
|
-
let prev = { input: 0, cachedInput: 0, output: 0, total: 0 };
|
|
122
|
-
let model = "gpt-5";
|
|
123
|
-
for (const raw of content.split(/\r?\n/)) {
|
|
124
|
-
if (!raw.trim()) continue;
|
|
125
|
-
let obj;
|
|
126
|
-
try {
|
|
127
|
-
obj = JSON.parse(raw);
|
|
128
|
-
} catch {
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
const modelHint = obj?.event_msg?.payload?.turn_context?.model ?? obj?.payload?.turn_context?.model ?? obj?.message?.model;
|
|
132
|
-
if (typeof modelHint === "string" && modelHint) model = modelHint.toLowerCase();
|
|
133
|
-
const payload = obj?.event_msg?.payload ?? obj?.payload;
|
|
134
|
-
if (!payload) continue;
|
|
135
|
-
if (payload.type !== "token_count") continue;
|
|
136
|
-
const t = payload;
|
|
137
|
-
const cur = {
|
|
138
|
-
input: t.input_tokens ?? 0,
|
|
139
|
-
cachedInput: t.cached_input_tokens ?? 0,
|
|
140
|
-
output: t.output_tokens ?? 0,
|
|
141
|
-
total: t.total_tokens ?? 0
|
|
142
|
-
};
|
|
143
|
-
const delta = {
|
|
144
|
-
inputTokens: Math.max(0, cur.input - prev.input),
|
|
145
|
-
cachedReadTokens: Math.max(0, cur.cachedInput - prev.cachedInput),
|
|
146
|
-
outputTokens: Math.max(0, cur.output - prev.output)
|
|
147
|
-
};
|
|
148
|
-
prev = cur;
|
|
149
|
-
const tsRaw = obj?.timestamp ?? obj?.event_msg?.timestamp ?? obj?.event_msg?.event_ts;
|
|
150
|
-
const ts = tsRaw ? new Date(tsRaw).getTime() : Date.now();
|
|
151
|
-
if (!Number.isFinite(ts)) continue;
|
|
152
|
-
out.push({ ts, model, delta });
|
|
153
|
-
}
|
|
154
|
-
return out;
|
|
155
|
-
}
|
|
156
|
-
function startOfTodayMs(now = Date.now()) {
|
|
157
|
-
const d = new Date(now);
|
|
158
|
-
d.setHours(0, 0, 0, 0);
|
|
159
|
-
return d.getTime();
|
|
160
|
-
}
|
|
161
|
-
var CLI_USAGE_PARSER_VERSION = 2;
|
|
162
|
-
async function gatherCliUsage() {
|
|
163
|
-
const home = os.homedir();
|
|
164
|
-
const claudeDir = path.join(home, ".claude", "projects");
|
|
165
|
-
const codexDir = path.join(home, ".codex", "sessions");
|
|
166
|
-
const claudeFiles = listJsonlFiles(claudeDir);
|
|
167
|
-
const codexFiles = listJsonlFiles(codexDir);
|
|
168
|
-
let skipped = 0;
|
|
169
|
-
const lines = [];
|
|
170
|
-
const seenMessageIds = /* @__PURE__ */ new Set();
|
|
171
|
-
for (const f of claudeFiles) {
|
|
172
|
-
const parsed = parseClaudeFile(f, seenMessageIds);
|
|
173
|
-
if (parsed.length === 0) skipped++;
|
|
174
|
-
else lines.push(...parsed);
|
|
175
|
-
}
|
|
176
|
-
for (const f of codexFiles) {
|
|
177
|
-
const parsed = parseCodexFile(f);
|
|
178
|
-
if (parsed.length === 0) skipped++;
|
|
179
|
-
else lines.push(...parsed);
|
|
180
|
-
}
|
|
181
|
-
const now = Date.now();
|
|
182
|
-
const cutoff5h = now - 5 * 60 * 60 * 1e3;
|
|
183
|
-
const cutoff24h = now - 24 * 60 * 60 * 1e3;
|
|
184
|
-
const cutoff7d = now - 7 * 24 * 60 * 60 * 1e3;
|
|
185
|
-
const startToday = startOfTodayMs(now);
|
|
186
|
-
const messageCounts = { last5h: 0, last24h: 0, last7d: 0, today: 0, lifetime: 0 };
|
|
187
|
-
const buckets = {
|
|
188
|
-
last5h: {},
|
|
189
|
-
last24h: {},
|
|
190
|
-
last7d: {},
|
|
191
|
-
today: {},
|
|
192
|
-
lifetime: {}
|
|
193
|
-
};
|
|
194
|
-
for (const ln of lines) {
|
|
195
|
-
addUsage(buckets.lifetime, ln.model, ln.delta);
|
|
196
|
-
messageCounts.lifetime++;
|
|
197
|
-
if (ln.ts >= cutoff7d) {
|
|
198
|
-
addUsage(buckets.last7d, ln.model, ln.delta);
|
|
199
|
-
messageCounts.last7d++;
|
|
200
|
-
}
|
|
201
|
-
if (ln.ts >= cutoff24h) {
|
|
202
|
-
addUsage(buckets.last24h, ln.model, ln.delta);
|
|
203
|
-
messageCounts.last24h++;
|
|
204
|
-
}
|
|
205
|
-
if (ln.ts >= cutoff5h) {
|
|
206
|
-
addUsage(buckets.last5h, ln.model, ln.delta);
|
|
207
|
-
messageCounts.last5h++;
|
|
208
|
-
}
|
|
209
|
-
if (ln.ts >= startToday) {
|
|
210
|
-
addUsage(buckets.today, ln.model, ln.delta);
|
|
211
|
-
messageCounts.today++;
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
function finalise(rec) {
|
|
215
|
-
return Object.values(rec).map(recompute).sort((a, b) => b.costUsd - a.costUsd);
|
|
216
|
-
}
|
|
217
|
-
const last5h = finalise(buckets.last5h);
|
|
218
|
-
const last24h = finalise(buckets.last24h);
|
|
219
|
-
const last7d = finalise(buckets.last7d);
|
|
220
|
-
const today = finalise(buckets.today);
|
|
221
|
-
const lifetime = finalise(buckets.lifetime);
|
|
222
|
-
function sum(rows, messages) {
|
|
223
|
-
return {
|
|
224
|
-
tokens: rows.reduce((s, r) => s + r.inputTokens + r.cachedReadTokens + r.cacheCreateTokens + r.outputTokens, 0),
|
|
225
|
-
costUsd: rows.reduce((s, r) => s + r.costUsd, 0),
|
|
226
|
-
messages
|
|
227
|
-
};
|
|
228
|
-
}
|
|
229
|
-
return {
|
|
230
|
-
generatedAt: new Date(now).toISOString(),
|
|
231
|
-
windows: { last5h, last24h, last7d, today, lifetime },
|
|
232
|
-
totals: {
|
|
233
|
-
last5h: sum(last5h, messageCounts.last5h),
|
|
234
|
-
last24h: sum(last24h, messageCounts.last24h),
|
|
235
|
-
last7d: sum(last7d, messageCounts.last7d),
|
|
236
|
-
today: sum(today, messageCounts.today),
|
|
237
|
-
lifetime: sum(lifetime, messageCounts.lifetime)
|
|
238
|
-
},
|
|
239
|
-
sources: { claudeFiles: claudeFiles.length, codexFiles: codexFiles.length, skipped }
|
|
240
|
-
};
|
|
241
|
-
}
|
|
242
|
-
export {
|
|
243
|
-
CLI_USAGE_PARSER_VERSION,
|
|
244
|
-
gatherCliUsage
|
|
245
|
-
};
|