@tokz/cli 0.2.4 → 0.2.6

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.
@@ -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 access5 } from "fs/promises";
31
- import { homedir as homedir6 } from "os";
32
- import { join as join6 } from "path";
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
- const byDir = /* @__PURE__ */ new Map();
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/codex.ts
372
- import { createReadStream } from "fs";
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 join4(home, ".codex");
401
- return process.env.CODEX_HOME ?? join4(homedir4(), ".codex");
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 = payload?.cwd ?? parsed.data.cwd;
422
- if (payload?.model) model = payload.model;
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 = payload?.type === "function_call" || payload?.type === "custom_tool_call" ? payload.name : payload?.type === "local_shell_call" ? "shell" : type === "function_call" ? parsed.data.name : void 0;
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 = payload?.type === "token_count" ? payload.info?.total_token_usage : void 0;
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 glob2(["sessions/**/*.jsonl", "archived_sessions/**/*.jsonl"], {
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 byCwd = /* @__PURE__ */ new Map();
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
- const s = await parseCodexRollout(f);
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
- const out = [];
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 access3(join4(codexHome(home), "sessions"));
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 access4, readFile as readFile3 } from "fs/promises";
528
- import { homedir as homedir5 } from "os";
529
- import { join as join5 } from "path";
530
- import { glob as glob3 } from "tinyglobby";
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 join5(home, ".local", "share", "opencode");
554
- return process.env.OPENCODE_DATA_DIR ?? join5(homedir5(), ".local", "share", "opencode");
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 readJson(file) {
1292
+ async function readJson2(file) {
557
1293
  try {
558
- return JSON.parse(await readFile3(file, "utf8"));
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 = join5(root, "storage");
566
- const messageFiles = await glob3(["message/*/*.json"], { cwd: storage, absolute: true }).catch(
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 glob3(["project/*.json"], { cwd: storage, absolute: true }).catch(() => [])) {
573
- const p = Project.safeParse(await readJson(f));
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 glob3(["session/**/*.json"], { cwd: storage, absolute: true }).catch(
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 readJson(f));
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 readJson(f));
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
- const byDir = /* @__PURE__ */ new Map();
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 access4(join5(opencodeRoot(home), "storage", "message"));
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, ...pathParts) {
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 = homedir6()) {
668
- try {
669
- await access5(join6(home, ...pathParts));
670
- return true;
671
- } catch {
672
- return false;
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
- detectOnly("cursor", "Cursor CLI", "sessions live in SQLite; parsing not supported yet", ".cursor", "chats")
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 Banner({ subtitle }) {
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 tiny = cols < 40 || rows < 18;
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 \xB7 100% offline" }),
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
- agents.map((a, i) => {
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 \xB7 100% offline`
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 visibleRows = Math.max(4, Math.min(20, rows - 10));
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, Math.min(projCursor - Math.floor(visibleRows / 2), filtered.length - visibleRows));
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 \xB7 100% offline" }),
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] }),