@tokz/cli 0.2.6 → 0.2.8
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/LICENSE +21 -0
- package/README.md +20 -7
- package/dist/{Root-TUEV5MJB.js → Root-7T6ZYTLC.js} +419 -221
- package/dist/{chunk-Z5W5QH2Z.js → chunk-KO5GQL76.js} +14 -6
- package/dist/{chunk-6Y6MKFHZ.js → chunk-VJFLJJGX.js} +1 -1
- package/dist/cli.js +37 -12
- package/dist/{statusline-EXFMQYFF.js → statusline-5SJKELOG.js} +2 -2
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Abdallah-Alwarawreh
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# tokz
|
|
2
2
|
|
|
3
|
+
[](https://github.com/tokz-dev/cli/actions/workflows/ci.yml)
|
|
4
|
+
|
|
3
5
|
**See where your coding agents' tokens — and dollars — actually go.**
|
|
4
6
|
|
|
5
7
|
tokz reads the session logs your coding agents already write to disk and turns
|
|
@@ -39,20 +41,31 @@ tokz auto-detects each agent from its local data directory. No configuration.
|
|
|
39
41
|
| Kilo | `~/.local/share/kilo/kilo.db` | ✅ parsed (SQLite) |
|
|
40
42
|
| Goose | `~/.local/share/goose` | ✅ parsed (SQLite) |
|
|
41
43
|
| Hermes | `~/.hermes/state.db` | ✅ parsed (SQLite) |
|
|
44
|
+
| GitHub Copilot CLI | `~/.copilot/otel` | ✅ parsed (OpenTelemetry) |
|
|
45
|
+
| Amp | `~/.local/share/amp` | ✅ parsed |
|
|
42
46
|
| Antigravity | `~/.gemini/antigravity-cli` | ≈ estimated¹ |
|
|
43
|
-
|
|
|
44
|
-
| Amp | `~/.local/share/amp` | 🔍 detected, not parsed² |
|
|
47
|
+
| Cursor | `~/.cursor` | 🔍 detected, not parsed² |
|
|
45
48
|
|
|
46
49
|
¹ Antigravity stores no token counts on disk. tokz reads real per-model turn
|
|
47
50
|
counts from its conversation databases and estimates tokens from content size
|
|
48
51
|
(~4 chars/token); the UI labels every Antigravity number **estimated**.
|
|
49
52
|
|
|
50
|
-
²
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
² Cursor keeps no token counts locally — its usage lives server-side behind
|
|
54
|
+
auth — so it's surfaced with that reason rather than parsed.
|
|
55
|
+
|
|
56
|
+
The SQLite-backed agents are read with a small built-in pure-JS SQLite reader,
|
|
57
|
+
so there's no native dependency and `npx` just works. When more than one agent
|
|
58
|
+
has data, the picker adds an **All agents** row that merges every agent's
|
|
59
|
+
projects into one cross-agent total.
|
|
60
|
+
|
|
61
|
+
## Config file (optional)
|
|
53
62
|
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
Drop defaults in `~/.tokz/config.json` so you don't retype flags; any CLI flag
|
|
64
|
+
still overrides it:
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{ "timezone": "local", "offline": false, "costSource": "auto", "days": 30 }
|
|
68
|
+
```
|
|
56
69
|
|
|
57
70
|
## Interactive TUI
|
|
58
71
|
|
|
@@ -13,28 +13,29 @@ import {
|
|
|
13
13
|
duration,
|
|
14
14
|
emptyUsage,
|
|
15
15
|
groupDaily,
|
|
16
|
-
|
|
16
|
+
parseTranscriptContent,
|
|
17
17
|
pct,
|
|
18
18
|
pct1,
|
|
19
19
|
relativeDate,
|
|
20
20
|
sanitizeProjectPath,
|
|
21
21
|
shortModel,
|
|
22
22
|
usd
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-KO5GQL76.js";
|
|
24
24
|
|
|
25
25
|
// src/ui/Root.tsx
|
|
26
26
|
import { useEffect as useEffect2, useState as useState6 } from "react";
|
|
27
27
|
import { Box as Box10, Text as Text11, useApp as useApp2 } from "ink";
|
|
28
28
|
|
|
29
29
|
// src/agents/index.ts
|
|
30
|
-
import { access as
|
|
31
|
-
import { homedir as
|
|
32
|
-
import { join as
|
|
30
|
+
import { access as access17 } from "fs/promises";
|
|
31
|
+
import { homedir as homedir18 } from "os";
|
|
32
|
+
import { join as join18 } from "path";
|
|
33
33
|
|
|
34
|
-
// src/agents/
|
|
35
|
-
import { access
|
|
34
|
+
// src/agents/amp.ts
|
|
35
|
+
import { access } from "fs/promises";
|
|
36
36
|
import { homedir as homedir2 } from "os";
|
|
37
37
|
import { join as join2 } from "path";
|
|
38
|
+
import { glob as glob2 } from "tinyglobby";
|
|
38
39
|
|
|
39
40
|
// src/projects.ts
|
|
40
41
|
import { readFile } from "fs/promises";
|
|
@@ -172,12 +173,18 @@ async function loadProjects(home = homedir(), onProgress) {
|
|
|
172
173
|
const seenMessageIds = /* @__PURE__ */ new Map();
|
|
173
174
|
const seenToolIds = /* @__PURE__ */ new Set();
|
|
174
175
|
let parsed = 0;
|
|
176
|
+
const contents = /* @__PURE__ */ new Map();
|
|
177
|
+
await Promise.all(
|
|
178
|
+
files.map(
|
|
179
|
+
(f) => readFile(f, "utf8").then((c) => void contents.set(f, c)).catch(() => void contents.set(f, ""))
|
|
180
|
+
)
|
|
181
|
+
);
|
|
175
182
|
const out = [];
|
|
176
183
|
for (const [dir, dirFiles] of byDir) {
|
|
177
184
|
onProgress?.({ parsed, total: files.length, currentProject: realMap.get(dir) ?? dir });
|
|
178
185
|
const sessions = [];
|
|
179
186
|
for (const f of dirFiles) {
|
|
180
|
-
sessions.push(
|
|
187
|
+
sessions.push(parseTranscriptContent(contents.get(f) ?? "", f, seenMessageIds, seenToolIds));
|
|
181
188
|
parsed += 1;
|
|
182
189
|
onProgress?.({ parsed, total: files.length, currentProject: realMap.get(dir) ?? dir });
|
|
183
190
|
}
|
|
@@ -197,11 +204,180 @@ async function loadProjects(home = homedir(), onProgress) {
|
|
|
197
204
|
return out;
|
|
198
205
|
}
|
|
199
206
|
|
|
207
|
+
// src/agents/usage.ts
|
|
208
|
+
import { readFile as readFile2 } from "fs/promises";
|
|
209
|
+
async function readJson(file) {
|
|
210
|
+
try {
|
|
211
|
+
return JSON.parse(await readFile2(file, "utf8"));
|
|
212
|
+
} catch {
|
|
213
|
+
return void 0;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
async function readJsonl(file) {
|
|
217
|
+
let raw;
|
|
218
|
+
try {
|
|
219
|
+
raw = await readFile2(file, "utf8");
|
|
220
|
+
} catch {
|
|
221
|
+
return [];
|
|
222
|
+
}
|
|
223
|
+
const out = [];
|
|
224
|
+
for (const line of raw.split("\n")) {
|
|
225
|
+
if (!line.trim()) continue;
|
|
226
|
+
try {
|
|
227
|
+
out.push(JSON.parse(line));
|
|
228
|
+
} catch {
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
function sessionFromRecords(file, cwd, records) {
|
|
234
|
+
const stats = { file, cwd, usageByModel: {}, toolCalls: {}, toolCostUsd: {}, dailyUsage: {} };
|
|
235
|
+
for (const r of records) {
|
|
236
|
+
if (r.input + r.output + r.cacheRead + r.cacheWrite === 0) continue;
|
|
237
|
+
if (r.ts) {
|
|
238
|
+
if (!stats.firstTs || r.ts < stats.firstTs) stats.firstTs = r.ts;
|
|
239
|
+
if (!stats.lastTs || r.ts > stats.lastTs) stats.lastTs = r.ts;
|
|
240
|
+
}
|
|
241
|
+
const accs = [stats.usageByModel[r.model] ??= emptyUsage()];
|
|
242
|
+
if (r.ts) {
|
|
243
|
+
const day = stats.dailyUsage[r.ts.slice(0, 10)] ??= {};
|
|
244
|
+
accs.push(day[r.model] ??= emptyUsage());
|
|
245
|
+
}
|
|
246
|
+
for (const u of accs) {
|
|
247
|
+
u.inputTokens += r.input;
|
|
248
|
+
u.outputTokens += r.output;
|
|
249
|
+
u.cacheReadTokens += r.cacheRead;
|
|
250
|
+
u.cacheCreationTokens += r.cacheWrite;
|
|
251
|
+
u.turns += 1;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return stats;
|
|
255
|
+
}
|
|
256
|
+
function pickNum(obj, keys) {
|
|
257
|
+
if (!obj || typeof obj !== "object") return 0;
|
|
258
|
+
const rec = obj;
|
|
259
|
+
for (const k of keys) {
|
|
260
|
+
const v = rec[k];
|
|
261
|
+
const n = typeof v === "string" ? Number(v) : typeof v === "number" ? v : NaN;
|
|
262
|
+
if (Number.isFinite(n)) return Math.max(0, Math.trunc(n));
|
|
263
|
+
}
|
|
264
|
+
return 0;
|
|
265
|
+
}
|
|
266
|
+
function str(obj, key) {
|
|
267
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
268
|
+
const v = obj[key];
|
|
269
|
+
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
270
|
+
}
|
|
271
|
+
function toIso(value) {
|
|
272
|
+
if (typeof value === "string") {
|
|
273
|
+
const t = Date.parse(value);
|
|
274
|
+
return Number.isFinite(t) ? new Date(t).toISOString() : void 0;
|
|
275
|
+
}
|
|
276
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
277
|
+
let ms = Number(value);
|
|
278
|
+
if (!Number.isFinite(ms) || ms <= 0) return void 0;
|
|
279
|
+
if (ms < 1e12) ms *= 1e3;
|
|
280
|
+
return new Date(ms).toISOString();
|
|
281
|
+
}
|
|
282
|
+
return void 0;
|
|
283
|
+
}
|
|
284
|
+
function deepFind(node, keys, depth = 6) {
|
|
285
|
+
if (!node || typeof node !== "object" || depth < 0) return void 0;
|
|
286
|
+
const rec = node;
|
|
287
|
+
if (!Array.isArray(node) && keys.some((k) => k in rec)) return rec;
|
|
288
|
+
for (const v of Object.values(rec)) {
|
|
289
|
+
const hit = deepFind(v, keys, depth - 1);
|
|
290
|
+
if (hit) return hit;
|
|
291
|
+
}
|
|
292
|
+
return void 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// src/agents/amp.ts
|
|
296
|
+
function ampRoot(home) {
|
|
297
|
+
const env = process.env.AMP_DATA_DIR;
|
|
298
|
+
return env ? env.split(",")[0].trim() : join2(home ?? homedir2(), ".local", "share", "amp");
|
|
299
|
+
}
|
|
300
|
+
function parseThread(thread) {
|
|
301
|
+
const messages = thread?.messages ?? [];
|
|
302
|
+
const events = thread?.usageLedger?.events;
|
|
303
|
+
const records = [];
|
|
304
|
+
if (Array.isArray(events)) {
|
|
305
|
+
const cacheByMsg = /* @__PURE__ */ new Map();
|
|
306
|
+
for (const m of messages) {
|
|
307
|
+
if (typeof m.messageId === "number" && m.usage) {
|
|
308
|
+
cacheByMsg.set(m.messageId, {
|
|
309
|
+
cw: m.usage.cacheCreationInputTokens ?? 0,
|
|
310
|
+
cr: m.usage.cacheReadInputTokens ?? 0
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
for (const e of events) {
|
|
315
|
+
const tokens = e.tokens;
|
|
316
|
+
const toMsg = e.toMessageId;
|
|
317
|
+
const cache = typeof toMsg === "number" ? cacheByMsg.get(toMsg) : void 0;
|
|
318
|
+
records.push({
|
|
319
|
+
model: str(e, "model") ?? "amp-unknown",
|
|
320
|
+
ts: toIso(str(e, "timestamp")),
|
|
321
|
+
input: pickNum(tokens, ["input"]),
|
|
322
|
+
output: pickNum(tokens, ["output"]),
|
|
323
|
+
cacheRead: cache?.cr ?? 0,
|
|
324
|
+
cacheWrite: cache?.cw ?? 0
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
return records;
|
|
328
|
+
}
|
|
329
|
+
for (const m of messages) {
|
|
330
|
+
if (m.role !== "assistant" || !m.usage) continue;
|
|
331
|
+
records.push({
|
|
332
|
+
model: m.model ?? "amp-unknown",
|
|
333
|
+
ts: toIso(m.timestamp),
|
|
334
|
+
input: m.usage.inputTokens ?? 0,
|
|
335
|
+
output: m.usage.outputTokens ?? 0,
|
|
336
|
+
cacheRead: m.usage.cacheReadInputTokens ?? 0,
|
|
337
|
+
cacheWrite: m.usage.cacheCreationInputTokens ?? 0
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
return records;
|
|
341
|
+
}
|
|
342
|
+
async function loadAmpProjects(home, onProgress) {
|
|
343
|
+
const files = await glob2(["threads/**/*.json"], { cwd: ampRoot(home), absolute: true }).catch(
|
|
344
|
+
() => []
|
|
345
|
+
);
|
|
346
|
+
const sessions = [];
|
|
347
|
+
let parsed = 0;
|
|
348
|
+
for (const f of files) {
|
|
349
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Amp threads" });
|
|
350
|
+
const thread = await readJson(f);
|
|
351
|
+
const id = str(thread, "id") ?? f;
|
|
352
|
+
sessions.push(sessionFromRecords(id, void 0, parseThread(thread)));
|
|
353
|
+
parsed += 1;
|
|
354
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Amp threads" });
|
|
355
|
+
}
|
|
356
|
+
return groupSessionsByCwd("amp", sessions);
|
|
357
|
+
}
|
|
358
|
+
var ampAdapter = {
|
|
359
|
+
id: "amp",
|
|
360
|
+
name: "Amp",
|
|
361
|
+
supported: true,
|
|
362
|
+
async detect(home) {
|
|
363
|
+
try {
|
|
364
|
+
await access(join2(ampRoot(home), "threads"));
|
|
365
|
+
return true;
|
|
366
|
+
} catch {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
},
|
|
370
|
+
loadProjects: loadAmpProjects
|
|
371
|
+
};
|
|
372
|
+
|
|
200
373
|
// src/agents/antigravity.ts
|
|
374
|
+
import { access as access2, readFile as readFile3, readdir } from "fs/promises";
|
|
375
|
+
import { homedir as homedir3 } from "os";
|
|
376
|
+
import { join as join3 } from "path";
|
|
201
377
|
var ESTIMATED_CHARS_PER_TOKEN = 4;
|
|
202
378
|
var INPUT_SHARE = 0.85;
|
|
203
379
|
function antigravityRoot(home) {
|
|
204
|
-
return
|
|
380
|
+
return join3(home ?? homedir3(), ".gemini", "antigravity-cli");
|
|
205
381
|
}
|
|
206
382
|
function modelIdFromDisplayName(name) {
|
|
207
383
|
const bare = name.replace(/\s*\(.*\)\s*$/, "").trim();
|
|
@@ -235,7 +411,7 @@ function scanConversation(buf) {
|
|
|
235
411
|
}
|
|
236
412
|
async function readHistory(root) {
|
|
237
413
|
try {
|
|
238
|
-
const raw = await
|
|
414
|
+
const raw = await readFile3(join3(root, "history.jsonl"), "utf8");
|
|
239
415
|
const out = [];
|
|
240
416
|
for (const line of raw.split("\n")) {
|
|
241
417
|
if (!line.trim()) continue;
|
|
@@ -251,7 +427,7 @@ async function readHistory(root) {
|
|
|
251
427
|
}
|
|
252
428
|
async function loadAntigravityProjects(home, onProgress) {
|
|
253
429
|
const root = antigravityRoot(home);
|
|
254
|
-
const convDir =
|
|
430
|
+
const convDir = join3(root, "conversations");
|
|
255
431
|
let dbFiles;
|
|
256
432
|
try {
|
|
257
433
|
dbFiles = (await readdir(convDir)).filter((f) => f.endsWith(".db"));
|
|
@@ -275,7 +451,7 @@ async function loadAntigravityProjects(home, onProgress) {
|
|
|
275
451
|
onProgress?.({ parsed, total: dbFiles.length, currentProject: "Antigravity conversations" });
|
|
276
452
|
let scan;
|
|
277
453
|
try {
|
|
278
|
-
scan = scanConversation(await
|
|
454
|
+
scan = scanConversation(await readFile3(join3(convDir, file)));
|
|
279
455
|
} catch {
|
|
280
456
|
parsed += 1;
|
|
281
457
|
continue;
|
|
@@ -333,11 +509,11 @@ var antigravityAdapter = {
|
|
|
333
509
|
estimateNote: "no token counts on disk \u2014 tokens/costs estimated from conversation size",
|
|
334
510
|
async detect(home) {
|
|
335
511
|
try {
|
|
336
|
-
await
|
|
512
|
+
await access2(join3(antigravityRoot(home), "conversations"));
|
|
337
513
|
return true;
|
|
338
514
|
} catch {
|
|
339
515
|
try {
|
|
340
|
-
await
|
|
516
|
+
await access2(join3(home ?? homedir3(), ".gemini", "antigravity", "conversations"));
|
|
341
517
|
return true;
|
|
342
518
|
} catch {
|
|
343
519
|
return false;
|
|
@@ -348,16 +524,16 @@ var antigravityAdapter = {
|
|
|
348
524
|
};
|
|
349
525
|
|
|
350
526
|
// src/agents/claude.ts
|
|
351
|
-
import { access as
|
|
352
|
-
import { homedir as
|
|
353
|
-
import { join as
|
|
527
|
+
import { access as access3 } from "fs/promises";
|
|
528
|
+
import { homedir as homedir4 } from "os";
|
|
529
|
+
import { join as join4 } from "path";
|
|
354
530
|
var claudeAdapter = {
|
|
355
531
|
id: "claude",
|
|
356
532
|
name: "Claude Code",
|
|
357
533
|
supported: true,
|
|
358
|
-
async detect(home =
|
|
534
|
+
async detect(home = homedir4()) {
|
|
359
535
|
try {
|
|
360
|
-
await
|
|
536
|
+
await access3(join4(home, ".claude", "projects"));
|
|
361
537
|
return true;
|
|
362
538
|
} catch {
|
|
363
539
|
return false;
|
|
@@ -367,106 +543,16 @@ var claudeAdapter = {
|
|
|
367
543
|
};
|
|
368
544
|
|
|
369
545
|
// src/agents/codebuff.ts
|
|
370
|
-
import { access as
|
|
371
|
-
import { homedir as
|
|
372
|
-
import { join as
|
|
373
|
-
import { glob as
|
|
374
|
-
|
|
375
|
-
// src/agents/usage.ts
|
|
376
|
-
import { readFile as readFile3 } from "fs/promises";
|
|
377
|
-
async function readJson(file) {
|
|
378
|
-
try {
|
|
379
|
-
return JSON.parse(await readFile3(file, "utf8"));
|
|
380
|
-
} catch {
|
|
381
|
-
return void 0;
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
async function readJsonl(file) {
|
|
385
|
-
let raw;
|
|
386
|
-
try {
|
|
387
|
-
raw = await readFile3(file, "utf8");
|
|
388
|
-
} catch {
|
|
389
|
-
return [];
|
|
390
|
-
}
|
|
391
|
-
const out = [];
|
|
392
|
-
for (const line of raw.split("\n")) {
|
|
393
|
-
if (!line.trim()) continue;
|
|
394
|
-
try {
|
|
395
|
-
out.push(JSON.parse(line));
|
|
396
|
-
} catch {
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
return out;
|
|
400
|
-
}
|
|
401
|
-
function sessionFromRecords(file, cwd, records) {
|
|
402
|
-
const stats = { file, cwd, usageByModel: {}, toolCalls: {}, toolCostUsd: {}, dailyUsage: {} };
|
|
403
|
-
for (const r of records) {
|
|
404
|
-
if (r.input + r.output + r.cacheRead + r.cacheWrite === 0) continue;
|
|
405
|
-
if (r.ts) {
|
|
406
|
-
if (!stats.firstTs || r.ts < stats.firstTs) stats.firstTs = r.ts;
|
|
407
|
-
if (!stats.lastTs || r.ts > stats.lastTs) stats.lastTs = r.ts;
|
|
408
|
-
}
|
|
409
|
-
const accs = [stats.usageByModel[r.model] ??= emptyUsage()];
|
|
410
|
-
if (r.ts) {
|
|
411
|
-
const day = stats.dailyUsage[r.ts.slice(0, 10)] ??= {};
|
|
412
|
-
accs.push(day[r.model] ??= emptyUsage());
|
|
413
|
-
}
|
|
414
|
-
for (const u of accs) {
|
|
415
|
-
u.inputTokens += r.input;
|
|
416
|
-
u.outputTokens += r.output;
|
|
417
|
-
u.cacheReadTokens += r.cacheRead;
|
|
418
|
-
u.cacheCreationTokens += r.cacheWrite;
|
|
419
|
-
u.turns += 1;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
return stats;
|
|
423
|
-
}
|
|
424
|
-
function pickNum(obj, keys) {
|
|
425
|
-
if (!obj || typeof obj !== "object") return 0;
|
|
426
|
-
const rec = obj;
|
|
427
|
-
for (const k of keys) {
|
|
428
|
-
const v = rec[k];
|
|
429
|
-
const n = typeof v === "string" ? Number(v) : typeof v === "number" ? v : NaN;
|
|
430
|
-
if (Number.isFinite(n)) return Math.max(0, Math.trunc(n));
|
|
431
|
-
}
|
|
432
|
-
return 0;
|
|
433
|
-
}
|
|
434
|
-
function str(obj, key) {
|
|
435
|
-
if (!obj || typeof obj !== "object") return void 0;
|
|
436
|
-
const v = obj[key];
|
|
437
|
-
return typeof v === "string" && v.length > 0 ? v : void 0;
|
|
438
|
-
}
|
|
439
|
-
function toIso(value) {
|
|
440
|
-
if (typeof value === "string") {
|
|
441
|
-
const t = Date.parse(value);
|
|
442
|
-
return Number.isFinite(t) ? new Date(t).toISOString() : void 0;
|
|
443
|
-
}
|
|
444
|
-
if (typeof value === "number" || typeof value === "bigint") {
|
|
445
|
-
let ms = Number(value);
|
|
446
|
-
if (!Number.isFinite(ms) || ms <= 0) return void 0;
|
|
447
|
-
if (ms < 1e12) ms *= 1e3;
|
|
448
|
-
return new Date(ms).toISOString();
|
|
449
|
-
}
|
|
450
|
-
return void 0;
|
|
451
|
-
}
|
|
452
|
-
function deepFind(node, keys, depth = 6) {
|
|
453
|
-
if (!node || typeof node !== "object" || depth < 0) return void 0;
|
|
454
|
-
const rec = node;
|
|
455
|
-
if (!Array.isArray(node) && keys.some((k) => k in rec)) return rec;
|
|
456
|
-
for (const v of Object.values(rec)) {
|
|
457
|
-
const hit = deepFind(v, keys, depth - 1);
|
|
458
|
-
if (hit) return hit;
|
|
459
|
-
}
|
|
460
|
-
return void 0;
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
// src/agents/codebuff.ts
|
|
546
|
+
import { access as access4, stat } from "fs/promises";
|
|
547
|
+
import { homedir as homedir5 } from "os";
|
|
548
|
+
import { join as join5 } from "path";
|
|
549
|
+
import { glob as glob3 } from "tinyglobby";
|
|
464
550
|
var CHANNELS = ["manicode", "manicode-dev", "manicode-staging"];
|
|
465
551
|
function codebuffRoots(home) {
|
|
466
552
|
const env = process.env.CODEBUFF_DATA_DIR;
|
|
467
553
|
if (env) return env.split(",").map((s) => s.trim()).filter(Boolean);
|
|
468
|
-
const h = home ??
|
|
469
|
-
return CHANNELS.map((c) =>
|
|
554
|
+
const h = home ?? homedir5();
|
|
555
|
+
return CHANNELS.map((c) => join5(h, ".config", c));
|
|
470
556
|
}
|
|
471
557
|
function isAssistant(msg) {
|
|
472
558
|
const role = str(msg, "variant") ?? str(msg, "role");
|
|
@@ -501,7 +587,7 @@ async function loadCodebuffProjects(home, onProgress) {
|
|
|
501
587
|
const files = [];
|
|
502
588
|
for (const root of codebuffRoots(home)) {
|
|
503
589
|
files.push(
|
|
504
|
-
...await
|
|
590
|
+
...await glob3(["**/chat-messages.json"], { cwd: root, absolute: true }).catch(() => [])
|
|
505
591
|
);
|
|
506
592
|
}
|
|
507
593
|
const sessions = [];
|
|
@@ -521,7 +607,7 @@ var codebuffAdapter = {
|
|
|
521
607
|
async detect(home) {
|
|
522
608
|
for (const root of codebuffRoots(home)) {
|
|
523
609
|
try {
|
|
524
|
-
await
|
|
610
|
+
await access4(root);
|
|
525
611
|
return true;
|
|
526
612
|
} catch {
|
|
527
613
|
}
|
|
@@ -533,11 +619,11 @@ var codebuffAdapter = {
|
|
|
533
619
|
|
|
534
620
|
// src/agents/codex.ts
|
|
535
621
|
import { createReadStream } from "fs";
|
|
536
|
-
import { access as
|
|
622
|
+
import { access as access5 } from "fs/promises";
|
|
537
623
|
import { createInterface } from "readline";
|
|
538
|
-
import { homedir as
|
|
539
|
-
import { join as
|
|
540
|
-
import { glob as
|
|
624
|
+
import { homedir as homedir6 } from "os";
|
|
625
|
+
import { join as join6 } from "path";
|
|
626
|
+
import { glob as glob4 } from "tinyglobby";
|
|
541
627
|
import { z } from "zod";
|
|
542
628
|
var TokenTotals = z.object({
|
|
543
629
|
input_tokens: z.number().catch(0).default(0),
|
|
@@ -560,8 +646,8 @@ var Line = z.object({
|
|
|
560
646
|
}).passthrough().optional()
|
|
561
647
|
});
|
|
562
648
|
function codexHome(home) {
|
|
563
|
-
if (home) return
|
|
564
|
-
return process.env.CODEX_HOME ??
|
|
649
|
+
if (home) return join6(home, ".codex");
|
|
650
|
+
return process.env.CODEX_HOME ?? join6(homedir6(), ".codex");
|
|
565
651
|
}
|
|
566
652
|
var DEFAULT_MODEL = "gpt-5";
|
|
567
653
|
async function parseCodexRollout(file) {
|
|
@@ -638,7 +724,7 @@ async function parseCodexRollout(file) {
|
|
|
638
724
|
}
|
|
639
725
|
async function loadCodexProjects(home, onProgress) {
|
|
640
726
|
const root = codexHome(home);
|
|
641
|
-
const files = await
|
|
727
|
+
const files = await glob4(["sessions/**/*.jsonl", "archived_sessions/**/*.jsonl"], {
|
|
642
728
|
cwd: root,
|
|
643
729
|
absolute: true
|
|
644
730
|
}).catch(() => []);
|
|
@@ -659,7 +745,7 @@ var codexAdapter = {
|
|
|
659
745
|
supported: true,
|
|
660
746
|
async detect(home) {
|
|
661
747
|
try {
|
|
662
|
-
await
|
|
748
|
+
await access5(join6(codexHome(home), "sessions"));
|
|
663
749
|
return true;
|
|
664
750
|
} catch {
|
|
665
751
|
return false;
|
|
@@ -668,13 +754,113 @@ var codexAdapter = {
|
|
|
668
754
|
loadProjects: loadCodexProjects
|
|
669
755
|
};
|
|
670
756
|
|
|
757
|
+
// src/agents/copilot.ts
|
|
758
|
+
import { access as access6 } from "fs/promises";
|
|
759
|
+
import { homedir as homedir7 } from "os";
|
|
760
|
+
import { join as join7 } from "path";
|
|
761
|
+
import { glob as glob5 } from "tinyglobby";
|
|
762
|
+
function copilotDir(home) {
|
|
763
|
+
return join7(home ?? homedir7(), ".copilot", "otel");
|
|
764
|
+
}
|
|
765
|
+
var MODEL_ATTRS = ["gen_ai.response.model", "gen_ai.request.model"];
|
|
766
|
+
var SESSION_ATTRS = [
|
|
767
|
+
"gen_ai.conversation.id",
|
|
768
|
+
"copilot_chat.session_id",
|
|
769
|
+
"copilot_chat.chat_session_id",
|
|
770
|
+
"session.id",
|
|
771
|
+
"github.copilot.interaction_id",
|
|
772
|
+
"gen_ai.response.id"
|
|
773
|
+
];
|
|
774
|
+
function unwrap(v) {
|
|
775
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
776
|
+
const o = v;
|
|
777
|
+
return o.intValue ?? o.doubleValue ?? o.stringValue ?? o.value ?? v;
|
|
778
|
+
}
|
|
779
|
+
return v;
|
|
780
|
+
}
|
|
781
|
+
function attrNum(attrs, key) {
|
|
782
|
+
const v = unwrap(attrs[key]);
|
|
783
|
+
const n = typeof v === "string" ? Number(v) : typeof v === "number" ? v : NaN;
|
|
784
|
+
return Number.isFinite(n) ? Math.max(0, Math.trunc(n)) : 0;
|
|
785
|
+
}
|
|
786
|
+
function attrStr(attrs, keys) {
|
|
787
|
+
for (const k of keys) {
|
|
788
|
+
const v = unwrap(attrs[k]);
|
|
789
|
+
if (typeof v === "string" && v.length > 0) return v;
|
|
790
|
+
if (typeof v === "number") return String(v);
|
|
791
|
+
}
|
|
792
|
+
return void 0;
|
|
793
|
+
}
|
|
794
|
+
function parseOtelFile(records) {
|
|
795
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
796
|
+
const seen = /* @__PURE__ */ new Set();
|
|
797
|
+
for (const rec of records) {
|
|
798
|
+
const attrs = rec.attributes;
|
|
799
|
+
if (!attrs || typeof attrs !== "object") continue;
|
|
800
|
+
const input = attrNum(attrs, "gen_ai.usage.input_tokens");
|
|
801
|
+
const output = attrNum(attrs, "gen_ai.usage.output_tokens");
|
|
802
|
+
const cacheRead = attrNum(attrs, "gen_ai.usage.cache_read.input_tokens");
|
|
803
|
+
const cacheWrite = attrNum(attrs, "gen_ai.usage.cache_write.input_tokens") || attrNum(attrs, "gen_ai.usage.cache_creation.input_tokens");
|
|
804
|
+
const reasoning = attrNum(attrs, "gen_ai.usage.reasoning.output_tokens") || attrNum(attrs, "gen_ai.usage.reasoning_tokens");
|
|
805
|
+
if (input + output + cacheRead + cacheWrite === 0) continue;
|
|
806
|
+
const session = attrStr(attrs, SESSION_ATTRS) ?? "copilot";
|
|
807
|
+
const r = rec;
|
|
808
|
+
const dedup = attrStr(attrs, ["gen_ai.response.id"]) ?? `${String(unwrap(r.spanId) ?? "")}:${session}:${input}:${output}`;
|
|
809
|
+
if (seen.has(dedup)) continue;
|
|
810
|
+
seen.add(dedup);
|
|
811
|
+
const list = bySession.get(session) ?? [];
|
|
812
|
+
list.push({
|
|
813
|
+
model: attrStr(attrs, MODEL_ATTRS) ?? "copilot-unknown",
|
|
814
|
+
ts: toIso(r.timestamp) ?? toIso(r.startTime),
|
|
815
|
+
input,
|
|
816
|
+
output: output + reasoning,
|
|
817
|
+
cacheRead,
|
|
818
|
+
cacheWrite
|
|
819
|
+
});
|
|
820
|
+
bySession.set(session, list);
|
|
821
|
+
}
|
|
822
|
+
return bySession;
|
|
823
|
+
}
|
|
824
|
+
async function loadCopilotProjects(home, onProgress) {
|
|
825
|
+
const files = await glob5(["**/*.jsonl"], { cwd: copilotDir(home), absolute: true }).catch(() => []);
|
|
826
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
827
|
+
let parsed = 0;
|
|
828
|
+
for (const f of files) {
|
|
829
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Copilot otel" });
|
|
830
|
+
for (const [session, records] of parseOtelFile(await readJsonl(f))) {
|
|
831
|
+
const list = bySession.get(session) ?? [];
|
|
832
|
+
list.push(...records);
|
|
833
|
+
bySession.set(session, list);
|
|
834
|
+
}
|
|
835
|
+
parsed += 1;
|
|
836
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Copilot otel" });
|
|
837
|
+
}
|
|
838
|
+
const sessions = [];
|
|
839
|
+
for (const [id, records] of bySession) sessions.push(sessionFromRecords(id, void 0, records));
|
|
840
|
+
return groupSessionsByCwd("copilot", sessions);
|
|
841
|
+
}
|
|
842
|
+
var copilotAdapter = {
|
|
843
|
+
id: "copilot",
|
|
844
|
+
name: "GitHub Copilot CLI",
|
|
845
|
+
supported: true,
|
|
846
|
+
async detect(home) {
|
|
847
|
+
try {
|
|
848
|
+
await access6(copilotDir(home));
|
|
849
|
+
return true;
|
|
850
|
+
} catch {
|
|
851
|
+
return false;
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
loadProjects: loadCopilotProjects
|
|
855
|
+
};
|
|
856
|
+
|
|
671
857
|
// src/agents/droid.ts
|
|
672
|
-
import { access as
|
|
673
|
-
import { homedir as
|
|
674
|
-
import { basename as basename2, join as
|
|
675
|
-
import { glob as
|
|
858
|
+
import { access as access7, stat as stat2 } from "fs/promises";
|
|
859
|
+
import { homedir as homedir8 } from "os";
|
|
860
|
+
import { basename as basename2, join as join8 } from "path";
|
|
861
|
+
import { glob as glob6 } from "tinyglobby";
|
|
676
862
|
function droidRoot(home) {
|
|
677
|
-
return process.env.DROID_SESSIONS_DIR ??
|
|
863
|
+
return process.env.DROID_SESSIONS_DIR ?? join8(home ?? homedir8(), ".factory", "sessions");
|
|
678
864
|
}
|
|
679
865
|
async function parseFile2(file) {
|
|
680
866
|
const settings = await readJson(file);
|
|
@@ -699,7 +885,7 @@ async function parseFile2(file) {
|
|
|
699
885
|
]);
|
|
700
886
|
}
|
|
701
887
|
async function loadDroidProjects(home, onProgress) {
|
|
702
|
-
const files = (await
|
|
888
|
+
const files = (await glob6(["**/*.settings.json"], { cwd: droidRoot(home), absolute: true }).catch(() => [])).filter((f) => basename2(f).endsWith(".settings.json"));
|
|
703
889
|
const sessions = [];
|
|
704
890
|
let parsed = 0;
|
|
705
891
|
for (const f of files) {
|
|
@@ -716,7 +902,7 @@ var droidAdapter = {
|
|
|
716
902
|
supported: true,
|
|
717
903
|
async detect(home) {
|
|
718
904
|
try {
|
|
719
|
-
await
|
|
905
|
+
await access7(droidRoot(home));
|
|
720
906
|
return true;
|
|
721
907
|
} catch {
|
|
722
908
|
return false;
|
|
@@ -726,12 +912,12 @@ var droidAdapter = {
|
|
|
726
912
|
};
|
|
727
913
|
|
|
728
914
|
// src/agents/gemini.ts
|
|
729
|
-
import { access as
|
|
730
|
-
import { homedir as
|
|
731
|
-
import { join as
|
|
732
|
-
import { glob as
|
|
915
|
+
import { access as access8 } from "fs/promises";
|
|
916
|
+
import { homedir as homedir9 } from "os";
|
|
917
|
+
import { join as join9 } from "path";
|
|
918
|
+
import { glob as glob7 } from "tinyglobby";
|
|
733
919
|
function geminiRoot(home) {
|
|
734
|
-
return process.env.GEMINI_DATA_DIR ??
|
|
920
|
+
return process.env.GEMINI_DATA_DIR ?? join9(home ?? homedir9(), ".gemini", "tmp");
|
|
735
921
|
}
|
|
736
922
|
var IN = ["input", "prompt", "input_tokens", "prompt_tokens"];
|
|
737
923
|
var OUT = ["output", "candidates", "output_tokens", "candidates_tokens"];
|
|
@@ -763,7 +949,7 @@ async function parseFile3(file) {
|
|
|
763
949
|
return sessionFromRecords(file, void 0, records);
|
|
764
950
|
}
|
|
765
951
|
async function loadGeminiProjects(home, onProgress) {
|
|
766
|
-
const files = await
|
|
952
|
+
const files = await glob7(["**/*.json", "**/*.jsonl"], { cwd: geminiRoot(home), absolute: true }).catch(
|
|
767
953
|
() => []
|
|
768
954
|
);
|
|
769
955
|
const sessions = [];
|
|
@@ -782,7 +968,7 @@ var geminiAdapter = {
|
|
|
782
968
|
supported: true,
|
|
783
969
|
async detect(home) {
|
|
784
970
|
try {
|
|
785
|
-
await
|
|
971
|
+
await access8(geminiRoot(home));
|
|
786
972
|
return true;
|
|
787
973
|
} catch {
|
|
788
974
|
return false;
|
|
@@ -792,9 +978,9 @@ var geminiAdapter = {
|
|
|
792
978
|
};
|
|
793
979
|
|
|
794
980
|
// src/agents/goose.ts
|
|
795
|
-
import { access as
|
|
796
|
-
import { homedir as
|
|
797
|
-
import { join as
|
|
981
|
+
import { access as access9 } from "fs/promises";
|
|
982
|
+
import { homedir as homedir10 } from "os";
|
|
983
|
+
import { join as join10 } from "path";
|
|
798
984
|
|
|
799
985
|
// src/sqlite.ts
|
|
800
986
|
import { readFile as readFile4 } from "fs/promises";
|
|
@@ -977,9 +1163,9 @@ var GOOSE_DB_CANDIDATES = [
|
|
|
977
1163
|
];
|
|
978
1164
|
function gooseDbCandidates(home) {
|
|
979
1165
|
const root = process.env.GOOSE_PATH_ROOT;
|
|
980
|
-
if (root) return [
|
|
981
|
-
const h = home ??
|
|
982
|
-
return GOOSE_DB_CANDIDATES.map((parts) =>
|
|
1166
|
+
if (root) return [join10(root, "data", "sessions", "sessions.db")];
|
|
1167
|
+
const h = home ?? homedir10();
|
|
1168
|
+
return GOOSE_DB_CANDIDATES.map((parts) => join10(h, ...parts));
|
|
983
1169
|
}
|
|
984
1170
|
async function loadGooseProjects(home, onProgress) {
|
|
985
1171
|
const records = [];
|
|
@@ -1016,7 +1202,7 @@ var gooseAdapter = {
|
|
|
1016
1202
|
async detect(home) {
|
|
1017
1203
|
for (const db of gooseDbCandidates(home)) {
|
|
1018
1204
|
try {
|
|
1019
|
-
await
|
|
1205
|
+
await access9(db);
|
|
1020
1206
|
return true;
|
|
1021
1207
|
} catch {
|
|
1022
1208
|
}
|
|
@@ -1027,13 +1213,13 @@ var gooseAdapter = {
|
|
|
1027
1213
|
};
|
|
1028
1214
|
|
|
1029
1215
|
// src/agents/hermes.ts
|
|
1030
|
-
import { access as
|
|
1031
|
-
import { homedir as
|
|
1032
|
-
import { join as
|
|
1216
|
+
import { access as access10 } from "fs/promises";
|
|
1217
|
+
import { homedir as homedir11 } from "os";
|
|
1218
|
+
import { join as join11 } from "path";
|
|
1033
1219
|
function hermesDb(home) {
|
|
1034
1220
|
const env = process.env.HERMES_HOME;
|
|
1035
|
-
const root = env ? env.split(",")[0].trim() :
|
|
1036
|
-
return
|
|
1221
|
+
const root = env ? env.split(",")[0].trim() : join11(home ?? homedir11(), ".hermes");
|
|
1222
|
+
return join11(root, "state.db");
|
|
1037
1223
|
}
|
|
1038
1224
|
async function loadHermesProjects(home, onProgress) {
|
|
1039
1225
|
const rows = await readTable(hermesDb(home), "sessions").catch(() => []);
|
|
@@ -1058,7 +1244,7 @@ var hermesAdapter = {
|
|
|
1058
1244
|
supported: true,
|
|
1059
1245
|
async detect(home) {
|
|
1060
1246
|
try {
|
|
1061
|
-
await
|
|
1247
|
+
await access10(hermesDb(home));
|
|
1062
1248
|
return true;
|
|
1063
1249
|
} catch {
|
|
1064
1250
|
return false;
|
|
@@ -1068,14 +1254,14 @@ var hermesAdapter = {
|
|
|
1068
1254
|
};
|
|
1069
1255
|
|
|
1070
1256
|
// src/agents/kilo.ts
|
|
1071
|
-
import { access as
|
|
1072
|
-
import { homedir as
|
|
1073
|
-
import { join as
|
|
1257
|
+
import { access as access11 } from "fs/promises";
|
|
1258
|
+
import { homedir as homedir12 } from "os";
|
|
1259
|
+
import { join as join12 } from "path";
|
|
1074
1260
|
function kiloDir(home) {
|
|
1075
|
-
return process.env.KILO_DATA_DIR ??
|
|
1261
|
+
return process.env.KILO_DATA_DIR ?? join12(home ?? homedir12(), ".local", "share", "kilo");
|
|
1076
1262
|
}
|
|
1077
1263
|
async function loadKiloProjects(home, onProgress) {
|
|
1078
|
-
const db =
|
|
1264
|
+
const db = join12(kiloDir(home), "kilo.db");
|
|
1079
1265
|
let rows;
|
|
1080
1266
|
try {
|
|
1081
1267
|
rows = await readTable(db, "message");
|
|
@@ -1117,7 +1303,7 @@ var kiloAdapter = {
|
|
|
1117
1303
|
supported: true,
|
|
1118
1304
|
async detect(home) {
|
|
1119
1305
|
try {
|
|
1120
|
-
await
|
|
1306
|
+
await access11(join12(kiloDir(home), "kilo.db"));
|
|
1121
1307
|
return true;
|
|
1122
1308
|
} catch {
|
|
1123
1309
|
return false;
|
|
@@ -1127,17 +1313,17 @@ var kiloAdapter = {
|
|
|
1127
1313
|
};
|
|
1128
1314
|
|
|
1129
1315
|
// src/agents/kimi.ts
|
|
1130
|
-
import { access as
|
|
1131
|
-
import { homedir as
|
|
1132
|
-
import { join as
|
|
1133
|
-
import { glob as
|
|
1316
|
+
import { access as access12 } from "fs/promises";
|
|
1317
|
+
import { homedir as homedir13 } from "os";
|
|
1318
|
+
import { join as join13 } from "path";
|
|
1319
|
+
import { glob as glob8 } from "tinyglobby";
|
|
1134
1320
|
var KIMI_DIRS = [".kimi", ".kimi-code"];
|
|
1135
1321
|
var USAGE_KEYS = ["inputOther", "input_other", "inputCacheRead", "input_cache_read"];
|
|
1136
1322
|
function kimiRoots(home) {
|
|
1137
1323
|
const env = process.env.KIMI_DATA_DIR;
|
|
1138
1324
|
if (env) return env.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1139
|
-
const h = home ??
|
|
1140
|
-
return KIMI_DIRS.map((d) =>
|
|
1325
|
+
const h = home ?? homedir13();
|
|
1326
|
+
return KIMI_DIRS.map((d) => join13(h, d));
|
|
1141
1327
|
}
|
|
1142
1328
|
async function parseFile4(file) {
|
|
1143
1329
|
const records = [];
|
|
@@ -1159,7 +1345,7 @@ async function loadKimiProjects(home, onProgress) {
|
|
|
1159
1345
|
const files = [];
|
|
1160
1346
|
for (const root of kimiRoots(home)) {
|
|
1161
1347
|
files.push(
|
|
1162
|
-
...await
|
|
1348
|
+
...await glob8(["sessions/**/wire.jsonl"], { cwd: root, absolute: true }).catch(() => [])
|
|
1163
1349
|
);
|
|
1164
1350
|
}
|
|
1165
1351
|
const sessions = [];
|
|
@@ -1179,7 +1365,7 @@ var kimiAdapter = {
|
|
|
1179
1365
|
async detect(home) {
|
|
1180
1366
|
for (const root of kimiRoots(home)) {
|
|
1181
1367
|
try {
|
|
1182
|
-
await
|
|
1368
|
+
await access12(join13(root, "sessions"));
|
|
1183
1369
|
return true;
|
|
1184
1370
|
} catch {
|
|
1185
1371
|
}
|
|
@@ -1190,16 +1376,16 @@ var kimiAdapter = {
|
|
|
1190
1376
|
};
|
|
1191
1377
|
|
|
1192
1378
|
// src/agents/openclaw.ts
|
|
1193
|
-
import { access as
|
|
1194
|
-
import { homedir as
|
|
1195
|
-
import { join as
|
|
1196
|
-
import { glob as
|
|
1379
|
+
import { access as access13 } from "fs/promises";
|
|
1380
|
+
import { homedir as homedir14 } from "os";
|
|
1381
|
+
import { join as join14 } from "path";
|
|
1382
|
+
import { glob as glob9 } from "tinyglobby";
|
|
1197
1383
|
var OPENCLAW_DIRS = [".openclaw", ".clawdbot", ".moltbot", ".moldbot"];
|
|
1198
1384
|
function openclawRoots(home) {
|
|
1199
1385
|
const env = process.env.OPENCLAW_DIR;
|
|
1200
1386
|
if (env) return env.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1201
|
-
const h = home ??
|
|
1202
|
-
return OPENCLAW_DIRS.map((d) =>
|
|
1387
|
+
const h = home ?? homedir14();
|
|
1388
|
+
return OPENCLAW_DIRS.map((d) => join14(h, d));
|
|
1203
1389
|
}
|
|
1204
1390
|
function modelFrom(obj) {
|
|
1205
1391
|
return str(obj, "modelId") ?? str(obj, "model");
|
|
@@ -1230,7 +1416,7 @@ async function parseFile5(file) {
|
|
|
1230
1416
|
async function loadOpenclawProjects(home, onProgress) {
|
|
1231
1417
|
const files = [];
|
|
1232
1418
|
for (const root of openclawRoots(home)) {
|
|
1233
|
-
files.push(...await
|
|
1419
|
+
files.push(...await glob9(["**/*.jsonl", "**/*.jsonl.*"], { cwd: root, absolute: true }).catch(() => []));
|
|
1234
1420
|
}
|
|
1235
1421
|
const sessions = [];
|
|
1236
1422
|
let parsed = 0;
|
|
@@ -1249,7 +1435,7 @@ var openclawAdapter = {
|
|
|
1249
1435
|
async detect(home) {
|
|
1250
1436
|
for (const root of openclawRoots(home)) {
|
|
1251
1437
|
try {
|
|
1252
|
-
await
|
|
1438
|
+
await access13(root);
|
|
1253
1439
|
return true;
|
|
1254
1440
|
} catch {
|
|
1255
1441
|
}
|
|
@@ -1260,10 +1446,10 @@ var openclawAdapter = {
|
|
|
1260
1446
|
};
|
|
1261
1447
|
|
|
1262
1448
|
// src/agents/opencode.ts
|
|
1263
|
-
import { access as
|
|
1264
|
-
import { homedir as
|
|
1265
|
-
import { join as
|
|
1266
|
-
import { glob as
|
|
1449
|
+
import { access as access14, readFile as readFile5 } from "fs/promises";
|
|
1450
|
+
import { homedir as homedir15 } from "os";
|
|
1451
|
+
import { join as join15 } from "path";
|
|
1452
|
+
import { glob as glob10 } from "tinyglobby";
|
|
1267
1453
|
import { z as z2 } from "zod";
|
|
1268
1454
|
var Message = z2.object({
|
|
1269
1455
|
sessionID: z2.string(),
|
|
@@ -1286,8 +1472,8 @@ var Session = z2.object({
|
|
|
1286
1472
|
});
|
|
1287
1473
|
var Project = z2.object({ id: z2.string(), worktree: z2.string().optional() });
|
|
1288
1474
|
function opencodeRoot(home) {
|
|
1289
|
-
if (home) return
|
|
1290
|
-
return process.env.OPENCODE_DATA_DIR ??
|
|
1475
|
+
if (home) return join15(home, ".local", "share", "opencode");
|
|
1476
|
+
return process.env.OPENCODE_DATA_DIR ?? join15(homedir15(), ".local", "share", "opencode");
|
|
1291
1477
|
}
|
|
1292
1478
|
async function readJson2(file) {
|
|
1293
1479
|
try {
|
|
@@ -1298,18 +1484,18 @@ async function readJson2(file) {
|
|
|
1298
1484
|
}
|
|
1299
1485
|
async function loadOpencodeProjects(home, onProgress) {
|
|
1300
1486
|
const root = opencodeRoot(home);
|
|
1301
|
-
const storage =
|
|
1302
|
-
const messageFiles = await
|
|
1487
|
+
const storage = join15(root, "storage");
|
|
1488
|
+
const messageFiles = await glob10(["message/*/*.json"], { cwd: storage, absolute: true }).catch(
|
|
1303
1489
|
() => []
|
|
1304
1490
|
);
|
|
1305
1491
|
if (messageFiles.length === 0) return [];
|
|
1306
1492
|
const sessionDir = /* @__PURE__ */ new Map();
|
|
1307
1493
|
const projectWorktree = /* @__PURE__ */ new Map();
|
|
1308
|
-
for (const f of await
|
|
1494
|
+
for (const f of await glob10(["project/*.json"], { cwd: storage, absolute: true }).catch(() => [])) {
|
|
1309
1495
|
const p = Project.safeParse(await readJson2(f));
|
|
1310
1496
|
if (p.success && p.data.worktree) projectWorktree.set(p.data.id, p.data.worktree);
|
|
1311
1497
|
}
|
|
1312
|
-
const sessionFiles = await
|
|
1498
|
+
const sessionFiles = await glob10(["session/**/*.json"], { cwd: storage, absolute: true }).catch(
|
|
1313
1499
|
() => []
|
|
1314
1500
|
);
|
|
1315
1501
|
for (const f of sessionFiles) {
|
|
@@ -1363,7 +1549,7 @@ var opencodeAdapter = {
|
|
|
1363
1549
|
supported: true,
|
|
1364
1550
|
async detect(home) {
|
|
1365
1551
|
try {
|
|
1366
|
-
await
|
|
1552
|
+
await access14(join15(opencodeRoot(home), "storage", "message"));
|
|
1367
1553
|
return true;
|
|
1368
1554
|
} catch {
|
|
1369
1555
|
return false;
|
|
@@ -1373,12 +1559,12 @@ var opencodeAdapter = {
|
|
|
1373
1559
|
};
|
|
1374
1560
|
|
|
1375
1561
|
// src/agents/pi.ts
|
|
1376
|
-
import { access as
|
|
1377
|
-
import { homedir as
|
|
1378
|
-
import { join as
|
|
1379
|
-
import { glob as
|
|
1562
|
+
import { access as access15 } from "fs/promises";
|
|
1563
|
+
import { homedir as homedir16 } from "os";
|
|
1564
|
+
import { join as join16 } from "path";
|
|
1565
|
+
import { glob as glob11 } from "tinyglobby";
|
|
1380
1566
|
function piRoot(home) {
|
|
1381
|
-
return process.env.PI_AGENT_DIR ??
|
|
1567
|
+
return process.env.PI_AGENT_DIR ?? join16(home ?? homedir16(), ".pi", "agent", "sessions");
|
|
1382
1568
|
}
|
|
1383
1569
|
async function parseFile6(file) {
|
|
1384
1570
|
const records = [];
|
|
@@ -1398,7 +1584,7 @@ async function parseFile6(file) {
|
|
|
1398
1584
|
return sessionFromRecords(file, void 0, records);
|
|
1399
1585
|
}
|
|
1400
1586
|
async function loadPiProjects(home, onProgress) {
|
|
1401
|
-
const files = await
|
|
1587
|
+
const files = await glob11(["**/*.jsonl"], { cwd: piRoot(home), absolute: true }).catch(() => []);
|
|
1402
1588
|
const sessions = [];
|
|
1403
1589
|
let parsed = 0;
|
|
1404
1590
|
for (const f of files) {
|
|
@@ -1415,7 +1601,7 @@ var piAdapter = {
|
|
|
1415
1601
|
supported: true,
|
|
1416
1602
|
async detect(home) {
|
|
1417
1603
|
try {
|
|
1418
|
-
await
|
|
1604
|
+
await access15(piRoot(home));
|
|
1419
1605
|
return true;
|
|
1420
1606
|
} catch {
|
|
1421
1607
|
return false;
|
|
@@ -1425,12 +1611,12 @@ var piAdapter = {
|
|
|
1425
1611
|
};
|
|
1426
1612
|
|
|
1427
1613
|
// src/agents/qwen.ts
|
|
1428
|
-
import { access as
|
|
1429
|
-
import { homedir as
|
|
1430
|
-
import { join as
|
|
1431
|
-
import { glob as
|
|
1614
|
+
import { access as access16 } from "fs/promises";
|
|
1615
|
+
import { homedir as homedir17 } from "os";
|
|
1616
|
+
import { join as join17 } from "path";
|
|
1617
|
+
import { glob as glob12 } from "tinyglobby";
|
|
1432
1618
|
function qwenRoot(home) {
|
|
1433
|
-
return process.env.QWEN_DATA_DIR ??
|
|
1619
|
+
return process.env.QWEN_DATA_DIR ?? join17(home ?? homedir17(), ".qwen");
|
|
1434
1620
|
}
|
|
1435
1621
|
async function parseFile7(file) {
|
|
1436
1622
|
const records = [];
|
|
@@ -1452,7 +1638,7 @@ async function parseFile7(file) {
|
|
|
1452
1638
|
return sessionFromRecords(file, void 0, records);
|
|
1453
1639
|
}
|
|
1454
1640
|
async function loadQwenProjects(home, onProgress) {
|
|
1455
|
-
const files = await
|
|
1641
|
+
const files = await glob12(["projects/**/*.jsonl"], { cwd: qwenRoot(home), absolute: true }).catch(
|
|
1456
1642
|
() => []
|
|
1457
1643
|
);
|
|
1458
1644
|
const sessions = [];
|
|
@@ -1471,7 +1657,7 @@ var qwenAdapter = {
|
|
|
1471
1657
|
supported: true,
|
|
1472
1658
|
async detect(home) {
|
|
1473
1659
|
try {
|
|
1474
|
-
await
|
|
1660
|
+
await access16(join17(qwenRoot(home), "projects"));
|
|
1475
1661
|
return true;
|
|
1476
1662
|
} catch {
|
|
1477
1663
|
return false;
|
|
@@ -1487,10 +1673,10 @@ function detectOnly(id, name, reason, ...pathCandidates) {
|
|
|
1487
1673
|
name,
|
|
1488
1674
|
supported: false,
|
|
1489
1675
|
unsupportedReason: reason,
|
|
1490
|
-
async detect(home =
|
|
1676
|
+
async detect(home = homedir18()) {
|
|
1491
1677
|
for (const parts of pathCandidates) {
|
|
1492
1678
|
try {
|
|
1493
|
-
await
|
|
1679
|
+
await access17(join18(home, ...parts));
|
|
1494
1680
|
return true;
|
|
1495
1681
|
} catch {
|
|
1496
1682
|
}
|
|
@@ -1513,26 +1699,25 @@ var ADAPTERS = [
|
|
|
1513
1699
|
kiloAdapter,
|
|
1514
1700
|
gooseAdapter,
|
|
1515
1701
|
hermesAdapter,
|
|
1702
|
+
copilotAdapter,
|
|
1703
|
+
ampAdapter,
|
|
1516
1704
|
piAdapter,
|
|
1517
1705
|
antigravityAdapter,
|
|
1518
|
-
//
|
|
1519
|
-
//
|
|
1520
|
-
|
|
1521
|
-
// which is out of scope for an offline tool.
|
|
1522
|
-
detectOnly("copilot", "GitHub Copilot CLI", "usage is OpenTelemetry spans \u2014 parsing not wired yet", [".copilot", "otel"]),
|
|
1523
|
-
detectOnly("amp", "Amp", "usage-ledger thread format \u2014 parsing not wired yet", [".local", "share", "amp"])
|
|
1706
|
+
// Cursor keeps no token counts on disk (usage is server-side, reachable only
|
|
1707
|
+
// with auth), so there's nothing to parse offline — surfaced with the reason.
|
|
1708
|
+
detectOnly("cursor", "Cursor", "no token counts stored locally \u2014 usage is server-side", [".cursor"])
|
|
1524
1709
|
];
|
|
1525
1710
|
async function loadAllAgents(home, onProgress) {
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1711
|
+
return Promise.all(
|
|
1712
|
+
ADAPTERS.map(async (adapter) => {
|
|
1713
|
+
const detected = await adapter.detect(home).catch(() => false);
|
|
1714
|
+
let projects = [];
|
|
1715
|
+
if (detected && adapter.supported) {
|
|
1716
|
+
projects = await adapter.loadProjects(home, (p) => onProgress?.(adapter.name, p)).catch(() => []);
|
|
1717
|
+
}
|
|
1718
|
+
return { adapter, detected, projects };
|
|
1719
|
+
})
|
|
1720
|
+
);
|
|
1536
1721
|
}
|
|
1537
1722
|
|
|
1538
1723
|
// src/ui/App.tsx
|
|
@@ -2377,10 +2562,23 @@ function App({
|
|
|
2377
2562
|
initialSelected = 0
|
|
2378
2563
|
}) {
|
|
2379
2564
|
const { exit } = useApp();
|
|
2380
|
-
const
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2565
|
+
const rawAgents = useMemo2(() => agents ?? wrapProjects(projects ?? []), [agents, projects]);
|
|
2566
|
+
const agentList = useMemo2(() => {
|
|
2567
|
+
const withData = rawAgents.filter((a) => a.projects.length > 0);
|
|
2568
|
+
if (withData.length <= 1) return rawAgents;
|
|
2569
|
+
const combined = {
|
|
2570
|
+
adapter: {
|
|
2571
|
+
id: "__all_agents__",
|
|
2572
|
+
name: "All agents",
|
|
2573
|
+
supported: true,
|
|
2574
|
+
detect: async () => true,
|
|
2575
|
+
loadProjects: async () => []
|
|
2576
|
+
},
|
|
2577
|
+
detected: true,
|
|
2578
|
+
projects: withData.flatMap((a) => a.projects)
|
|
2579
|
+
};
|
|
2580
|
+
return [combined, ...rawAgents];
|
|
2581
|
+
}, [rawAgents]);
|
|
2384
2582
|
const multiAgent = agentList.length > 1;
|
|
2385
2583
|
const firstWithData = Math.max(0, agentList.findIndex((a) => a.projects.length > 0));
|
|
2386
2584
|
const [agentIdx, setAgentIdx] = useState5(firstWithData);
|
|
@@ -335,8 +335,7 @@ async function findTranscripts(projectPath, home = homedir()) {
|
|
|
335
335
|
}
|
|
336
336
|
|
|
337
337
|
// src/transcript.ts
|
|
338
|
-
import {
|
|
339
|
-
import { createInterface } from "readline";
|
|
338
|
+
import { readFile } from "fs/promises";
|
|
340
339
|
import { z } from "zod";
|
|
341
340
|
var AssistantLine = z.object({
|
|
342
341
|
type: z.literal("assistant"),
|
|
@@ -359,10 +358,18 @@ var AssistantLine = z.object({
|
|
|
359
358
|
})
|
|
360
359
|
});
|
|
361
360
|
async function parseTranscript(file, seenMessages = /* @__PURE__ */ new Map(), seenToolIds = /* @__PURE__ */ new Set(), events) {
|
|
361
|
+
let content;
|
|
362
|
+
try {
|
|
363
|
+
content = await readFile(file, "utf8");
|
|
364
|
+
} catch {
|
|
365
|
+
content = "";
|
|
366
|
+
}
|
|
367
|
+
return parseTranscriptContent(content, file, seenMessages, seenToolIds, events);
|
|
368
|
+
}
|
|
369
|
+
function parseTranscriptContent(content, file, seenMessages = /* @__PURE__ */ new Map(), seenToolIds = /* @__PURE__ */ new Set(), events) {
|
|
362
370
|
const stats = { file, usageByModel: {}, toolCalls: {}, toolCostUsd: {}, dailyUsage: {} };
|
|
363
|
-
const
|
|
364
|
-
|
|
365
|
-
if (!line.trim()) continue;
|
|
371
|
+
for (const line of content.split("\n")) {
|
|
372
|
+
if (line.length < 2 || !line.includes("assistant")) continue;
|
|
366
373
|
let raw;
|
|
367
374
|
try {
|
|
368
375
|
raw = JSON.parse(line);
|
|
@@ -467,5 +474,6 @@ export {
|
|
|
467
474
|
parseDateArg,
|
|
468
475
|
sanitizeProjectPath,
|
|
469
476
|
findTranscripts,
|
|
470
|
-
parseTranscript
|
|
477
|
+
parseTranscript,
|
|
478
|
+
parseTranscriptContent
|
|
471
479
|
};
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
buildBlocks,
|
|
4
4
|
renderBlocksReport
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-VJFLJJGX.js";
|
|
6
6
|
import {
|
|
7
7
|
findMcpServers
|
|
8
8
|
} from "./chunk-65BQE6TI.js";
|
|
@@ -17,16 +17,39 @@ import {
|
|
|
17
17
|
setTimezone,
|
|
18
18
|
tok,
|
|
19
19
|
usd
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-KO5GQL76.js";
|
|
21
21
|
|
|
22
22
|
// src/cli.ts
|
|
23
23
|
import { createRequire } from "module";
|
|
24
24
|
import { Command } from "commander";
|
|
25
25
|
|
|
26
|
-
// src/
|
|
27
|
-
import {
|
|
26
|
+
// src/config.ts
|
|
27
|
+
import { readFileSync } from "fs";
|
|
28
28
|
import { homedir } from "os";
|
|
29
29
|
import { join } from "path";
|
|
30
|
+
var COST_SOURCES = /* @__PURE__ */ new Set(["auto", "cc", "calc", "both"]);
|
|
31
|
+
function loadConfig(home = homedir()) {
|
|
32
|
+
let raw;
|
|
33
|
+
try {
|
|
34
|
+
raw = JSON.parse(readFileSync(join(home, ".tokz", "config.json"), "utf8"));
|
|
35
|
+
} catch {
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
if (!raw || typeof raw !== "object") return {};
|
|
39
|
+
const r = raw;
|
|
40
|
+
const cfg = {};
|
|
41
|
+
if (typeof r.timezone === "string") cfg.timezone = r.timezone;
|
|
42
|
+
if (typeof r.offline === "boolean") cfg.offline = r.offline;
|
|
43
|
+
if (typeof r.costSource === "string" && COST_SOURCES.has(r.costSource))
|
|
44
|
+
cfg.costSource = r.costSource;
|
|
45
|
+
if (typeof r.days === "number" && Number.isFinite(r.days) && r.days > 0) cfg.days = Math.trunc(r.days);
|
|
46
|
+
return cfg;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// src/livePricing.ts
|
|
50
|
+
import { mkdir, readFile, writeFile } from "fs/promises";
|
|
51
|
+
import { homedir as homedir2 } from "os";
|
|
52
|
+
import { join as join2 } from "path";
|
|
30
53
|
var LITELLM_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json";
|
|
31
54
|
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
32
55
|
var FETCH_TIMEOUT_MS = 3500;
|
|
@@ -54,8 +77,8 @@ function mapLitellmPrices(raw) {
|
|
|
54
77
|
return out;
|
|
55
78
|
}
|
|
56
79
|
async function initPricing(opts) {
|
|
57
|
-
const dir = opts?.cacheDir ??
|
|
58
|
-
const file =
|
|
80
|
+
const dir = opts?.cacheDir ?? join2(homedir2(), ".tokz");
|
|
81
|
+
const file = join2(dir, "litellm-prices.json");
|
|
59
82
|
let cached;
|
|
60
83
|
try {
|
|
61
84
|
cached = JSON.parse(await readFile(file, "utf8"));
|
|
@@ -149,10 +172,11 @@ function renderReport(report) {
|
|
|
149
172
|
var { version } = createRequire(import.meta.url)("../package.json");
|
|
150
173
|
var program = new Command();
|
|
151
174
|
program.name("tokz").description("Audit where your coding agent's context window and API dollars go.").option("--offline", "don't fetch live pricing; use cached/built-in rates").option("--timezone <zone>", 'group days in this timezone: "utc" (default), "local", or an IANA name').version(version);
|
|
175
|
+
var config = loadConfig();
|
|
152
176
|
function applyGlobals() {
|
|
153
177
|
const opts = program.opts();
|
|
154
|
-
setTimezone(opts.timezone);
|
|
155
|
-
return { offline: opts.offline };
|
|
178
|
+
setTimezone(opts.timezone ?? config.timezone);
|
|
179
|
+
return { offline: opts.offline ?? config.offline };
|
|
156
180
|
}
|
|
157
181
|
async function parseAll(projectPath, events) {
|
|
158
182
|
const transcripts = await findTranscripts(projectPath);
|
|
@@ -176,7 +200,7 @@ program.command("audit").argument("[project]", "project path (default: current d
|
|
|
176
200
|
return;
|
|
177
201
|
}
|
|
178
202
|
const servers = opts.all ? [] : await findMcpServers(projectPath);
|
|
179
|
-
const days = opts.days ? Number.parseInt(opts.days, 10) :
|
|
203
|
+
const days = opts.days ? Number.parseInt(opts.days, 10) : config.days;
|
|
180
204
|
const isoDay = (offset) => new Date(Date.now() - offset * 864e5).toISOString().slice(0, 10);
|
|
181
205
|
const since = parseDateArg(opts.since);
|
|
182
206
|
const until = parseDateArg(opts.until);
|
|
@@ -210,7 +234,7 @@ program.command("blocks").description("Claude usage grouped into rolling 5-hour
|
|
|
210
234
|
});
|
|
211
235
|
program.command("statusline").description("compact usage line for Claude Code's statusLine hook (reads hook JSON on stdin)").argument("[action]", '"enable" writes the hook into ~/.claude/settings.json, "disable" removes it').option("--cost-source <mode>", "session cost source: auto | cc | calc | both", "auto").action(async (action, opts) => {
|
|
212
236
|
const globals = applyGlobals();
|
|
213
|
-
const sl = await import("./statusline-
|
|
237
|
+
const sl = await import("./statusline-5SJKELOG.js");
|
|
214
238
|
if (action === "enable" || action === "disable") {
|
|
215
239
|
try {
|
|
216
240
|
console.log(action === "enable" ? await sl.enableStatusline() : await sl.disableStatusline());
|
|
@@ -232,7 +256,8 @@ program.command("statusline").description("compact usage line for Claude Code's
|
|
|
232
256
|
} catch {
|
|
233
257
|
}
|
|
234
258
|
const valid = ["auto", "cc", "calc", "both"];
|
|
235
|
-
const
|
|
259
|
+
const requested = opts.costSource ?? config.costSource ?? "auto";
|
|
260
|
+
const costSource = valid.includes(requested) ? requested : "auto";
|
|
236
261
|
console.log(await sl.statusline(input, Date.now(), void 0, { costSource }));
|
|
237
262
|
});
|
|
238
263
|
program.action(async () => {
|
|
@@ -251,7 +276,7 @@ program.action(async () => {
|
|
|
251
276
|
const [{ render }, React, { Root }, { Fullscreen }] = await Promise.all([
|
|
252
277
|
import("ink"),
|
|
253
278
|
import("react"),
|
|
254
|
-
import("./Root-
|
|
279
|
+
import("./Root-7T6ZYTLC.js"),
|
|
255
280
|
import("./Fullscreen-GK2ZYXHV.js")
|
|
256
281
|
]);
|
|
257
282
|
render(React.createElement(Fullscreen, null, React.createElement(Root)));
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
buildBlocks,
|
|
4
4
|
burnRate,
|
|
5
5
|
fmtMs
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-VJFLJJGX.js";
|
|
7
7
|
import {
|
|
8
8
|
compact,
|
|
9
9
|
costUsd,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
parseTranscript,
|
|
13
13
|
shortModel,
|
|
14
14
|
usd
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-KO5GQL76.js";
|
|
16
16
|
|
|
17
17
|
// src/statusline.ts
|
|
18
18
|
import { mkdir, readFile, stat, writeFile } from "fs/promises";
|