@tokz/cli 0.2.16 → 0.2.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20,7 +20,7 @@ import {
20
20
  sanitizeProjectPath,
21
21
  shortModel,
22
22
  usd
23
- } from "./chunk-KO5GQL76.js";
23
+ } from "./chunk-T5J7LVH6.js";
24
24
 
25
25
  // src/ui/Root.tsx
26
26
  import { useEffect as useEffect2, useState as useState6 } from "react";
@@ -766,9 +766,6 @@ function parseLine(raw) {
766
766
  obj: o
767
767
  };
768
768
  }
769
- function tokenCountInfo(p) {
770
- return p.type === "event_msg" && p.payload?.type === "token_count" ? asDict(p.payload.info) : void 0;
771
- }
772
769
  function headlessUsage(o) {
773
770
  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
771
  if (!usage) return void 0;
@@ -784,6 +781,43 @@ function headlessTimestamp(o, pick) {
784
781
  const fromFields = (f) => f ? pick(f.timestamp) ?? pick(f.created_at) ?? pick(f.createdAt) : void 0;
785
782
  return fromFields(o) ?? fromFields(asDict(o.data)) ?? fromFields(asDict(o.result)) ?? fromFields(asDict(o.response));
786
783
  }
784
+ function detectReplaySecond(content, lines) {
785
+ const encoder = new TextEncoder();
786
+ let byteLen = 0;
787
+ let charLimit = 0;
788
+ for (let i = 0; i < content.length; i++) {
789
+ const code = content.charCodeAt(i);
790
+ if (code <= 127) byteLen += 1;
791
+ else if (code <= 2047) byteLen += 2;
792
+ else if (code >= 55296 && code <= 57343) {
793
+ byteLen += 4;
794
+ i++;
795
+ } else byteLen += 3;
796
+ if (byteLen > 16384) break;
797
+ charLimit = i + 1;
798
+ }
799
+ const head = content.slice(0, charLimit);
800
+ if (!head.includes("thread_spawn") && !head.includes("forked_from_id")) return void 0;
801
+ let firstSecond;
802
+ for (const p of lines) {
803
+ if (p.type !== "event_msg" || p.payload?.type !== "token_count") continue;
804
+ const info = p.payload.info;
805
+ if (!info) continue;
806
+ if (!asDict(info.last_token_usage) && !asDict(info.total_token_usage)) continue;
807
+ const ts = p.timestamp;
808
+ if (!ts || typeof ts !== "string" || ts.length < 19) continue;
809
+ const sec = ts.slice(0, 19);
810
+ if (firstSecond === void 0) {
811
+ firstSecond = sec;
812
+ } else {
813
+ if (firstSecond === sec) {
814
+ return sec;
815
+ }
816
+ return void 0;
817
+ }
818
+ }
819
+ return void 0;
820
+ }
787
821
  async function parseCodexRollout(file, seen = /* @__PURE__ */ new Set()) {
788
822
  const stats = { file, usageByModel: {}, toolCalls: {}, toolCostUsd: {}, dailyUsage: {} };
789
823
  const content = await readFile4(file, "utf8").catch(() => "");
@@ -793,6 +827,8 @@ async function parseCodexRollout(file, seen = /* @__PURE__ */ new Set()) {
793
827
  const parsed = parseLine(raw);
794
828
  if (parsed) lines.push(parsed);
795
829
  }
830
+ const replaySecond = detectReplaySecond(content, lines);
831
+ let skipReplay = replaySecond !== void 0;
796
832
  const modelState = { currentIsFallback: false };
797
833
  let prev;
798
834
  let turnTools = [];
@@ -812,12 +848,14 @@ async function parseCodexRollout(file, seen = /* @__PURE__ */ new Set()) {
812
848
  }
813
849
  seen.add(dedupKey);
814
850
  const delta = {
815
- // Codex's input_tokens INCLUDES cached tokens; split them out.
816
851
  inputTokens: Math.max(0, usage.input - cached),
817
852
  cacheReadTokens: cached,
818
853
  cacheCreationTokens: 0,
819
854
  outputTokens: usage.output,
820
- turns: 1
855
+ turns: 1,
856
+ longContextInputTokens: usage.input > 272e3 ? Math.max(0, usage.input - cached) : 0,
857
+ longContextCacheReadTokens: usage.input > 272e3 ? cached : 0,
858
+ longContextOutputTokens: usage.input > 272e3 ? usage.output : 0
821
859
  };
822
860
  const accs = [stats.usageByModel[model] ??= emptyUsage()];
823
861
  if (timestamp) {
@@ -829,6 +867,9 @@ async function parseCodexRollout(file, seen = /* @__PURE__ */ new Set()) {
829
867
  u.cacheReadTokens += delta.cacheReadTokens;
830
868
  u.outputTokens += delta.outputTokens;
831
869
  u.turns += 1;
870
+ u.longContextInputTokens = (u.longContextInputTokens ?? 0) + (delta.longContextInputTokens ?? 0);
871
+ u.longContextCacheReadTokens = (u.longContextCacheReadTokens ?? 0) + (delta.longContextCacheReadTokens ?? 0);
872
+ u.longContextOutputTokens = (u.longContextOutputTokens ?? 0) + (delta.longContextOutputTokens ?? 0);
832
873
  }
833
874
  const turnCost = costUsd(delta, model).total;
834
875
  if (turnCost > 0 && turnTools.length > 0) {
@@ -862,33 +903,34 @@ async function parseCodexRollout(file, seen = /* @__PURE__ */ new Set()) {
862
903
  }
863
904
  continue;
864
905
  }
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
- let usage2;
871
- if (totals) {
872
- if (prev && totals.total === prev.total && totals.input === prev.input && totals.output === prev.output && totals.cached === prev.cached) {
906
+ if (type === "event_msg" && payload2?.type === "token_count") {
907
+ const info = asDict(payload2.info);
908
+ if (replaySecond !== void 0 && skipReplay) {
909
+ const ts = timestamp ?? parsed.obj.timestamp;
910
+ const matchesReplay = typeof ts === "string" && ts.length >= 19 && ts.slice(0, 19) === replaySecond;
911
+ if (matchesReplay) {
912
+ const totals = rawUsage(info?.total_token_usage);
913
+ if (totals) prev = totals;
914
+ turnTools = [];
873
915
  continue;
874
916
  }
875
- if (prev && totals.total < prev.total) {
876
- usage2 = last ?? totals;
877
- } else {
878
- usage2 = subtractUsage(totals, prev);
879
- }
880
- prev = totals;
881
- } else {
882
- usage2 = last;
917
+ skipReplay = false;
883
918
  }
884
- if (usage2.input === 0 && usage2.cached === 0 && usage2.output === 0 && usage2.reasoning === 0) {
919
+ if (info) {
920
+ const totals = rawUsage(info.total_token_usage);
921
+ const last = rawUsage(info.last_token_usage);
922
+ if (!last && !totals) continue;
923
+ const usage2 = last ?? subtractUsage(totals, prev);
924
+ if (totals) prev = totals;
925
+ if (usage2.input === 0 && usage2.cached === 0 && usage2.output === 0 && usage2.reasoning === 0) {
926
+ continue;
927
+ }
928
+ const ts = nonEmpty(timestamp) ?? normalizeTimestamp(parsed.obj.timestamp);
929
+ const parsedModel = modelFromParts(payload2) ?? modelFromParts(info);
930
+ const model2 = resolveModel(parsedModel, ts ?? "", modelState);
931
+ accumulate(usage2, model2, ts);
885
932
  continue;
886
933
  }
887
- const ts = nonEmpty(timestamp) ?? normalizeTimestamp(parsed.obj.timestamp);
888
- const parsedModel = modelFromParts(payload2) ?? modelFromParts(info);
889
- const model2 = resolveModel(parsedModel, ts ?? "", modelState);
890
- accumulate(usage2, model2, ts);
891
- continue;
892
934
  }
893
935
  if (type === "event_msg" || payload2?.type === "turn_context") continue;
894
936
  const usage = headlessUsage(parsed.obj);
@@ -68,13 +68,25 @@ function costUsd(usage, modelId) {
68
68
  const p = resolvePrice(modelId);
69
69
  const readMult = p.cacheReadMult ?? DEFAULT_CACHE_READ_MULT;
70
70
  const writeMult = p.cacheWriteMult ?? DEFAULT_CACHE_WRITE_MULT;
71
- const input = usage.inputTokens / 1e6 * p.inputPerMTok;
72
- const cacheRead = usage.cacheReadTokens / 1e6 * p.inputPerMTok * readMult;
71
+ const regInputTokens = usage.inputTokens - (usage.longContextInputTokens ?? 0);
72
+ const regCacheReadTokens = usage.cacheReadTokens - (usage.longContextCacheReadTokens ?? 0);
73
+ const regOutputTokens = usage.outputTokens - (usage.longContextOutputTokens ?? 0);
74
+ let input = regInputTokens / 1e6 * p.inputPerMTok;
75
+ let cacheRead = regCacheReadTokens / 1e6 * p.inputPerMTok * readMult;
76
+ let output = regOutputTokens / 1e6 * p.outputPerMTok;
77
+ if (usage.longContextInputTokens) {
78
+ input += usage.longContextInputTokens / 1e6 * (p.inputPerMTok * 2);
79
+ }
80
+ if (usage.longContextCacheReadTokens) {
81
+ cacheRead += usage.longContextCacheReadTokens / 1e6 * (p.inputPerMTok * 2) * readMult;
82
+ }
83
+ if (usage.longContextOutputTokens) {
84
+ output += usage.longContextOutputTokens / 1e6 * (p.outputPerMTok * 2);
85
+ }
73
86
  const write1h = Math.min(usage.cacheCreation1hTokens ?? 0, usage.cacheCreationTokens);
74
87
  const write5m = usage.cacheCreationTokens - write1h;
75
88
  const write1hMult = p.cacheWrite1hMult ?? (p.cacheWriteMult === 0 ? 0 : CACHE_WRITE_1H_MULT);
76
89
  const cacheWrite = write5m / 1e6 * p.inputPerMTok * writeMult + write1h / 1e6 * p.inputPerMTok * write1hMult;
77
- const output = usage.outputTokens / 1e6 * p.outputPerMTok;
78
90
  return { input, cacheRead, cacheWrite, output, total: input + cacheRead + cacheWrite + output };
79
91
  }
80
92
 
@@ -6,7 +6,7 @@ import {
6
6
  emptyUsage,
7
7
  shortModel,
8
8
  usd
9
- } from "./chunk-KO5GQL76.js";
9
+ } from "./chunk-T5J7LVH6.js";
10
10
 
11
11
  // src/blocks.ts
12
12
  var BLOCK_LENGTH_MS = 5 * 60 * 60 * 1e3;
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  buildBlocks,
4
4
  renderBlocksReport
5
- } from "./chunk-VJFLJJGX.js";
5
+ } from "./chunk-VXWHRHQU.js";
6
6
  import {
7
7
  findMcpServers
8
8
  } from "./chunk-65BQE6TI.js";
@@ -17,7 +17,7 @@ import {
17
17
  setTimezone,
18
18
  tok,
19
19
  usd
20
- } from "./chunk-KO5GQL76.js";
20
+ } from "./chunk-T5J7LVH6.js";
21
21
 
22
22
  // src/cli.ts
23
23
  import { createRequire } from "module";
@@ -234,7 +234,7 @@ program.command("blocks").description("Claude usage grouped into rolling 5-hour
234
234
  });
235
235
  program.command("statusline").description("compact usage line for Claude Code's statusLine hook (reads hook JSON on stdin)").argument("[action]", '"enable" writes the hook into ~/.claude/settings.json, "disable" removes it').option("--cost-source <mode>", "session cost source: auto | cc | calc | both", "auto").action(async (action, opts) => {
236
236
  const globals = applyGlobals();
237
- const sl = await import("./statusline-5SJKELOG.js");
237
+ const sl = await import("./statusline-4UN2Q6BT.js");
238
238
  if (action === "enable" || action === "disable") {
239
239
  try {
240
240
  console.log(action === "enable" ? await sl.enableStatusline() : await sl.disableStatusline());
@@ -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-NB26ZIPU.js"),
279
+ import("./Root-IVPGDV7M.js"),
280
280
  import("./Fullscreen-GK2ZYXHV.js")
281
281
  ]);
282
282
  render(React.createElement(Fullscreen, null, React.createElement(Root)));
@@ -3,7 +3,7 @@ import {
3
3
  buildBlocks,
4
4
  burnRate,
5
5
  fmtMs
6
- } from "./chunk-VJFLJJGX.js";
6
+ } from "./chunk-VXWHRHQU.js";
7
7
  import {
8
8
  compact,
9
9
  costUsd,
@@ -12,7 +12,7 @@ import {
12
12
  parseTranscript,
13
13
  shortModel,
14
14
  usd
15
- } from "./chunk-KO5GQL76.js";
15
+ } from "./chunk-T5J7LVH6.js";
16
16
 
17
17
  // src/statusline.ts
18
18
  import { mkdir, readFile, stat, writeFile } from "fs/promises";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokz/cli",
3
- "version": "0.2.16",
3
+ "version": "0.2.18",
4
4
  "description": "See where your coding agents' tokens and dollars actually go.",
5
5
  "keywords": [
6
6
  "claude",