@tokz/cli 0.2.12 → 0.2.14

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.
@@ -632,149 +632,191 @@ var codebuffAdapter = {
632
632
  };
633
633
 
634
634
  // src/agents/codex.ts
635
- import { access as access5, readFile as readFile4 } from "fs/promises";
635
+ import { access as access5, readFile as readFile4, stat as stat2 } from "fs/promises";
636
636
  import { homedir as homedir6 } from "os";
637
637
  import { join as join6 } from "path";
638
638
  import { glob as glob4 } from "tinyglobby";
639
- import { z } from "zod";
640
- var TokenTotals = z.object({
641
- input_tokens: z.number().catch(0).default(0),
642
- cached_input_tokens: z.number().catch(0).default(0),
643
- output_tokens: z.number().catch(0).default(0),
644
- reasoning_output_tokens: z.number().catch(0).default(0),
645
- total_tokens: z.number().catch(0).default(0)
646
- });
647
- var Line = z.object({
648
- timestamp: z.string().optional(),
649
- type: z.string().optional(),
650
- // old-format meta lines carry cwd at the top level
651
- cwd: z.string().optional(),
652
- payload: z.object({
653
- type: z.string().optional(),
654
- cwd: z.string().optional(),
655
- model: z.string().optional(),
656
- name: z.string().optional(),
657
- info: z.object({
658
- total_token_usage: TokenTotals.optional(),
659
- // Codex writes the per-turn amount here directly; prefer it over
660
- // differencing cumulative totals.
661
- last_token_usage: TokenTotals.optional()
662
- }).nullish()
663
- }).passthrough().optional()
664
- });
665
- function codexHome(home) {
666
- if (home) return join6(home, ".codex");
667
- return process.env.CODEX_HOME ?? join6(homedir6(), ".codex");
668
- }
669
639
  var DEFAULT_MODEL = "gpt-5";
670
- function tsSecond(ts) {
671
- return (ts ?? "").slice(0, 19);
672
- }
673
- function replayBurstSecond(content, lines) {
674
- const head = content.slice(0, 16384);
675
- if (!head.includes("thread_spawn") && !head.includes("forked_from_id")) return void 0;
676
- let first;
677
- for (const p of lines) {
678
- const info = p.payload?.type === "token_count" ? p.payload.info : void 0;
679
- if (!info?.last_token_usage && !info?.total_token_usage) continue;
680
- const sec = tsSecond(p.timestamp);
681
- if (first === void 0) first = sec;
682
- else return first === sec ? first : void 0;
640
+ var CODEX_AUTO_REVIEW_MODEL = "codex-auto-review";
641
+ var CODEX_AUTO_REVIEW_FALLBACKS = [
642
+ { releasedOn: "2026-04-23", model: "gpt-5.5" },
643
+ { releasedOn: "2026-03-05", model: "gpt-5.4" },
644
+ { releasedOn: "2026-02-05", model: "gpt-5.3-codex" },
645
+ { releasedOn: "2025-12-11", model: "gpt-5.2-codex" },
646
+ { releasedOn: "2025-11-13", model: "gpt-5.1-codex" },
647
+ { releasedOn: "2025-09-15", model: "gpt-5-codex" },
648
+ { releasedOn: "2025-08-07", model: "gpt-5" }
649
+ ];
650
+ function codexHomes(home) {
651
+ if (home) return [join6(home, ".codex")];
652
+ const env = process.env.CODEX_HOME;
653
+ if (env) {
654
+ return env.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
655
+ }
656
+ return [join6(homedir6(), ".codex")];
657
+ }
658
+ function asDict(v) {
659
+ return typeof v === "object" && v !== null && !Array.isArray(v) ? v : void 0;
660
+ }
661
+ function u64(v) {
662
+ if (typeof v === "number") {
663
+ return Number.isInteger(v) && v >= 0 ? v : void 0;
664
+ }
665
+ if (typeof v === "string") {
666
+ const t = v.trim();
667
+ return /^\d+$/.test(t) ? Number(t) : void 0;
683
668
  }
684
669
  return void 0;
685
670
  }
671
+ function rawUsage(v) {
672
+ const o = asDict(v);
673
+ if (!o) return void 0;
674
+ const input = u64(o.input_tokens) ?? u64(o.prompt_tokens) ?? u64(o.input) ?? 0;
675
+ const cached = u64(o.cached_input_tokens) ?? u64(o.cache_read_input_tokens) ?? u64(o.cached_tokens) ?? 0;
676
+ const output = u64(o.output_tokens) ?? u64(o.completion_tokens) ?? u64(o.output) ?? 0;
677
+ const reasoning = u64(o.reasoning_output_tokens) ?? u64(o.reasoning_tokens) ?? 0;
678
+ const sum = input + output + reasoning;
679
+ const totalField = u64(o.total_tokens);
680
+ const total = totalField !== void 0 && (totalField > 0 || sum === 0) ? totalField : sum;
681
+ return { input, cached, output, reasoning, total };
682
+ }
683
+ function subtractUsage(current, previous) {
684
+ return {
685
+ input: Math.max(0, current.input - (previous?.input ?? 0)),
686
+ cached: Math.max(0, current.cached - (previous?.cached ?? 0)),
687
+ output: Math.max(0, current.output - (previous?.output ?? 0)),
688
+ reasoning: Math.max(0, current.reasoning - (previous?.reasoning ?? 0)),
689
+ total: Math.max(0, current.total - (previous?.total ?? 0))
690
+ };
691
+ }
692
+ function nonEmpty(v) {
693
+ if (typeof v !== "string") return void 0;
694
+ const t = v.trim();
695
+ return t.length > 0 ? t : void 0;
696
+ }
697
+ function modelFromParts(v) {
698
+ const o = asDict(v);
699
+ if (!o) return void 0;
700
+ return nonEmpty(o.model) ?? nonEmpty(o.model_name) ?? nonEmpty(asDict(o.metadata)?.model);
701
+ }
702
+ function timestampDate(ts) {
703
+ const date = ts.slice(0, 10);
704
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(date)) return void 0;
705
+ const year = Number(date.slice(0, 4));
706
+ const month = Number(date.slice(5, 7));
707
+ const day = Number(date.slice(8, 10));
708
+ const daysInMonth = month === 2 ? year % 4 === 0 && year % 100 !== 0 || year % 400 === 0 ? 29 : 28 : [1, 3, 5, 7, 8, 10, 12].includes(month) ? 31 : [4, 6, 9, 11].includes(month) ? 30 : void 0;
709
+ if (daysInMonth === void 0 || day < 1 || day > daysInMonth) return void 0;
710
+ return date;
711
+ }
712
+ function autoReviewFallback(model, timestamp) {
713
+ if (model !== CODEX_AUTO_REVIEW_MODEL) return void 0;
714
+ const date = timestampDate(timestamp);
715
+ if (!date) return DEFAULT_MODEL;
716
+ return CODEX_AUTO_REVIEW_FALLBACKS.find((f) => date >= f.releasedOn)?.model ?? DEFAULT_MODEL;
717
+ }
718
+ function resolveModel(parsed, timestamp, state) {
719
+ if (parsed !== void 0) {
720
+ state.current = parsed;
721
+ state.currentIsFallback = false;
722
+ }
723
+ let model = parsed ?? state.current;
724
+ if (model === void 0) {
725
+ state.currentIsFallback = true;
726
+ state.current = DEFAULT_MODEL;
727
+ model = DEFAULT_MODEL;
728
+ }
729
+ return autoReviewFallback(model, timestamp) ?? model;
730
+ }
731
+ function normalizeTimestamp(v) {
732
+ if (typeof v === "string") {
733
+ const t = v.trim();
734
+ if (t.length === 0) return void 0;
735
+ const ms2 = Date.parse(t);
736
+ return Number.isNaN(ms2) ? void 0 : new Date(ms2).toISOString();
737
+ }
738
+ const raw = u64(v);
739
+ if (raw === void 0) return void 0;
740
+ const ms = raw > 1e10 ? raw : raw * 1e3;
741
+ return new Date(ms).toISOString();
742
+ }
743
+ function rawOrNormalizedTimestamp(v) {
744
+ if (typeof v === "string") {
745
+ const t = v.trim();
746
+ if (t.length === 0) return void 0;
747
+ if (timestampDate(t) !== void 0) return t;
748
+ return normalizeTimestamp(t);
749
+ }
750
+ return normalizeTimestamp(v);
751
+ }
752
+ function parseLine(raw) {
753
+ let obj;
754
+ try {
755
+ obj = JSON.parse(raw);
756
+ } catch {
757
+ return void 0;
758
+ }
759
+ const o = asDict(obj);
760
+ if (!o) return void 0;
761
+ return {
762
+ timestamp: typeof o.timestamp === "string" ? o.timestamp : void 0,
763
+ type: typeof o.type === "string" ? o.type : void 0,
764
+ cwd: typeof o.cwd === "string" ? o.cwd : void 0,
765
+ payload: asDict(o.payload),
766
+ obj: o
767
+ };
768
+ }
769
+ function tokenCountInfo(p) {
770
+ return p.type === "event_msg" && p.payload?.type === "token_count" ? asDict(p.payload.info) : void 0;
771
+ }
772
+ function headlessUsage(o) {
773
+ const usage = rawUsage(asDict(o.usage)) ?? rawUsage(asDict(asDict(o.data)?.usage)) ?? rawUsage(asDict(asDict(o.result)?.usage)) ?? rawUsage(asDict(asDict(o.response)?.usage));
774
+ if (!usage) return void 0;
775
+ if (usage.input === 0 && usage.cached === 0 && usage.output === 0 && usage.reasoning === 0 && usage.total === 0) {
776
+ return void 0;
777
+ }
778
+ return usage;
779
+ }
780
+ function headlessModel(o) {
781
+ return modelFromParts(o) ?? modelFromParts(asDict(o.data)) ?? modelFromParts(asDict(o.result)) ?? modelFromParts(asDict(o.response));
782
+ }
783
+ function headlessTimestamp(o, pick) {
784
+ const fromFields = (f) => f ? pick(f.timestamp) ?? pick(f.created_at) ?? pick(f.createdAt) : void 0;
785
+ return fromFields(o) ?? fromFields(asDict(o.data)) ?? fromFields(asDict(o.result)) ?? fromFields(asDict(o.response));
786
+ }
686
787
  async function parseCodexRollout(file, seen = /* @__PURE__ */ new Set()) {
687
788
  const stats = { file, usageByModel: {}, toolCalls: {}, toolCostUsd: {}, dailyUsage: {} };
688
789
  const content = await readFile4(file, "utf8").catch(() => "");
689
790
  const lines = [];
690
791
  for (const raw of content.split("\n")) {
691
792
  if (!raw.trim()) continue;
692
- let obj;
693
- try {
694
- obj = JSON.parse(raw);
695
- } catch {
696
- continue;
697
- }
698
- const parsed = Line.safeParse(obj);
699
- if (parsed.success) lines.push(parsed.data);
793
+ const parsed = parseLine(raw);
794
+ if (parsed) lines.push(parsed);
700
795
  }
701
- const burstSecond = replayBurstSecond(content, lines);
702
- let skippingReplay = burstSecond !== void 0;
703
- let model = DEFAULT_MODEL;
704
- let prev = { input: 0, cached: 0, output: 0, total: 0 };
796
+ const modelState = { currentIsFallback: false };
797
+ let prev;
705
798
  let turnTools = [];
706
- for (const parsed of lines) {
707
- const { timestamp, type, payload: payload2 } = parsed;
708
- if (!stats.cwd) stats.cwd = payload2?.cwd ?? parsed.cwd;
709
- if (payload2?.model) model = payload2.model;
710
- if (timestamp) {
711
- if (!stats.firstTs) stats.firstTs = timestamp;
712
- stats.lastTs = timestamp;
713
- }
714
- const toolName = payload2?.type === "function_call" || payload2?.type === "custom_tool_call" ? payload2.name : payload2?.type === "local_shell_call" ? "shell" : type === "function_call" ? parsed.name : void 0;
715
- if (toolName) {
716
- stats.toolCalls[toolName] = (stats.toolCalls[toolName] ?? 0) + 1;
717
- turnTools.push(toolName);
799
+ let mtimeIso;
800
+ const fileMtime = async () => {
801
+ if (mtimeIso === void 0) {
802
+ mtimeIso = await stat2(file).then((s) => s.mtime.toISOString()).catch(() => (/* @__PURE__ */ new Date(0)).toISOString());
718
803
  }
719
- const info = payload2?.type === "token_count" ? payload2.info : void 0;
720
- const last = info?.last_token_usage;
721
- const totals = info?.total_token_usage;
722
- if (!last && !totals) continue;
723
- if (skippingReplay && tsSecond(timestamp) === burstSecond) {
724
- if (totals) {
725
- prev = {
726
- input: totals.input_tokens,
727
- cached: totals.cached_input_tokens,
728
- output: totals.output_tokens,
729
- total: totals.total_tokens
730
- };
731
- }
732
- turnTools = [];
733
- continue;
734
- }
735
- skippingReplay = false;
736
- const idTotals = last ?? totals;
737
- const dedupKey = `${timestamp ?? ""}|${model}|${idTotals.input_tokens}|${idTotals.cached_input_tokens}|${idTotals.output_tokens}|${idTotals.reasoning_output_tokens}|${idTotals.total_tokens}`;
738
- let dInput;
739
- let dCached;
740
- let dOutput;
741
- if (last) {
742
- dInput = last.input_tokens;
743
- dCached = last.cached_input_tokens;
744
- dOutput = last.output_tokens;
745
- if (totals) {
746
- prev = {
747
- input: totals.input_tokens,
748
- cached: totals.cached_input_tokens,
749
- output: totals.output_tokens,
750
- total: totals.total_tokens
751
- };
752
- }
753
- } else {
754
- const cur = {
755
- input: totals.input_tokens,
756
- cached: totals.cached_input_tokens,
757
- output: totals.output_tokens,
758
- total: totals.total_tokens
759
- };
760
- if (cur.total < prev.total) prev = { input: 0, cached: 0, output: 0, total: 0 };
761
- dInput = Math.max(0, cur.input - prev.input);
762
- dCached = Math.max(0, cur.cached - prev.cached);
763
- dOutput = Math.max(0, cur.output - prev.output);
764
- prev = cur;
765
- }
766
- if (dInput + dOutput === 0) continue;
804
+ return mtimeIso;
805
+ };
806
+ const accumulate = (usage, model, timestamp) => {
807
+ const cached = Math.min(usage.cached, usage.input);
808
+ const dedupKey = `${timestamp ?? ""}|${model}|${usage.input}|${cached}|${usage.output}|${usage.reasoning}|${usage.total}`;
767
809
  if (seen.has(dedupKey)) {
768
810
  turnTools = [];
769
- continue;
811
+ return;
770
812
  }
771
813
  seen.add(dedupKey);
772
814
  const delta = {
773
815
  // Codex's input_tokens INCLUDES cached tokens; split them out.
774
- inputTokens: Math.max(0, dInput - dCached),
775
- cacheReadTokens: dCached,
816
+ inputTokens: Math.max(0, usage.input - cached),
817
+ cacheReadTokens: cached,
776
818
  cacheCreationTokens: 0,
777
- outputTokens: dOutput,
819
+ outputTokens: usage.output,
778
820
  turns: 1
779
821
  };
780
822
  const accs = [stats.usageByModel[model] ??= emptyUsage()];
@@ -796,15 +838,78 @@ async function parseCodexRollout(file, seen = /* @__PURE__ */ new Set()) {
796
838
  }
797
839
  }
798
840
  turnTools = [];
841
+ };
842
+ for (const parsed of lines) {
843
+ const { timestamp, type, payload: payload2 } = parsed;
844
+ if (!stats.cwd) {
845
+ const payloadCwd = typeof payload2?.cwd === "string" ? payload2.cwd : void 0;
846
+ stats.cwd = payloadCwd ?? parsed.cwd;
847
+ }
848
+ if (timestamp) {
849
+ if (!stats.firstTs) stats.firstTs = timestamp;
850
+ stats.lastTs = timestamp;
851
+ }
852
+ const toolName = payload2?.type === "function_call" || payload2?.type === "custom_tool_call" ? nonEmpty(payload2.name) : payload2?.type === "local_shell_call" ? "shell" : type === "function_call" ? nonEmpty(parsed.obj.name) : void 0;
853
+ if (toolName) {
854
+ stats.toolCalls[toolName] = (stats.toolCalls[toolName] ?? 0) + 1;
855
+ turnTools.push(toolName);
856
+ }
857
+ if (type === "turn_context") {
858
+ const model2 = modelFromParts(payload2);
859
+ if (model2 !== void 0) {
860
+ modelState.current = model2;
861
+ modelState.currentIsFallback = false;
862
+ }
863
+ continue;
864
+ }
865
+ const info = tokenCountInfo(parsed);
866
+ if (info) {
867
+ const totals = rawUsage(info.total_token_usage);
868
+ const last = rawUsage(info.last_token_usage);
869
+ if (!last && !totals) continue;
870
+ const usage2 = last ?? subtractUsage(totals, prev);
871
+ if (totals) prev = totals;
872
+ if (usage2.input === 0 && usage2.cached === 0 && usage2.output === 0 && usage2.reasoning === 0) {
873
+ continue;
874
+ }
875
+ const ts = nonEmpty(timestamp) ?? normalizeTimestamp(parsed.obj.timestamp);
876
+ const parsedModel = modelFromParts(payload2) ?? modelFromParts(info);
877
+ const model2 = resolveModel(parsedModel, ts ?? "", modelState);
878
+ accumulate(usage2, model2, ts);
879
+ continue;
880
+ }
881
+ if (type === "event_msg" || payload2?.type === "turn_context") continue;
882
+ const usage = headlessUsage(parsed.obj);
883
+ if (!usage) continue;
884
+ const eventTs = headlessTimestamp(parsed.obj, normalizeTimestamp) ?? await fileMtime();
885
+ const modelTs = headlessTimestamp(parsed.obj, rawOrNormalizedTimestamp) ?? await fileMtime();
886
+ const model = resolveModel(headlessModel(parsed.obj), modelTs, modelState);
887
+ accumulate(usage, model, eventTs);
799
888
  }
800
889
  return stats;
801
890
  }
802
891
  async function loadCodexProjects(home, onProgress) {
803
- const root = codexHome(home);
804
- const files = await glob4(["sessions/**/*.jsonl", "archived_sessions/**/*.jsonl"], {
805
- cwd: root,
806
- absolute: true
807
- }).catch(() => []);
892
+ const files = [];
893
+ for (const root of codexHomes(home)) {
894
+ const sources = [];
895
+ for (const dir of ["sessions", "archived_sessions"]) {
896
+ const full = join6(root, dir);
897
+ const ok = await access5(full).then(() => true).catch(() => false);
898
+ if (ok) sources.push(full);
899
+ }
900
+ if (sources.length === 0) sources.push(root);
901
+ const seenRelative = /* @__PURE__ */ new Set();
902
+ for (const source of sources) {
903
+ const found = await glob4(["**/*.jsonl"], { cwd: source, absolute: false }).catch(() => []);
904
+ found.sort();
905
+ for (const rel of found) {
906
+ const key = rel.replaceAll("\\", "/");
907
+ if (seenRelative.has(key)) continue;
908
+ seenRelative.add(key);
909
+ files.push(join6(source, rel));
910
+ }
911
+ }
912
+ }
808
913
  if (files.length === 0) return [];
809
914
  const sessions = [];
810
915
  const seen = /* @__PURE__ */ new Set();
@@ -822,12 +927,11 @@ var codexAdapter = {
822
927
  name: "OpenAI Codex",
823
928
  supported: true,
824
929
  async detect(home) {
825
- try {
826
- await access5(join6(codexHome(home), "sessions"));
827
- return true;
828
- } catch {
829
- return false;
930
+ for (const root of codexHomes(home)) {
931
+ const ok = await access5(join6(root, "sessions")).then(() => true).catch(() => false);
932
+ if (ok) return true;
830
933
  }
934
+ return false;
831
935
  },
832
936
  loadProjects: loadCodexProjects
833
937
  };
@@ -933,7 +1037,7 @@ var copilotAdapter = {
933
1037
  };
934
1038
 
935
1039
  // src/agents/droid.ts
936
- import { access as access7, stat as stat2 } from "fs/promises";
1040
+ import { access as access7, stat as stat3 } from "fs/promises";
937
1041
  import { homedir as homedir8 } from "os";
938
1042
  import { basename as basename2, join as join8 } from "path";
939
1043
  import { glob as glob6 } from "tinyglobby";
@@ -947,7 +1051,7 @@ async function parseFile2(file) {
947
1051
  let ts = str(settings, "updatedAt") ?? str(settings, "createdAt");
948
1052
  if (!ts) {
949
1053
  try {
950
- ts = (await stat2(file)).mtime.toISOString();
1054
+ ts = (await stat3(file)).mtime.toISOString();
951
1055
  } catch {
952
1056
  }
953
1057
  }
@@ -1535,27 +1639,27 @@ import { access as access14, readFile as readFile6 } from "fs/promises";
1535
1639
  import { homedir as homedir15 } from "os";
1536
1640
  import { join as join15 } from "path";
1537
1641
  import { glob as glob10 } from "tinyglobby";
1538
- import { z as z2 } from "zod";
1539
- var Message = z2.object({
1540
- sessionID: z2.string(),
1541
- role: z2.string(),
1542
- modelID: z2.string().optional(),
1543
- providerID: z2.string().optional(),
1544
- time: z2.object({ created: z2.number().optional(), completed: z2.number().optional() }).optional(),
1545
- tokens: z2.object({
1546
- input: z2.number().catch(0).default(0),
1547
- output: z2.number().catch(0).default(0),
1548
- reasoning: z2.number().catch(0).default(0),
1549
- cache: z2.object({ read: z2.number().catch(0).default(0), write: z2.number().catch(0).default(0) }).optional()
1642
+ import { z } from "zod";
1643
+ var Message = z.object({
1644
+ sessionID: z.string(),
1645
+ role: z.string(),
1646
+ modelID: z.string().optional(),
1647
+ providerID: z.string().optional(),
1648
+ time: z.object({ created: z.number().optional(), completed: z.number().optional() }).optional(),
1649
+ tokens: z.object({
1650
+ input: z.number().catch(0).default(0),
1651
+ output: z.number().catch(0).default(0),
1652
+ reasoning: z.number().catch(0).default(0),
1653
+ cache: z.object({ read: z.number().catch(0).default(0), write: z.number().catch(0).default(0) }).optional()
1550
1654
  }).optional()
1551
1655
  });
1552
- var Session = z2.object({
1553
- id: z2.string(),
1554
- directory: z2.string().optional(),
1555
- projectID: z2.string().optional(),
1556
- time: z2.object({ created: z2.number().optional() }).optional()
1656
+ var Session = z.object({
1657
+ id: z.string(),
1658
+ directory: z.string().optional(),
1659
+ projectID: z.string().optional(),
1660
+ time: z.object({ created: z.number().optional() }).optional()
1557
1661
  });
1558
- var Project = z2.object({ id: z2.string(), worktree: z2.string().optional() });
1662
+ var Project = z.object({ id: z.string(), worktree: z.string().optional() });
1559
1663
  function opencodeRoot(home) {
1560
1664
  if (home) return join15(home, ".local", "share", "opencode");
1561
1665
  return process.env.OPENCODE_DATA_DIR ?? join15(homedir15(), ".local", "share", "opencode");
package/dist/cli.js CHANGED
@@ -276,7 +276,7 @@ program.action(async () => {
276
276
  const [{ render }, React, { Root }, { Fullscreen }] = await Promise.all([
277
277
  import("ink"),
278
278
  import("react"),
279
- import("./Root-FWMJ2TO2.js"),
279
+ import("./Root-ZXWUSTBH.js"),
280
280
  import("./Fullscreen-GK2ZYXHV.js")
281
281
  ]);
282
282
  render(React.createElement(Fullscreen, null, React.createElement(Root)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokz/cli",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "See where your coding agents' tokens and dollars actually go.",
5
5
  "keywords": [
6
6
  "claude",