/
/
/
1#!/usr/bin/env node
2"use strict";
3
4const fs = require("fs");
5
6// --- input ---
7const input = readJSON(0); // stdin
8const sessionId = `\x1b[90m${String(input.session_id ?? "")}\x1b[0m`;
9const transcript = input.transcript_path;
10const model = input.model || {};
11const name = `\x1b[95m${String(model.display_name ?? "")}\x1b[0m`.trim();
12const CONTEXT_WINDOW = 200_000;
13
14// --- helpers ---
15function readJSON(fd) {
16 try {
17 return JSON.parse(fs.readFileSync(fd, "utf8"));
18 } catch {
19 return {};
20 }
21}
22function color(p) {
23 if (p >= 90) return "\x1b[31m"; // red
24 if (p >= 70) return "\x1b[33m"; // yellow
25 return "\x1b[32m"; // green
26}
27const comma = (n) =>
28 new Intl.NumberFormat("en-US").format(
29 Math.max(0, Math.floor(Number(n) || 0))
30 );
31
32function usedTotal(u) {
33 return (
34 (u?.input_tokens ?? 0) +
35 (u?.output_tokens ?? 0) +
36 (u?.cache_read_input_tokens ?? 0) +
37 (u?.cache_creation_input_tokens ?? 0)
38 );
39}
40
41function syntheticModel(j) {
42 const m = String(j?.message?.model ?? "").toLowerCase();
43 return m === "<synthetic>" || m.includes("synthetic");
44}
45
46function assistantMessage(j) {
47 return j?.message?.role === "assistant";
48}
49
50function subContext(j) {
51 return j?.isSidechain === true;
52}
53
54function contentNoResponse(j) {
55 const c = j?.message?.content;
56 return (
57 Array.isArray(c) &&
58 c.some(
59 (x) =>
60 x &&
61 x.type === "text" &&
62 /no\s+response\s+requested/i.test(String(x.text))
63 )
64 );
65}
66
67function parseTs(j) {
68 const t = j?.timestamp;
69 const n = Date.parse(t);
70 return Number.isFinite(n) ? n : -Infinity;
71}
72
73// Find the newest main-context entry by timestamp (not file order)
74function newestMainUsageByTimestamp() {
75 if (!transcript) return null;
76 let latestTs = -Infinity;
77 let latestUsage = null;
78
79 let lines;
80 try {
81 lines = fs.readFileSync(transcript, "utf8").split(/\r?\n/);
82 } catch {
83 return null;
84 }
85
86 for (let i = lines.length - 1; i >= 0; i--) {
87 const line = lines[i].trim();
88 if (!line) continue;
89
90 let j;
91 try {
92 j = JSON.parse(line);
93 } catch {
94 continue;
95 }
96 const u = j.message?.usage;
97 if (
98 subContext(j) ||
99 syntheticModel(j) ||
100 j.isApiErrorMessage === true ||
101 usedTotal(u) === 0 ||
102 contentNoResponse(j) ||
103 !assistantMessage(j)
104 )
105 continue;
106
107 const ts = parseTs(j);
108 if (ts > latestTs) {
109 latestTs = ts;
110 latestUsage = u;
111 }
112 else if (ts == latestTs && usedTotal(u) > usedTotal(latestUsage)) {
113 latestUsage = u;
114 }
115 }
116 return latestUsage;
117}
118
119// --- compute/print ---
120const usage = newestMainUsageByTimestamp();
121if (!usage) {
122 console.log(
123 `${name} | \x1b[36mcontext window usage starts after your first question.\x1b[0m\nsession: ${sessionId}`
124 );
125 process.exit(0);
126}
127
128const used = usedTotal(usage);
129const pct = CONTEXT_WINDOW > 0 ? Math.round((used * 1000) / CONTEXT_WINDOW) / 10 : 0;
130
131const usagePercentLabel = `${color(pct)}context used ${pct.toFixed(1)}%\x1b[0m`;
132const usageCountLabel = `\x1b[33m(${comma(used)}/${comma(
133 CONTEXT_WINDOW
134)})\x1b[0m`;
135
136console.log(
137 `${name} | ${usagePercentLabel} - ${usageCountLabel}\nsession: ${sessionId}`
138);