@tokz/cli 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +124 -98
- package/dist/{Root-WEIYEE6M.js → Root-TUEV5MJB.js} +983 -121
- package/dist/cli.js +6 -4
- package/dist/{statusline-A36XPBG6.js → statusline-EXFMQYFF.js} +37 -16
- package/package.json +1 -1
|
@@ -27,9 +27,9 @@ 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 access15 } from "fs/promises";
|
|
31
|
+
import { homedir as homedir16 } from "os";
|
|
32
|
+
import { join as join16 } from "path";
|
|
33
33
|
|
|
34
34
|
// src/agents/antigravity.ts
|
|
35
35
|
import { access, readFile as readFile2, readdir } from "fs/promises";
|
|
@@ -51,6 +51,24 @@ function baseName(p) {
|
|
|
51
51
|
const parts = p.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
52
52
|
return parts.at(-1) ?? p;
|
|
53
53
|
}
|
|
54
|
+
var UNKNOWN_PROJECT = "(unknown project)";
|
|
55
|
+
function groupSessionsByCwd(agentId, sessions) {
|
|
56
|
+
const byCwd = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const s of sessions) {
|
|
58
|
+
if (Object.keys(s.usageByModel).length === 0) continue;
|
|
59
|
+
const key = s.cwd ?? UNKNOWN_PROJECT;
|
|
60
|
+
(byCwd.get(key) ?? byCwd.set(key, []).get(key)).push(s);
|
|
61
|
+
}
|
|
62
|
+
return [...byCwd].map(([cwd, group]) => ({
|
|
63
|
+
id: `${agentId}:${cwd}`,
|
|
64
|
+
name: cwd,
|
|
65
|
+
label: cwd === UNKNOWN_PROJECT ? cwd : baseName(cwd),
|
|
66
|
+
realPath: cwd === UNKNOWN_PROJECT ? void 0 : cwd,
|
|
67
|
+
report: buildReport(group, []),
|
|
68
|
+
sessions: group,
|
|
69
|
+
serverList: []
|
|
70
|
+
})).sort((a, b) => b.report.totalCostUsd - a.report.totalCostUsd);
|
|
71
|
+
}
|
|
54
72
|
var DAY_MS = 864e5;
|
|
55
73
|
function aggregate(projects) {
|
|
56
74
|
const usageByModel = {};
|
|
@@ -305,27 +323,7 @@ async function loadAntigravityProjects(home, onProgress) {
|
|
|
305
323
|
}
|
|
306
324
|
sessions.push(stats);
|
|
307
325
|
}
|
|
308
|
-
|
|
309
|
-
for (const s of sessions) {
|
|
310
|
-
const key = s.cwd ?? "(unknown project)";
|
|
311
|
-
const list = byDir.get(key) ?? [];
|
|
312
|
-
list.push(s);
|
|
313
|
-
byDir.set(key, list);
|
|
314
|
-
}
|
|
315
|
-
const out = [];
|
|
316
|
-
for (const [dir, list] of byDir) {
|
|
317
|
-
out.push({
|
|
318
|
-
id: `antigravity:${dir}`,
|
|
319
|
-
name: dir,
|
|
320
|
-
label: dir === "(unknown project)" ? dir : baseName(dir),
|
|
321
|
-
realPath: dir,
|
|
322
|
-
report: buildReport(list, []),
|
|
323
|
-
sessions: list,
|
|
324
|
-
serverList: []
|
|
325
|
-
});
|
|
326
|
-
}
|
|
327
|
-
out.sort((a, b) => b.report.totalCostUsd - a.report.totalCostUsd);
|
|
328
|
-
return out;
|
|
326
|
+
return groupSessionsByCwd("antigravity", sessions);
|
|
329
327
|
}
|
|
330
328
|
var antigravityAdapter = {
|
|
331
329
|
id: "antigravity",
|
|
@@ -368,13 +366,178 @@ var claudeAdapter = {
|
|
|
368
366
|
loadProjects: (home, onProgress) => loadProjects(home, onProgress)
|
|
369
367
|
};
|
|
370
368
|
|
|
371
|
-
// src/agents/
|
|
372
|
-
import {
|
|
373
|
-
import { access as access3 } from "fs/promises";
|
|
374
|
-
import { createInterface } from "readline";
|
|
369
|
+
// src/agents/codebuff.ts
|
|
370
|
+
import { access as access3, stat } from "fs/promises";
|
|
375
371
|
import { homedir as homedir4 } from "os";
|
|
376
372
|
import { join as join4 } from "path";
|
|
377
373
|
import { glob as glob2 } from "tinyglobby";
|
|
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
|
|
464
|
+
var CHANNELS = ["manicode", "manicode-dev", "manicode-staging"];
|
|
465
|
+
function codebuffRoots(home) {
|
|
466
|
+
const env = process.env.CODEBUFF_DATA_DIR;
|
|
467
|
+
if (env) return env.split(",").map((s) => s.trim()).filter(Boolean);
|
|
468
|
+
const h = home ?? homedir4();
|
|
469
|
+
return CHANNELS.map((c) => join4(h, ".config", c));
|
|
470
|
+
}
|
|
471
|
+
function isAssistant(msg) {
|
|
472
|
+
const role = str(msg, "variant") ?? str(msg, "role");
|
|
473
|
+
return role === "ai" || role === "agent" || role === "assistant";
|
|
474
|
+
}
|
|
475
|
+
async function parseFile(file) {
|
|
476
|
+
const arr = await readJson(file);
|
|
477
|
+
if (!Array.isArray(arr)) return sessionFromRecords(file, void 0, []);
|
|
478
|
+
let fileTs;
|
|
479
|
+
try {
|
|
480
|
+
fileTs = (await stat(file)).mtime.toISOString();
|
|
481
|
+
} catch {
|
|
482
|
+
}
|
|
483
|
+
const records = [];
|
|
484
|
+
for (const msg of arr) {
|
|
485
|
+
if (!isAssistant(msg)) continue;
|
|
486
|
+
const meta = msg.metadata;
|
|
487
|
+
const usage = meta?.usage;
|
|
488
|
+
if (!usage) continue;
|
|
489
|
+
records.push({
|
|
490
|
+
model: str(meta, "model") ?? "codebuff-unknown",
|
|
491
|
+
ts: str(msg, "timestamp") ?? fileTs,
|
|
492
|
+
input: pickNum(usage, ["inputTokens", "input_tokens"]),
|
|
493
|
+
output: pickNum(usage, ["outputTokens", "output_tokens"]),
|
|
494
|
+
cacheRead: pickNum(usage, ["cacheReadInputTokens", "cache_read_input_tokens", "cachedTokens"]),
|
|
495
|
+
cacheWrite: pickNum(usage, ["cacheCreationInputTokens", "cache_creation_input_tokens"])
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
return sessionFromRecords(file, void 0, records);
|
|
499
|
+
}
|
|
500
|
+
async function loadCodebuffProjects(home, onProgress) {
|
|
501
|
+
const files = [];
|
|
502
|
+
for (const root of codebuffRoots(home)) {
|
|
503
|
+
files.push(
|
|
504
|
+
...await glob2(["**/chat-messages.json"], { cwd: root, absolute: true }).catch(() => [])
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
const sessions = [];
|
|
508
|
+
let parsed = 0;
|
|
509
|
+
for (const f of files) {
|
|
510
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Codebuff chats" });
|
|
511
|
+
sessions.push(await parseFile(f));
|
|
512
|
+
parsed += 1;
|
|
513
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Codebuff chats" });
|
|
514
|
+
}
|
|
515
|
+
return groupSessionsByCwd("codebuff", sessions);
|
|
516
|
+
}
|
|
517
|
+
var codebuffAdapter = {
|
|
518
|
+
id: "codebuff",
|
|
519
|
+
name: "Codebuff",
|
|
520
|
+
supported: true,
|
|
521
|
+
async detect(home) {
|
|
522
|
+
for (const root of codebuffRoots(home)) {
|
|
523
|
+
try {
|
|
524
|
+
await access3(root);
|
|
525
|
+
return true;
|
|
526
|
+
} catch {
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
return false;
|
|
530
|
+
},
|
|
531
|
+
loadProjects: loadCodebuffProjects
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// src/agents/codex.ts
|
|
535
|
+
import { createReadStream } from "fs";
|
|
536
|
+
import { access as access4 } from "fs/promises";
|
|
537
|
+
import { createInterface } from "readline";
|
|
538
|
+
import { homedir as homedir5 } from "os";
|
|
539
|
+
import { join as join5 } from "path";
|
|
540
|
+
import { glob as glob3 } from "tinyglobby";
|
|
378
541
|
import { z } from "zod";
|
|
379
542
|
var TokenTotals = z.object({
|
|
380
543
|
input_tokens: z.number().catch(0).default(0),
|
|
@@ -397,8 +560,8 @@ var Line = z.object({
|
|
|
397
560
|
}).passthrough().optional()
|
|
398
561
|
});
|
|
399
562
|
function codexHome(home) {
|
|
400
|
-
if (home) return
|
|
401
|
-
return process.env.CODEX_HOME ??
|
|
563
|
+
if (home) return join5(home, ".codex");
|
|
564
|
+
return process.env.CODEX_HOME ?? join5(homedir5(), ".codex");
|
|
402
565
|
}
|
|
403
566
|
var DEFAULT_MODEL = "gpt-5";
|
|
404
567
|
async function parseCodexRollout(file) {
|
|
@@ -417,19 +580,19 @@ async function parseCodexRollout(file) {
|
|
|
417
580
|
}
|
|
418
581
|
const parsed = Line.safeParse(obj);
|
|
419
582
|
if (!parsed.success) continue;
|
|
420
|
-
const { timestamp, type, payload } = parsed.data;
|
|
421
|
-
if (!stats.cwd) stats.cwd =
|
|
422
|
-
if (
|
|
583
|
+
const { timestamp, type, payload: payload2 } = parsed.data;
|
|
584
|
+
if (!stats.cwd) stats.cwd = payload2?.cwd ?? parsed.data.cwd;
|
|
585
|
+
if (payload2?.model) model = payload2.model;
|
|
423
586
|
if (timestamp) {
|
|
424
587
|
if (!stats.firstTs) stats.firstTs = timestamp;
|
|
425
588
|
stats.lastTs = timestamp;
|
|
426
589
|
}
|
|
427
|
-
const toolName =
|
|
590
|
+
const toolName = payload2?.type === "function_call" || payload2?.type === "custom_tool_call" ? payload2.name : payload2?.type === "local_shell_call" ? "shell" : type === "function_call" ? parsed.data.name : void 0;
|
|
428
591
|
if (toolName) {
|
|
429
592
|
stats.toolCalls[toolName] = (stats.toolCalls[toolName] ?? 0) + 1;
|
|
430
593
|
turnTools.push(toolName);
|
|
431
594
|
}
|
|
432
|
-
const totals =
|
|
595
|
+
const totals = payload2?.type === "token_count" ? payload2.info?.total_token_usage : void 0;
|
|
433
596
|
if (!totals) continue;
|
|
434
597
|
const cur = {
|
|
435
598
|
input: totals.input_tokens,
|
|
@@ -475,38 +638,20 @@ async function parseCodexRollout(file) {
|
|
|
475
638
|
}
|
|
476
639
|
async function loadCodexProjects(home, onProgress) {
|
|
477
640
|
const root = codexHome(home);
|
|
478
|
-
const files = await
|
|
641
|
+
const files = await glob3(["sessions/**/*.jsonl", "archived_sessions/**/*.jsonl"], {
|
|
479
642
|
cwd: root,
|
|
480
643
|
absolute: true
|
|
481
644
|
}).catch(() => []);
|
|
482
645
|
if (files.length === 0) return [];
|
|
483
|
-
const
|
|
646
|
+
const sessions = [];
|
|
484
647
|
let parsed = 0;
|
|
485
648
|
for (const f of files) {
|
|
486
649
|
onProgress?.({ parsed, total: files.length, currentProject: "Codex sessions" });
|
|
487
|
-
|
|
650
|
+
sessions.push(await parseCodexRollout(f));
|
|
488
651
|
parsed += 1;
|
|
489
652
|
onProgress?.({ parsed, total: files.length, currentProject: "Codex sessions" });
|
|
490
|
-
if (Object.keys(s.usageByModel).length === 0) continue;
|
|
491
|
-
const key = s.cwd ?? "(unknown project)";
|
|
492
|
-
const list = byCwd.get(key) ?? [];
|
|
493
|
-
list.push(s);
|
|
494
|
-
byCwd.set(key, list);
|
|
495
653
|
}
|
|
496
|
-
|
|
497
|
-
for (const [cwd, sessions] of byCwd) {
|
|
498
|
-
out.push({
|
|
499
|
-
id: `codex:${cwd}`,
|
|
500
|
-
name: cwd,
|
|
501
|
-
label: cwd === "(unknown project)" ? cwd : baseName(cwd),
|
|
502
|
-
realPath: cwd,
|
|
503
|
-
report: buildReport(sessions, []),
|
|
504
|
-
sessions,
|
|
505
|
-
serverList: []
|
|
506
|
-
});
|
|
507
|
-
}
|
|
508
|
-
out.sort((a, b) => b.report.totalCostUsd - a.report.totalCostUsd);
|
|
509
|
-
return out;
|
|
654
|
+
return groupSessionsByCwd("codex", sessions);
|
|
510
655
|
}
|
|
511
656
|
var codexAdapter = {
|
|
512
657
|
id: "codex",
|
|
@@ -514,7 +659,7 @@ var codexAdapter = {
|
|
|
514
659
|
supported: true,
|
|
515
660
|
async detect(home) {
|
|
516
661
|
try {
|
|
517
|
-
await
|
|
662
|
+
await access4(join5(codexHome(home), "sessions"));
|
|
518
663
|
return true;
|
|
519
664
|
} catch {
|
|
520
665
|
return false;
|
|
@@ -523,11 +668,602 @@ var codexAdapter = {
|
|
|
523
668
|
loadProjects: loadCodexProjects
|
|
524
669
|
};
|
|
525
670
|
|
|
671
|
+
// src/agents/droid.ts
|
|
672
|
+
import { access as access5, stat as stat2 } from "fs/promises";
|
|
673
|
+
import { homedir as homedir6 } from "os";
|
|
674
|
+
import { basename as basename2, join as join6 } from "path";
|
|
675
|
+
import { glob as glob4 } from "tinyglobby";
|
|
676
|
+
function droidRoot(home) {
|
|
677
|
+
return process.env.DROID_SESSIONS_DIR ?? join6(home ?? homedir6(), ".factory", "sessions");
|
|
678
|
+
}
|
|
679
|
+
async function parseFile2(file) {
|
|
680
|
+
const settings = await readJson(file);
|
|
681
|
+
const usage = settings?.tokenUsage;
|
|
682
|
+
if (!usage) return sessionFromRecords(file, void 0, []);
|
|
683
|
+
let ts = str(settings, "updatedAt") ?? str(settings, "createdAt");
|
|
684
|
+
if (!ts) {
|
|
685
|
+
try {
|
|
686
|
+
ts = (await stat2(file)).mtime.toISOString();
|
|
687
|
+
} catch {
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
return sessionFromRecords(file, void 0, [
|
|
691
|
+
{
|
|
692
|
+
model: str(settings, "model") ?? "droid-unknown",
|
|
693
|
+
ts,
|
|
694
|
+
input: pickNum(usage, ["inputTokens"]),
|
|
695
|
+
output: pickNum(usage, ["outputTokens"]) + pickNum(usage, ["thinkingTokens"]),
|
|
696
|
+
cacheRead: pickNum(usage, ["cacheReadTokens"]),
|
|
697
|
+
cacheWrite: pickNum(usage, ["cacheCreationTokens"])
|
|
698
|
+
}
|
|
699
|
+
]);
|
|
700
|
+
}
|
|
701
|
+
async function loadDroidProjects(home, onProgress) {
|
|
702
|
+
const files = (await glob4(["**/*.settings.json"], { cwd: droidRoot(home), absolute: true }).catch(() => [])).filter((f) => basename2(f).endsWith(".settings.json"));
|
|
703
|
+
const sessions = [];
|
|
704
|
+
let parsed = 0;
|
|
705
|
+
for (const f of files) {
|
|
706
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Droid sessions" });
|
|
707
|
+
sessions.push(await parseFile2(f));
|
|
708
|
+
parsed += 1;
|
|
709
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Droid sessions" });
|
|
710
|
+
}
|
|
711
|
+
return groupSessionsByCwd("droid", sessions);
|
|
712
|
+
}
|
|
713
|
+
var droidAdapter = {
|
|
714
|
+
id: "droid",
|
|
715
|
+
name: "Droid (Factory)",
|
|
716
|
+
supported: true,
|
|
717
|
+
async detect(home) {
|
|
718
|
+
try {
|
|
719
|
+
await access5(droidRoot(home));
|
|
720
|
+
return true;
|
|
721
|
+
} catch {
|
|
722
|
+
return false;
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
loadProjects: loadDroidProjects
|
|
726
|
+
};
|
|
727
|
+
|
|
728
|
+
// src/agents/gemini.ts
|
|
729
|
+
import { access as access6 } from "fs/promises";
|
|
730
|
+
import { homedir as homedir7 } from "os";
|
|
731
|
+
import { join as join7 } from "path";
|
|
732
|
+
import { glob as glob5 } from "tinyglobby";
|
|
733
|
+
function geminiRoot(home) {
|
|
734
|
+
return process.env.GEMINI_DATA_DIR ?? join7(home ?? homedir7(), ".gemini", "tmp");
|
|
735
|
+
}
|
|
736
|
+
var IN = ["input", "prompt", "input_tokens", "prompt_tokens"];
|
|
737
|
+
var OUT = ["output", "candidates", "output_tokens", "candidates_tokens"];
|
|
738
|
+
function recordFrom(node, model, ts) {
|
|
739
|
+
const tokens = node.tokens;
|
|
740
|
+
if (!tokens) return void 0;
|
|
741
|
+
const reasoning = pickNum(tokens, ["thoughts", "reasoning", "thoughts_tokens", "reasoning_tokens"]);
|
|
742
|
+
return {
|
|
743
|
+
model: str(node, "model") ?? model ?? "gemini-unknown",
|
|
744
|
+
ts: str(node, "timestamp") ?? ts,
|
|
745
|
+
input: pickNum(tokens, IN),
|
|
746
|
+
output: pickNum(tokens, OUT) + reasoning,
|
|
747
|
+
cacheRead: pickNum(tokens, ["cached", "cached_tokens"]),
|
|
748
|
+
cacheWrite: 0
|
|
749
|
+
};
|
|
750
|
+
}
|
|
751
|
+
async function parseFile3(file) {
|
|
752
|
+
const records = [];
|
|
753
|
+
const push = (r) => r && records.push(r);
|
|
754
|
+
if (file.endsWith(".jsonl")) {
|
|
755
|
+
for (const line of await readJsonl(file)) push(recordFrom(line, str(line, "model"), void 0));
|
|
756
|
+
} else {
|
|
757
|
+
const doc = await readJson(file);
|
|
758
|
+
const model = str(doc, "model");
|
|
759
|
+
const messages = doc?.messages;
|
|
760
|
+
if (Array.isArray(messages)) for (const m of messages) push(recordFrom(m, model, void 0));
|
|
761
|
+
push(recordFrom(doc, model, void 0));
|
|
762
|
+
}
|
|
763
|
+
return sessionFromRecords(file, void 0, records);
|
|
764
|
+
}
|
|
765
|
+
async function loadGeminiProjects(home, onProgress) {
|
|
766
|
+
const files = await glob5(["**/*.json", "**/*.jsonl"], { cwd: geminiRoot(home), absolute: true }).catch(
|
|
767
|
+
() => []
|
|
768
|
+
);
|
|
769
|
+
const sessions = [];
|
|
770
|
+
let parsed = 0;
|
|
771
|
+
for (const f of files) {
|
|
772
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Gemini logs" });
|
|
773
|
+
sessions.push(await parseFile3(f));
|
|
774
|
+
parsed += 1;
|
|
775
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Gemini logs" });
|
|
776
|
+
}
|
|
777
|
+
return groupSessionsByCwd("gemini", sessions);
|
|
778
|
+
}
|
|
779
|
+
var geminiAdapter = {
|
|
780
|
+
id: "gemini",
|
|
781
|
+
name: "Gemini CLI",
|
|
782
|
+
supported: true,
|
|
783
|
+
async detect(home) {
|
|
784
|
+
try {
|
|
785
|
+
await access6(geminiRoot(home));
|
|
786
|
+
return true;
|
|
787
|
+
} catch {
|
|
788
|
+
return false;
|
|
789
|
+
}
|
|
790
|
+
},
|
|
791
|
+
loadProjects: loadGeminiProjects
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
// src/agents/goose.ts
|
|
795
|
+
import { access as access7 } from "fs/promises";
|
|
796
|
+
import { homedir as homedir8 } from "os";
|
|
797
|
+
import { join as join8 } from "path";
|
|
798
|
+
|
|
799
|
+
// src/sqlite.ts
|
|
800
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
801
|
+
function u8(b, o) {
|
|
802
|
+
return b[o];
|
|
803
|
+
}
|
|
804
|
+
function u16(b, o) {
|
|
805
|
+
return b[o] << 8 | b[o + 1];
|
|
806
|
+
}
|
|
807
|
+
function u32(b, o) {
|
|
808
|
+
return b[o] * 16777216 + (b[o + 1] << 16) + (b[o + 2] << 8) + b[o + 3];
|
|
809
|
+
}
|
|
810
|
+
function varint(b, o) {
|
|
811
|
+
let result = 0n;
|
|
812
|
+
for (let i = 0; i < 8; i++) {
|
|
813
|
+
const byte = b[o + i];
|
|
814
|
+
result = result << 7n | BigInt(byte & 127);
|
|
815
|
+
if ((byte & 128) === 0) return [result, i + 1];
|
|
816
|
+
}
|
|
817
|
+
result = result << 8n | BigInt(b[o + 8]);
|
|
818
|
+
return [result, 9];
|
|
819
|
+
}
|
|
820
|
+
function page(db, n) {
|
|
821
|
+
const start = (n - 1) * db.pageSize;
|
|
822
|
+
return db.buf.subarray(start, start + db.pageSize);
|
|
823
|
+
}
|
|
824
|
+
function payload(db, p, cellContentOffset, payloadLen) {
|
|
825
|
+
const U = db.usable;
|
|
826
|
+
const X = U - 35;
|
|
827
|
+
if (payloadLen <= X) return p.subarray(cellContentOffset, cellContentOffset + payloadLen);
|
|
828
|
+
const M = Math.floor((U - 12) * 32 / 255) - 23;
|
|
829
|
+
const K = M + (payloadLen - M) % (U - 4);
|
|
830
|
+
const local = K <= X ? K : M;
|
|
831
|
+
const parts = [p.subarray(cellContentOffset, cellContentOffset + local)];
|
|
832
|
+
let next = u32(p, cellContentOffset + local);
|
|
833
|
+
let remaining = payloadLen - local;
|
|
834
|
+
while (next !== 0 && remaining > 0) {
|
|
835
|
+
const ov = page(db, next);
|
|
836
|
+
next = u32(ov, 0);
|
|
837
|
+
const take = Math.min(remaining, U - 4);
|
|
838
|
+
parts.push(ov.subarray(4, 4 + take));
|
|
839
|
+
remaining -= take;
|
|
840
|
+
}
|
|
841
|
+
return Buffer.concat(parts);
|
|
842
|
+
}
|
|
843
|
+
function decodeRecord(rec, rowid) {
|
|
844
|
+
const [headerLen, hlBytes] = varint(rec, 0);
|
|
845
|
+
const serials = [];
|
|
846
|
+
let o = hlBytes;
|
|
847
|
+
while (o < Number(headerLen)) {
|
|
848
|
+
const [s, n] = varint(rec, o);
|
|
849
|
+
serials.push(s);
|
|
850
|
+
o += n;
|
|
851
|
+
}
|
|
852
|
+
const values = [];
|
|
853
|
+
let body = Number(headerLen);
|
|
854
|
+
for (const st of serials) {
|
|
855
|
+
const t = Number(st);
|
|
856
|
+
if (t === 0) {
|
|
857
|
+
values.push(rowid);
|
|
858
|
+
} else if (t >= 1 && t <= 6) {
|
|
859
|
+
const len = t <= 4 ? t : t === 5 ? 6 : 8;
|
|
860
|
+
let v = 0n;
|
|
861
|
+
for (let i = 0; i < len; i++) v = v << 8n | BigInt(rec[body + i]);
|
|
862
|
+
const bits = BigInt(len * 8);
|
|
863
|
+
if (v >> bits - 1n) v -= 1n << bits;
|
|
864
|
+
values.push(v >= -9007199254740991n && v <= 9007199254740991n ? Number(v) : v);
|
|
865
|
+
body += len;
|
|
866
|
+
} else if (t === 7) {
|
|
867
|
+
values.push(rec.readDoubleBE(body));
|
|
868
|
+
body += 8;
|
|
869
|
+
} else if (t === 8) {
|
|
870
|
+
values.push(0);
|
|
871
|
+
} else if (t === 9) {
|
|
872
|
+
values.push(1);
|
|
873
|
+
} else if (t >= 12 && t % 2 === 0) {
|
|
874
|
+
const len = (t - 12) / 2;
|
|
875
|
+
values.push(Uint8Array.from(rec.subarray(body, body + len)));
|
|
876
|
+
body += len;
|
|
877
|
+
} else if (t >= 13) {
|
|
878
|
+
const len = (t - 13) / 2;
|
|
879
|
+
values.push(rec.toString("utf8", body, body + len));
|
|
880
|
+
body += len;
|
|
881
|
+
} else {
|
|
882
|
+
values.push(null);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
return values;
|
|
886
|
+
}
|
|
887
|
+
function walkTable(db, rootPage, out) {
|
|
888
|
+
const p = page(db, rootPage);
|
|
889
|
+
const headerOffset = rootPage === 1 ? 100 : 0;
|
|
890
|
+
const type = u8(p, headerOffset);
|
|
891
|
+
const cellCount = u16(p, headerOffset + 3);
|
|
892
|
+
const interior = type === 5;
|
|
893
|
+
const cellPtrBase = headerOffset + (interior ? 12 : 8);
|
|
894
|
+
for (let i = 0; i < cellCount; i++) {
|
|
895
|
+
const cellOffset = u16(p, cellPtrBase + i * 2);
|
|
896
|
+
if (interior) {
|
|
897
|
+
const child = u32(p, cellOffset);
|
|
898
|
+
walkTable(db, child, out);
|
|
899
|
+
} else if (type === 13) {
|
|
900
|
+
const [plen, n1] = varint(p, cellOffset);
|
|
901
|
+
const [rowid, n2] = varint(p, cellOffset + n1);
|
|
902
|
+
const rec = payload(db, p, cellOffset + n1 + n2, Number(plen));
|
|
903
|
+
out.push([rowid, decodeRecord(rec, rowid)]);
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
if (interior) {
|
|
907
|
+
const rightMost = u32(p, headerOffset + 8);
|
|
908
|
+
if (rightMost) walkTable(db, rightMost, out);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
function columnNames(sql) {
|
|
912
|
+
const open = sql.indexOf("(");
|
|
913
|
+
const close = sql.lastIndexOf(")");
|
|
914
|
+
if (open < 0 || close < 0) return [];
|
|
915
|
+
const body = sql.slice(open + 1, close);
|
|
916
|
+
const cols = [];
|
|
917
|
+
let depth = 0;
|
|
918
|
+
let cur = "";
|
|
919
|
+
const parts = [];
|
|
920
|
+
for (const ch of body) {
|
|
921
|
+
if (ch === "(") depth++;
|
|
922
|
+
else if (ch === ")") depth--;
|
|
923
|
+
if (ch === "," && depth === 0) {
|
|
924
|
+
parts.push(cur);
|
|
925
|
+
cur = "";
|
|
926
|
+
} else cur += ch;
|
|
927
|
+
}
|
|
928
|
+
if (cur.trim()) parts.push(cur);
|
|
929
|
+
const CONSTRAINTS = /* @__PURE__ */ new Set(["primary", "unique", "check", "foreign", "constraint"]);
|
|
930
|
+
for (const part of parts) {
|
|
931
|
+
const token = part.trim().replace(/^["'`\[]|["'`\]]$/g, "");
|
|
932
|
+
const name = token.split(/[\s(]/)[0].replace(/["'`\[\]]/g, "");
|
|
933
|
+
if (name && !CONSTRAINTS.has(name.toLowerCase())) cols.push(name);
|
|
934
|
+
}
|
|
935
|
+
return cols;
|
|
936
|
+
}
|
|
937
|
+
async function readTable(file, table) {
|
|
938
|
+
let buf;
|
|
939
|
+
try {
|
|
940
|
+
buf = await readFile4(file);
|
|
941
|
+
} catch {
|
|
942
|
+
return [];
|
|
943
|
+
}
|
|
944
|
+
if (buf.length < 100 || buf.toString("latin1", 0, 16) !== "SQLite format 3\0") return [];
|
|
945
|
+
let pageSize = u16(buf, 16);
|
|
946
|
+
if (pageSize === 1) pageSize = 65536;
|
|
947
|
+
const reserved = u8(buf, 20);
|
|
948
|
+
const db = { buf, pageSize, usable: pageSize - reserved };
|
|
949
|
+
const master = [];
|
|
950
|
+
walkTable(db, 1, master);
|
|
951
|
+
let rootPage = 0;
|
|
952
|
+
let createSql = "";
|
|
953
|
+
for (const [, cols] of master) {
|
|
954
|
+
if (cols[0] === "table" && cols[1] === table) {
|
|
955
|
+
rootPage = Number(cols[3]);
|
|
956
|
+
createSql = typeof cols[4] === "string" ? cols[4] : "";
|
|
957
|
+
break;
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
if (!rootPage) return [];
|
|
961
|
+
const names = columnNames(createSql);
|
|
962
|
+
const rows = [];
|
|
963
|
+
walkTable(db, rootPage, rows);
|
|
964
|
+
return rows.map(([rowid, values]) => {
|
|
965
|
+
const row = {};
|
|
966
|
+
names.forEach((name, i) => row[name] = values[i] ?? null);
|
|
967
|
+
row.rowid ??= rowid <= 9007199254740991n ? Number(rowid) : rowid;
|
|
968
|
+
return row;
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// src/agents/goose.ts
|
|
973
|
+
var GOOSE_DB_CANDIDATES = [
|
|
974
|
+
[".local", "share", "goose", "sessions", "sessions.db"],
|
|
975
|
+
["Library", "Application Support", "goose", "sessions", "sessions.db"],
|
|
976
|
+
[".local", "share", "Block", "goose", "sessions", "sessions.db"]
|
|
977
|
+
];
|
|
978
|
+
function gooseDbCandidates(home) {
|
|
979
|
+
const root = process.env.GOOSE_PATH_ROOT;
|
|
980
|
+
if (root) return [join8(root, "data", "sessions", "sessions.db")];
|
|
981
|
+
const h = home ?? homedir8();
|
|
982
|
+
return GOOSE_DB_CANDIDATES.map((parts) => join8(h, ...parts));
|
|
983
|
+
}
|
|
984
|
+
async function loadGooseProjects(home, onProgress) {
|
|
985
|
+
const records = [];
|
|
986
|
+
for (const db of gooseDbCandidates(home)) {
|
|
987
|
+
const rows = await readTable(db, "sessions").catch(() => []);
|
|
988
|
+
for (const row of rows) {
|
|
989
|
+
onProgress?.({ parsed: records.length, total: rows.length, currentProject: "Goose sessions" });
|
|
990
|
+
const input = pickNum(row, ["accumulated_input_tokens"]) || pickNum(row, ["input_tokens"]);
|
|
991
|
+
const output = pickNum(row, ["accumulated_output_tokens"]) || pickNum(row, ["output_tokens"]);
|
|
992
|
+
const total = pickNum(row, ["accumulated_total_tokens"]) || pickNum(row, ["total_tokens"]);
|
|
993
|
+
const reasoning = Math.max(0, total - input - output);
|
|
994
|
+
let model = "goose-unknown";
|
|
995
|
+
try {
|
|
996
|
+
model = JSON.parse(String(row.model_config_json ?? "{}")).model_name || model;
|
|
997
|
+
} catch {
|
|
998
|
+
}
|
|
999
|
+
records.push({
|
|
1000
|
+
model,
|
|
1001
|
+
ts: toIso(row.created_at),
|
|
1002
|
+
input,
|
|
1003
|
+
output: output + reasoning,
|
|
1004
|
+
cacheRead: 0,
|
|
1005
|
+
cacheWrite: 0
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
const session = sessionFromRecords("goose", void 0, records);
|
|
1010
|
+
return groupSessionsByCwd("goose", [session]);
|
|
1011
|
+
}
|
|
1012
|
+
var gooseAdapter = {
|
|
1013
|
+
id: "goose",
|
|
1014
|
+
name: "Goose",
|
|
1015
|
+
supported: true,
|
|
1016
|
+
async detect(home) {
|
|
1017
|
+
for (const db of gooseDbCandidates(home)) {
|
|
1018
|
+
try {
|
|
1019
|
+
await access7(db);
|
|
1020
|
+
return true;
|
|
1021
|
+
} catch {
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return false;
|
|
1025
|
+
},
|
|
1026
|
+
loadProjects: loadGooseProjects
|
|
1027
|
+
};
|
|
1028
|
+
|
|
1029
|
+
// src/agents/hermes.ts
|
|
1030
|
+
import { access as access8 } from "fs/promises";
|
|
1031
|
+
import { homedir as homedir9 } from "os";
|
|
1032
|
+
import { join as join9 } from "path";
|
|
1033
|
+
function hermesDb(home) {
|
|
1034
|
+
const env = process.env.HERMES_HOME;
|
|
1035
|
+
const root = env ? env.split(",")[0].trim() : join9(home ?? homedir9(), ".hermes");
|
|
1036
|
+
return join9(root, "state.db");
|
|
1037
|
+
}
|
|
1038
|
+
async function loadHermesProjects(home, onProgress) {
|
|
1039
|
+
const rows = await readTable(hermesDb(home), "sessions").catch(() => []);
|
|
1040
|
+
const records = [];
|
|
1041
|
+
for (const row of rows) {
|
|
1042
|
+
onProgress?.({ parsed: records.length, total: rows.length, currentProject: "Hermes sessions" });
|
|
1043
|
+
records.push({
|
|
1044
|
+
model: str(row, "model") ?? "hermes-unknown",
|
|
1045
|
+
ts: toIso(row.started_at),
|
|
1046
|
+
input: pickNum(row, ["input_tokens"]),
|
|
1047
|
+
output: pickNum(row, ["output_tokens"]) + pickNum(row, ["reasoning_tokens"]),
|
|
1048
|
+
cacheRead: pickNum(row, ["cache_read_tokens"]),
|
|
1049
|
+
cacheWrite: pickNum(row, ["cache_write_tokens"])
|
|
1050
|
+
});
|
|
1051
|
+
}
|
|
1052
|
+
const session = sessionFromRecords("hermes", void 0, records);
|
|
1053
|
+
return groupSessionsByCwd("hermes", [session]);
|
|
1054
|
+
}
|
|
1055
|
+
var hermesAdapter = {
|
|
1056
|
+
id: "hermes",
|
|
1057
|
+
name: "Hermes",
|
|
1058
|
+
supported: true,
|
|
1059
|
+
async detect(home) {
|
|
1060
|
+
try {
|
|
1061
|
+
await access8(hermesDb(home));
|
|
1062
|
+
return true;
|
|
1063
|
+
} catch {
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
},
|
|
1067
|
+
loadProjects: loadHermesProjects
|
|
1068
|
+
};
|
|
1069
|
+
|
|
1070
|
+
// src/agents/kilo.ts
|
|
1071
|
+
import { access as access9 } from "fs/promises";
|
|
1072
|
+
import { homedir as homedir10 } from "os";
|
|
1073
|
+
import { join as join10 } from "path";
|
|
1074
|
+
function kiloDir(home) {
|
|
1075
|
+
return process.env.KILO_DATA_DIR ?? join10(home ?? homedir10(), ".local", "share", "kilo");
|
|
1076
|
+
}
|
|
1077
|
+
async function loadKiloProjects(home, onProgress) {
|
|
1078
|
+
const db = join10(kiloDir(home), "kilo.db");
|
|
1079
|
+
let rows;
|
|
1080
|
+
try {
|
|
1081
|
+
rows = await readTable(db, "message");
|
|
1082
|
+
} catch {
|
|
1083
|
+
return [];
|
|
1084
|
+
}
|
|
1085
|
+
const bySession = /* @__PURE__ */ new Map();
|
|
1086
|
+
let parsed = 0;
|
|
1087
|
+
for (const row of rows) {
|
|
1088
|
+
onProgress?.({ parsed, total: rows.length, currentProject: "Kilo messages" });
|
|
1089
|
+
parsed += 1;
|
|
1090
|
+
if (typeof row.data !== "string") continue;
|
|
1091
|
+
let m;
|
|
1092
|
+
try {
|
|
1093
|
+
m = JSON.parse(row.data);
|
|
1094
|
+
} catch {
|
|
1095
|
+
continue;
|
|
1096
|
+
}
|
|
1097
|
+
if (m.role !== "assistant" || !m.tokens) continue;
|
|
1098
|
+
const session = m.sessionID ?? String(row.session_id ?? "kilo");
|
|
1099
|
+
const list = bySession.get(session) ?? [];
|
|
1100
|
+
list.push({
|
|
1101
|
+
model: m.modelID ?? "kilo-unknown",
|
|
1102
|
+
ts: m.time?.created ? new Date(m.time.created).toISOString() : void 0,
|
|
1103
|
+
input: m.tokens.input ?? 0,
|
|
1104
|
+
output: (m.tokens.output ?? 0) + (m.tokens.reasoning ?? 0),
|
|
1105
|
+
cacheRead: m.tokens.cache?.read ?? 0,
|
|
1106
|
+
cacheWrite: m.tokens.cache?.write ?? 0
|
|
1107
|
+
});
|
|
1108
|
+
bySession.set(session, list);
|
|
1109
|
+
}
|
|
1110
|
+
const sessions = [];
|
|
1111
|
+
for (const [id, records] of bySession) sessions.push(sessionFromRecords(id, void 0, records));
|
|
1112
|
+
return groupSessionsByCwd("kilo", sessions);
|
|
1113
|
+
}
|
|
1114
|
+
var kiloAdapter = {
|
|
1115
|
+
id: "kilo",
|
|
1116
|
+
name: "Kilo",
|
|
1117
|
+
supported: true,
|
|
1118
|
+
async detect(home) {
|
|
1119
|
+
try {
|
|
1120
|
+
await access9(join10(kiloDir(home), "kilo.db"));
|
|
1121
|
+
return true;
|
|
1122
|
+
} catch {
|
|
1123
|
+
return false;
|
|
1124
|
+
}
|
|
1125
|
+
},
|
|
1126
|
+
loadProjects: loadKiloProjects
|
|
1127
|
+
};
|
|
1128
|
+
|
|
1129
|
+
// src/agents/kimi.ts
|
|
1130
|
+
import { access as access10 } from "fs/promises";
|
|
1131
|
+
import { homedir as homedir11 } from "os";
|
|
1132
|
+
import { join as join11 } from "path";
|
|
1133
|
+
import { glob as glob6 } from "tinyglobby";
|
|
1134
|
+
var KIMI_DIRS = [".kimi", ".kimi-code"];
|
|
1135
|
+
var USAGE_KEYS = ["inputOther", "input_other", "inputCacheRead", "input_cache_read"];
|
|
1136
|
+
function kimiRoots(home) {
|
|
1137
|
+
const env = process.env.KIMI_DATA_DIR;
|
|
1138
|
+
if (env) return env.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1139
|
+
const h = home ?? homedir11();
|
|
1140
|
+
return KIMI_DIRS.map((d) => join11(h, d));
|
|
1141
|
+
}
|
|
1142
|
+
async function parseFile4(file) {
|
|
1143
|
+
const records = [];
|
|
1144
|
+
for (const line of await readJsonl(file)) {
|
|
1145
|
+
const usage = deepFind(line, USAGE_KEYS);
|
|
1146
|
+
if (!usage) continue;
|
|
1147
|
+
records.push({
|
|
1148
|
+
model: str(line, "model") ?? "kimi-unknown",
|
|
1149
|
+
ts: str(line, "timestamp"),
|
|
1150
|
+
input: pickNum(usage, ["inputOther", "input_other"]),
|
|
1151
|
+
output: pickNum(usage, ["output"]),
|
|
1152
|
+
cacheRead: pickNum(usage, ["inputCacheRead", "input_cache_read"]),
|
|
1153
|
+
cacheWrite: pickNum(usage, ["inputCacheCreation", "input_cache_creation"])
|
|
1154
|
+
});
|
|
1155
|
+
}
|
|
1156
|
+
return sessionFromRecords(file, void 0, records);
|
|
1157
|
+
}
|
|
1158
|
+
async function loadKimiProjects(home, onProgress) {
|
|
1159
|
+
const files = [];
|
|
1160
|
+
for (const root of kimiRoots(home)) {
|
|
1161
|
+
files.push(
|
|
1162
|
+
...await glob6(["sessions/**/wire.jsonl"], { cwd: root, absolute: true }).catch(() => [])
|
|
1163
|
+
);
|
|
1164
|
+
}
|
|
1165
|
+
const sessions = [];
|
|
1166
|
+
let parsed = 0;
|
|
1167
|
+
for (const f of files) {
|
|
1168
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Kimi sessions" });
|
|
1169
|
+
sessions.push(await parseFile4(f));
|
|
1170
|
+
parsed += 1;
|
|
1171
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Kimi sessions" });
|
|
1172
|
+
}
|
|
1173
|
+
return groupSessionsByCwd("kimi", sessions);
|
|
1174
|
+
}
|
|
1175
|
+
var kimiAdapter = {
|
|
1176
|
+
id: "kimi",
|
|
1177
|
+
name: "Kimi CLI",
|
|
1178
|
+
supported: true,
|
|
1179
|
+
async detect(home) {
|
|
1180
|
+
for (const root of kimiRoots(home)) {
|
|
1181
|
+
try {
|
|
1182
|
+
await access10(join11(root, "sessions"));
|
|
1183
|
+
return true;
|
|
1184
|
+
} catch {
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
return false;
|
|
1188
|
+
},
|
|
1189
|
+
loadProjects: loadKimiProjects
|
|
1190
|
+
};
|
|
1191
|
+
|
|
1192
|
+
// src/agents/openclaw.ts
|
|
1193
|
+
import { access as access11 } from "fs/promises";
|
|
1194
|
+
import { homedir as homedir12 } from "os";
|
|
1195
|
+
import { join as join12 } from "path";
|
|
1196
|
+
import { glob as glob7 } from "tinyglobby";
|
|
1197
|
+
var OPENCLAW_DIRS = [".openclaw", ".clawdbot", ".moltbot", ".moldbot"];
|
|
1198
|
+
function openclawRoots(home) {
|
|
1199
|
+
const env = process.env.OPENCLAW_DIR;
|
|
1200
|
+
if (env) return env.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1201
|
+
const h = home ?? homedir12();
|
|
1202
|
+
return OPENCLAW_DIRS.map((d) => join12(h, d));
|
|
1203
|
+
}
|
|
1204
|
+
function modelFrom(obj) {
|
|
1205
|
+
return str(obj, "modelId") ?? str(obj, "model");
|
|
1206
|
+
}
|
|
1207
|
+
async function parseFile5(file) {
|
|
1208
|
+
const records = [];
|
|
1209
|
+
let currentModel;
|
|
1210
|
+
for (const line of await readJsonl(file)) {
|
|
1211
|
+
const l = line;
|
|
1212
|
+
if (l.type === "model_change" || l.type === "model-snapshot") {
|
|
1213
|
+
currentModel = modelFrom(l.data) ?? modelFrom(l) ?? currentModel;
|
|
1214
|
+
continue;
|
|
1215
|
+
}
|
|
1216
|
+
const usage = l.message?.usage;
|
|
1217
|
+
if (!usage) continue;
|
|
1218
|
+
const model = modelFrom(l.message) ?? currentModel ?? "openclaw-unknown";
|
|
1219
|
+
records.push({
|
|
1220
|
+
model,
|
|
1221
|
+
ts: str(l.message, "timestamp") ?? str(l, "timestamp"),
|
|
1222
|
+
input: pickNum(usage, ["input"]),
|
|
1223
|
+
output: pickNum(usage, ["output"]),
|
|
1224
|
+
cacheRead: pickNum(usage, ["cacheRead"]),
|
|
1225
|
+
cacheWrite: pickNum(usage, ["cacheWrite"])
|
|
1226
|
+
});
|
|
1227
|
+
}
|
|
1228
|
+
return sessionFromRecords(file, void 0, records);
|
|
1229
|
+
}
|
|
1230
|
+
async function loadOpenclawProjects(home, onProgress) {
|
|
1231
|
+
const files = [];
|
|
1232
|
+
for (const root of openclawRoots(home)) {
|
|
1233
|
+
files.push(...await glob7(["**/*.jsonl", "**/*.jsonl.*"], { cwd: root, absolute: true }).catch(() => []));
|
|
1234
|
+
}
|
|
1235
|
+
const sessions = [];
|
|
1236
|
+
let parsed = 0;
|
|
1237
|
+
for (const f of files) {
|
|
1238
|
+
onProgress?.({ parsed, total: files.length, currentProject: "OpenClaw sessions" });
|
|
1239
|
+
sessions.push(await parseFile5(f));
|
|
1240
|
+
parsed += 1;
|
|
1241
|
+
onProgress?.({ parsed, total: files.length, currentProject: "OpenClaw sessions" });
|
|
1242
|
+
}
|
|
1243
|
+
return groupSessionsByCwd("openclaw", sessions);
|
|
1244
|
+
}
|
|
1245
|
+
var openclawAdapter = {
|
|
1246
|
+
id: "openclaw",
|
|
1247
|
+
name: "OpenClaw",
|
|
1248
|
+
supported: true,
|
|
1249
|
+
async detect(home) {
|
|
1250
|
+
for (const root of openclawRoots(home)) {
|
|
1251
|
+
try {
|
|
1252
|
+
await access11(root);
|
|
1253
|
+
return true;
|
|
1254
|
+
} catch {
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
return false;
|
|
1258
|
+
},
|
|
1259
|
+
loadProjects: loadOpenclawProjects
|
|
1260
|
+
};
|
|
1261
|
+
|
|
526
1262
|
// src/agents/opencode.ts
|
|
527
|
-
import { access as
|
|
528
|
-
import { homedir as
|
|
529
|
-
import { join as
|
|
530
|
-
import { glob as
|
|
1263
|
+
import { access as access12, readFile as readFile5 } from "fs/promises";
|
|
1264
|
+
import { homedir as homedir13 } from "os";
|
|
1265
|
+
import { join as join13 } from "path";
|
|
1266
|
+
import { glob as glob8 } from "tinyglobby";
|
|
531
1267
|
import { z as z2 } from "zod";
|
|
532
1268
|
var Message = z2.object({
|
|
533
1269
|
sessionID: z2.string(),
|
|
@@ -550,34 +1286,34 @@ var Session = z2.object({
|
|
|
550
1286
|
});
|
|
551
1287
|
var Project = z2.object({ id: z2.string(), worktree: z2.string().optional() });
|
|
552
1288
|
function opencodeRoot(home) {
|
|
553
|
-
if (home) return
|
|
554
|
-
return process.env.OPENCODE_DATA_DIR ??
|
|
1289
|
+
if (home) return join13(home, ".local", "share", "opencode");
|
|
1290
|
+
return process.env.OPENCODE_DATA_DIR ?? join13(homedir13(), ".local", "share", "opencode");
|
|
555
1291
|
}
|
|
556
|
-
async function
|
|
1292
|
+
async function readJson2(file) {
|
|
557
1293
|
try {
|
|
558
|
-
return JSON.parse(await
|
|
1294
|
+
return JSON.parse(await readFile5(file, "utf8"));
|
|
559
1295
|
} catch {
|
|
560
1296
|
return void 0;
|
|
561
1297
|
}
|
|
562
1298
|
}
|
|
563
1299
|
async function loadOpencodeProjects(home, onProgress) {
|
|
564
1300
|
const root = opencodeRoot(home);
|
|
565
|
-
const storage =
|
|
566
|
-
const messageFiles = await
|
|
1301
|
+
const storage = join13(root, "storage");
|
|
1302
|
+
const messageFiles = await glob8(["message/*/*.json"], { cwd: storage, absolute: true }).catch(
|
|
567
1303
|
() => []
|
|
568
1304
|
);
|
|
569
1305
|
if (messageFiles.length === 0) return [];
|
|
570
1306
|
const sessionDir = /* @__PURE__ */ new Map();
|
|
571
1307
|
const projectWorktree = /* @__PURE__ */ new Map();
|
|
572
|
-
for (const f of await
|
|
573
|
-
const p = Project.safeParse(await
|
|
1308
|
+
for (const f of await glob8(["project/*.json"], { cwd: storage, absolute: true }).catch(() => [])) {
|
|
1309
|
+
const p = Project.safeParse(await readJson2(f));
|
|
574
1310
|
if (p.success && p.data.worktree) projectWorktree.set(p.data.id, p.data.worktree);
|
|
575
1311
|
}
|
|
576
|
-
const sessionFiles = await
|
|
1312
|
+
const sessionFiles = await glob8(["session/**/*.json"], { cwd: storage, absolute: true }).catch(
|
|
577
1313
|
() => []
|
|
578
1314
|
);
|
|
579
1315
|
for (const f of sessionFiles) {
|
|
580
|
-
const s = Session.safeParse(await
|
|
1316
|
+
const s = Session.safeParse(await readJson2(f));
|
|
581
1317
|
if (!s.success) continue;
|
|
582
1318
|
const dir = s.data.directory ?? (s.data.projectID ? projectWorktree.get(s.data.projectID) : void 0);
|
|
583
1319
|
if (dir) sessionDir.set(s.data.id, dir);
|
|
@@ -586,7 +1322,7 @@ async function loadOpencodeProjects(home, onProgress) {
|
|
|
586
1322
|
let parsed = 0;
|
|
587
1323
|
for (const f of messageFiles) {
|
|
588
1324
|
onProgress?.({ parsed, total: messageFiles.length, currentProject: "OpenCode messages" });
|
|
589
|
-
const m = Message.safeParse(await
|
|
1325
|
+
const m = Message.safeParse(await readJson2(f));
|
|
590
1326
|
parsed += 1;
|
|
591
1327
|
onProgress?.({ parsed, total: messageFiles.length, currentProject: "OpenCode messages" });
|
|
592
1328
|
if (!m.success || m.data.role !== "assistant" || !m.data.tokens) continue;
|
|
@@ -619,28 +1355,7 @@ async function loadOpencodeProjects(home, onProgress) {
|
|
|
619
1355
|
u.turns += 1;
|
|
620
1356
|
}
|
|
621
1357
|
}
|
|
622
|
-
|
|
623
|
-
for (const s of bySession.values()) {
|
|
624
|
-
if (Object.keys(s.usageByModel).length === 0) continue;
|
|
625
|
-
const key = s.cwd ?? "(unknown project)";
|
|
626
|
-
const list = byDir.get(key) ?? [];
|
|
627
|
-
list.push(s);
|
|
628
|
-
byDir.set(key, list);
|
|
629
|
-
}
|
|
630
|
-
const out = [];
|
|
631
|
-
for (const [dir, sessions] of byDir) {
|
|
632
|
-
out.push({
|
|
633
|
-
id: `opencode:${dir}`,
|
|
634
|
-
name: dir,
|
|
635
|
-
label: dir === "(unknown project)" ? dir : baseName(dir),
|
|
636
|
-
realPath: dir,
|
|
637
|
-
report: buildReport(sessions, []),
|
|
638
|
-
sessions,
|
|
639
|
-
serverList: []
|
|
640
|
-
});
|
|
641
|
-
}
|
|
642
|
-
out.sort((a, b) => b.report.totalCostUsd - a.report.totalCostUsd);
|
|
643
|
-
return out;
|
|
1358
|
+
return groupSessionsByCwd("opencode", [...bySession.values()]);
|
|
644
1359
|
}
|
|
645
1360
|
var opencodeAdapter = {
|
|
646
1361
|
id: "opencode",
|
|
@@ -648,7 +1363,7 @@ var opencodeAdapter = {
|
|
|
648
1363
|
supported: true,
|
|
649
1364
|
async detect(home) {
|
|
650
1365
|
try {
|
|
651
|
-
await
|
|
1366
|
+
await access12(join13(opencodeRoot(home), "storage", "message"));
|
|
652
1367
|
return true;
|
|
653
1368
|
} catch {
|
|
654
1369
|
return false;
|
|
@@ -657,20 +1372,130 @@ var opencodeAdapter = {
|
|
|
657
1372
|
loadProjects: loadOpencodeProjects
|
|
658
1373
|
};
|
|
659
1374
|
|
|
1375
|
+
// src/agents/pi.ts
|
|
1376
|
+
import { access as access13 } from "fs/promises";
|
|
1377
|
+
import { homedir as homedir14 } from "os";
|
|
1378
|
+
import { join as join14 } from "path";
|
|
1379
|
+
import { glob as glob9 } from "tinyglobby";
|
|
1380
|
+
function piRoot(home) {
|
|
1381
|
+
return process.env.PI_AGENT_DIR ?? join14(home ?? homedir14(), ".pi", "agent", "sessions");
|
|
1382
|
+
}
|
|
1383
|
+
async function parseFile6(file) {
|
|
1384
|
+
const records = [];
|
|
1385
|
+
for (const line of await readJsonl(file)) {
|
|
1386
|
+
const msg = line.message;
|
|
1387
|
+
const usage = msg?.usage;
|
|
1388
|
+
if (!usage) continue;
|
|
1389
|
+
records.push({
|
|
1390
|
+
model: str(msg, "model") ?? "pi-unknown",
|
|
1391
|
+
ts: str(line, "timestamp"),
|
|
1392
|
+
input: pickNum(usage, ["input"]),
|
|
1393
|
+
output: pickNum(usage, ["output"]),
|
|
1394
|
+
cacheRead: pickNum(usage, ["cacheRead"]),
|
|
1395
|
+
cacheWrite: pickNum(usage, ["cacheWrite"])
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
return sessionFromRecords(file, void 0, records);
|
|
1399
|
+
}
|
|
1400
|
+
async function loadPiProjects(home, onProgress) {
|
|
1401
|
+
const files = await glob9(["**/*.jsonl"], { cwd: piRoot(home), absolute: true }).catch(() => []);
|
|
1402
|
+
const sessions = [];
|
|
1403
|
+
let parsed = 0;
|
|
1404
|
+
for (const f of files) {
|
|
1405
|
+
onProgress?.({ parsed, total: files.length, currentProject: "pi sessions" });
|
|
1406
|
+
sessions.push(await parseFile6(f));
|
|
1407
|
+
parsed += 1;
|
|
1408
|
+
onProgress?.({ parsed, total: files.length, currentProject: "pi sessions" });
|
|
1409
|
+
}
|
|
1410
|
+
return groupSessionsByCwd("pi", sessions);
|
|
1411
|
+
}
|
|
1412
|
+
var piAdapter = {
|
|
1413
|
+
id: "pi",
|
|
1414
|
+
name: "pi-agent",
|
|
1415
|
+
supported: true,
|
|
1416
|
+
async detect(home) {
|
|
1417
|
+
try {
|
|
1418
|
+
await access13(piRoot(home));
|
|
1419
|
+
return true;
|
|
1420
|
+
} catch {
|
|
1421
|
+
return false;
|
|
1422
|
+
}
|
|
1423
|
+
},
|
|
1424
|
+
loadProjects: loadPiProjects
|
|
1425
|
+
};
|
|
1426
|
+
|
|
1427
|
+
// src/agents/qwen.ts
|
|
1428
|
+
import { access as access14 } from "fs/promises";
|
|
1429
|
+
import { homedir as homedir15 } from "os";
|
|
1430
|
+
import { join as join15 } from "path";
|
|
1431
|
+
import { glob as glob10 } from "tinyglobby";
|
|
1432
|
+
function qwenRoot(home) {
|
|
1433
|
+
return process.env.QWEN_DATA_DIR ?? join15(home ?? homedir15(), ".qwen");
|
|
1434
|
+
}
|
|
1435
|
+
async function parseFile7(file) {
|
|
1436
|
+
const records = [];
|
|
1437
|
+
for (const line of await readJsonl(file)) {
|
|
1438
|
+
const meta = line.usageMetadata;
|
|
1439
|
+
if (!meta) continue;
|
|
1440
|
+
const prompt = pickNum(meta, ["promptTokenCount"]);
|
|
1441
|
+
const cached = pickNum(meta, ["cachedContentTokenCount"]);
|
|
1442
|
+
const reasoning = pickNum(meta, ["thoughtsTokenCount"]);
|
|
1443
|
+
records.push({
|
|
1444
|
+
model: str(line, "model") ?? "qwen-unknown",
|
|
1445
|
+
ts: str(line, "timestamp"),
|
|
1446
|
+
input: Math.max(0, prompt - cached),
|
|
1447
|
+
output: pickNum(meta, ["candidatesTokenCount"]) + reasoning,
|
|
1448
|
+
cacheRead: cached,
|
|
1449
|
+
cacheWrite: 0
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
return sessionFromRecords(file, void 0, records);
|
|
1453
|
+
}
|
|
1454
|
+
async function loadQwenProjects(home, onProgress) {
|
|
1455
|
+
const files = await glob10(["projects/**/*.jsonl"], { cwd: qwenRoot(home), absolute: true }).catch(
|
|
1456
|
+
() => []
|
|
1457
|
+
);
|
|
1458
|
+
const sessions = [];
|
|
1459
|
+
let parsed = 0;
|
|
1460
|
+
for (const f of files) {
|
|
1461
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Qwen sessions" });
|
|
1462
|
+
sessions.push(await parseFile7(f));
|
|
1463
|
+
parsed += 1;
|
|
1464
|
+
onProgress?.({ parsed, total: files.length, currentProject: "Qwen sessions" });
|
|
1465
|
+
}
|
|
1466
|
+
return groupSessionsByCwd("qwen", sessions);
|
|
1467
|
+
}
|
|
1468
|
+
var qwenAdapter = {
|
|
1469
|
+
id: "qwen",
|
|
1470
|
+
name: "Qwen Code",
|
|
1471
|
+
supported: true,
|
|
1472
|
+
async detect(home) {
|
|
1473
|
+
try {
|
|
1474
|
+
await access14(join15(qwenRoot(home), "projects"));
|
|
1475
|
+
return true;
|
|
1476
|
+
} catch {
|
|
1477
|
+
return false;
|
|
1478
|
+
}
|
|
1479
|
+
},
|
|
1480
|
+
loadProjects: loadQwenProjects
|
|
1481
|
+
};
|
|
1482
|
+
|
|
660
1483
|
// src/agents/index.ts
|
|
661
|
-
function detectOnly(id, name, reason, ...
|
|
1484
|
+
function detectOnly(id, name, reason, ...pathCandidates) {
|
|
662
1485
|
return {
|
|
663
1486
|
id,
|
|
664
1487
|
name,
|
|
665
1488
|
supported: false,
|
|
666
1489
|
unsupportedReason: reason,
|
|
667
|
-
async detect(home =
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
1490
|
+
async detect(home = homedir16()) {
|
|
1491
|
+
for (const parts of pathCandidates) {
|
|
1492
|
+
try {
|
|
1493
|
+
await access15(join16(home, ...parts));
|
|
1494
|
+
return true;
|
|
1495
|
+
} catch {
|
|
1496
|
+
}
|
|
673
1497
|
}
|
|
1498
|
+
return false;
|
|
674
1499
|
},
|
|
675
1500
|
loadProjects: async () => []
|
|
676
1501
|
};
|
|
@@ -679,8 +1504,23 @@ var ADAPTERS = [
|
|
|
679
1504
|
claudeAdapter,
|
|
680
1505
|
codexAdapter,
|
|
681
1506
|
opencodeAdapter,
|
|
1507
|
+
geminiAdapter,
|
|
1508
|
+
qwenAdapter,
|
|
1509
|
+
droidAdapter,
|
|
1510
|
+
codebuffAdapter,
|
|
1511
|
+
openclawAdapter,
|
|
1512
|
+
kimiAdapter,
|
|
1513
|
+
kiloAdapter,
|
|
1514
|
+
gooseAdapter,
|
|
1515
|
+
hermesAdapter,
|
|
1516
|
+
piAdapter,
|
|
682
1517
|
antigravityAdapter,
|
|
683
|
-
|
|
1518
|
+
// Detected but not parsed: Copilot uses OpenTelemetry spans, Amp a usage
|
|
1519
|
+
// ledger (formats not wired yet). Cursor is intentionally absent — it stores
|
|
1520
|
+
// no token counts on disk (usage is server-side, reachable only with auth),
|
|
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"])
|
|
684
1524
|
];
|
|
685
1525
|
async function loadAllAgents(home, onProgress) {
|
|
686
1526
|
const out = [];
|
|
@@ -767,11 +1607,15 @@ var ART = [
|
|
|
767
1607
|
" \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588 ",
|
|
768
1608
|
" \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588 \u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588 "
|
|
769
1609
|
];
|
|
770
|
-
function
|
|
1610
|
+
function bannerHeight(mode) {
|
|
1611
|
+
return mode === "full" ? 7 : mode === "tiny" ? 3 : 0;
|
|
1612
|
+
}
|
|
1613
|
+
function Banner({ subtitle, mode }) {
|
|
771
1614
|
const { cols, rows } = useTerminalSize();
|
|
772
|
-
const
|
|
1615
|
+
const resolved = mode ?? (cols < 40 || rows < 18 ? "tiny" : "full");
|
|
1616
|
+
if (resolved === "none") return null;
|
|
773
1617
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
774
|
-
tiny ? /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2590 TOKZ \u258C" }) : ART.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: line }, i)),
|
|
1618
|
+
resolved === "tiny" ? /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u2590 TOKZ \u258C" }) : ART.map((line, i) => /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: line }, i)),
|
|
775
1619
|
subtitle ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: subtitle }) : null
|
|
776
1620
|
] });
|
|
777
1621
|
}
|
|
@@ -797,6 +1641,13 @@ function modelColor(modelId) {
|
|
|
797
1641
|
return "white";
|
|
798
1642
|
}
|
|
799
1643
|
|
|
1644
|
+
// src/ui/viewport.ts
|
|
1645
|
+
function windowOffset(cursor, count, height) {
|
|
1646
|
+
if (count <= height) return 0;
|
|
1647
|
+
const centered = cursor - Math.floor(height / 2);
|
|
1648
|
+
return Math.max(0, Math.min(centered, count - height));
|
|
1649
|
+
}
|
|
1650
|
+
|
|
800
1651
|
// src/ui/AgentPicker.tsx
|
|
801
1652
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
802
1653
|
function status(a) {
|
|
@@ -822,33 +1673,43 @@ function AgentPicker({
|
|
|
822
1673
|
const selectable = agents.map((a, i) => ({ a, i })).filter(({ a }) => a.projects.length > 0).map(({ i }) => i);
|
|
823
1674
|
const [cursor, setCursor] = useState2(0);
|
|
824
1675
|
const clamped = Math.min(cursor, Math.max(0, selectable.length - 1));
|
|
1676
|
+
const { cols, rows } = useTerminalSize();
|
|
825
1677
|
useInput((_input, key) => {
|
|
826
1678
|
if (key.upArrow) setCursor(() => Math.max(0, clamped - 1));
|
|
827
1679
|
if (key.downArrow) setCursor(() => Math.min(selectable.length - 1, clamped + 1));
|
|
828
1680
|
if (key.return && selectable[clamped] !== void 0) onSelect(selectable[clamped]);
|
|
829
1681
|
});
|
|
830
1682
|
const nameW = Math.max(...agents.map((a) => a.adapter.name.length)) + 2;
|
|
1683
|
+
const FIXED_CHROME = 9;
|
|
1684
|
+
const LIST_MIN = 3;
|
|
1685
|
+
const forcedTiny = cols < 40;
|
|
1686
|
+
const bannerBudget = rows - FIXED_CHROME - LIST_MIN;
|
|
1687
|
+
const bannerMode = !forcedTiny && bannerBudget >= 7 ? "full" : bannerBudget >= 3 ? "tiny" : "none";
|
|
1688
|
+
const visibleRows = Math.max(1, rows - FIXED_CHROME - bannerHeight(bannerMode));
|
|
1689
|
+
const selectedDisplayIdx = selectable[clamped] ?? 0;
|
|
1690
|
+
const offset = windowOffset(selectedDisplayIdx, agents.length, visibleRows);
|
|
1691
|
+
const visible = agents.slice(offset, offset + visibleRows);
|
|
1692
|
+
const hiddenBelow = agents.length - offset - visible.length;
|
|
831
1693
|
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, children: [
|
|
832
|
-
/* @__PURE__ */ jsx2(Banner, { subtitle: "where your agents' tokens and dollars go
|
|
1694
|
+
/* @__PURE__ */ jsx2(Banner, { subtitle: "where your agents' tokens and dollars actually go", mode: bannerMode }),
|
|
833
1695
|
/* @__PURE__ */ jsx2(Text2, { bold: true, color: theme.accent, children: "Pick an agent" }),
|
|
834
1696
|
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, marginTop: 1, children: [
|
|
835
|
-
|
|
1697
|
+
visible.map((a, vi) => {
|
|
1698
|
+
const i = offset + vi;
|
|
836
1699
|
const st = status(a);
|
|
837
1700
|
const selected = selectable[clamped] === i;
|
|
838
1701
|
const selectableRow = a.projects.length > 0;
|
|
839
1702
|
return /* @__PURE__ */ jsxs2(Text2, { bold: selected, children: [
|
|
840
1703
|
/* @__PURE__ */ jsx2(Text2, { color: selected ? theme.accent : void 0, children: selected ? "\u25B8 " : " " }),
|
|
841
|
-
/* @__PURE__ */ jsx2(
|
|
842
|
-
Text2,
|
|
843
|
-
{
|
|
844
|
-
color: selected ? theme.accent : selectableRow ? void 0 : void 0,
|
|
845
|
-
dimColor: !selectableRow,
|
|
846
|
-
children: a.adapter.name.padEnd(nameW)
|
|
847
|
-
}
|
|
848
|
-
),
|
|
1704
|
+
/* @__PURE__ */ jsx2(Text2, { color: selected ? theme.accent : void 0, dimColor: !selectableRow, children: a.adapter.name.padEnd(nameW) }),
|
|
849
1705
|
/* @__PURE__ */ jsx2(Text2, { color: st.color, dimColor: st.dim, children: st.text })
|
|
850
1706
|
] }, a.adapter.id);
|
|
851
1707
|
}),
|
|
1708
|
+
agents.length > visibleRows ? /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
1709
|
+
offset > 0 ? `\u2191 ${offset} above` : "",
|
|
1710
|
+
offset > 0 && hiddenBelow > 0 ? " \xB7 " : "",
|
|
1711
|
+
hiddenBelow > 0 ? `\u2193 ${hiddenBelow} below` : ""
|
|
1712
|
+
] }) : null,
|
|
852
1713
|
selectable.length === 0 ? /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: "no agent usage data found on this machine" }) : null
|
|
853
1714
|
] })
|
|
854
1715
|
] });
|
|
@@ -924,7 +1785,7 @@ function Menu({
|
|
|
924
1785
|
/* @__PURE__ */ jsx5(
|
|
925
1786
|
Banner,
|
|
926
1787
|
{
|
|
927
|
-
subtitle: `${agentName ? `${agentName} \xB7 ` : ""}where your agent's tokens and dollars go
|
|
1788
|
+
subtitle: `${agentName ? `${agentName} \xB7 ` : ""}where your agent's tokens and dollars go`
|
|
928
1789
|
}
|
|
929
1790
|
),
|
|
930
1791
|
/* @__PURE__ */ jsx5(Box4, { marginBottom: 1, children: /* @__PURE__ */ jsx5(
|
|
@@ -1075,12 +1936,13 @@ function ProjectList({
|
|
|
1075
1936
|
const fixed = COST_W + (showSess ? SESS_W : 0) + (showWhen ? WHEN_W : 0) + (showBar ? BAR_W + 2 : 0);
|
|
1076
1937
|
const nameW = Math.max(10, Math.min(42, avail - fixed - 2));
|
|
1077
1938
|
const barW = showBar ? Math.min(BAR_W + Math.max(0, avail - fixed - 2 - nameW), 30) : 0;
|
|
1078
|
-
const
|
|
1939
|
+
const CHROME_ROWS = 11;
|
|
1940
|
+
const visibleRows = Math.max(3, rows - CHROME_ROWS);
|
|
1079
1941
|
const clip = (s) => s.length > nameW ? s.slice(0, nameW - 1) + "\u2026" : s;
|
|
1080
1942
|
const total = projects.reduce((s, p) => s + p.report.totalCostUsd, 0);
|
|
1081
1943
|
const max = Math.max(0, ...projects.map((p) => p.report.totalCostUsd));
|
|
1082
1944
|
const projCursor = clamped - (pinnedShown ? 1 : 0);
|
|
1083
|
-
const offset = Math.max(0,
|
|
1945
|
+
const offset = windowOffset(Math.max(0, projCursor), filtered.length, visibleRows);
|
|
1084
1946
|
const visible = filtered.slice(offset, offset + visibleRows);
|
|
1085
1947
|
const line = (label, sess, when, cost, share, selected) => (selected ? "\u25B8 " : " ") + clip(label).padEnd(nameW) + (showSess ? sess.padStart(SESS_W) : "") + (showWhen ? when.padStart(WHEN_W) : "") + cost.padStart(COST_W) + (showBar ? " " + share : "");
|
|
1086
1948
|
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", paddingX: 1, children: [
|
|
@@ -1670,7 +2532,7 @@ function Root() {
|
|
|
1670
2532
|
}, [empty, exit]);
|
|
1671
2533
|
if (!agents) {
|
|
1672
2534
|
return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", paddingX: 1, children: [
|
|
1673
|
-
/* @__PURE__ */ jsx11(Banner, { subtitle: "where your agents' tokens and dollars go
|
|
2535
|
+
/* @__PURE__ */ jsx11(Banner, { subtitle: "where your agents' tokens and dollars go" }),
|
|
1674
2536
|
/* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", children: [
|
|
1675
2537
|
/* @__PURE__ */ jsxs10(Text11, { children: [
|
|
1676
2538
|
/* @__PURE__ */ jsx11(Text11, { color: theme.accent, children: SPINNER[frame % SPINNER.length] }),
|