claude-token-saver 2.1.0 → 2.5.0

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 CHANGED
@@ -23,7 +23,7 @@ v1.5 adds three things on top of the original `claude-cache-monitor`:
23
23
 
24
24
  The original functionality still works: cache hit rate, TTL breakdown, cost impact vs. no-cache, TTL countdown timer, and Claude Code statusline integration.
25
25
 
26
- **Runs standalone or as a plugin under another statusline.** Use `npx claude-token-saver` as a one-shot report, wire it into Claude Code's native statusline as the sole segment, or drop it in as an appended segment under a richer statusline like [rz1989s/claude-code-statusline](https://github.com/rz1989s/claude-code-statusline). See [Three Ways to Use It](#three-ways-to-use-it).
26
+ **Run it standalone or wire it into Claude Code's statusline.** Use `npx claude-token-saver` as a one-shot report, or wire it into Claude Code's native statusline for an always-on chip. See [Two Ways to Use It](#two-ways-to-use-it).
27
27
 
28
28
  ---
29
29
 
@@ -36,7 +36,7 @@ v1.5 신규:
36
36
 
37
37
  기존 기능(캐시 히트율·TTL 분포·비용 절감·TTL 카운트다운·statusline)은 그대로 유지됩니다.
38
38
 
39
- **단독 도구로도, 다른 statusline 플러그인으로도 동작합니다.** `npx claude-token-saver` 한 줄로 진단 리포트만 보는 것도 가능하고, Claude Code 내장 statusline에 직접 연결해 상시 표시할 수도 있고, [rz1989s/claude-code-statusline](https://github.com/rz1989s/claude-code-statusline) 같은 기존 statusline 아래에 세그먼트로 덧붙일 수도 있습니다. 자세한 용법은 [Three Ways to Use It](#three-ways-to-use-it) 참고.
39
+ **단독 도구로도, Claude Code statusline 통합으로도 동작합니다.** `npx claude-token-saver` 한 줄로 진단 리포트만 보거나, 내장 statusline에 연결해 상시 표시할 있습니다. 자세한 용법은 [Two Ways to Use It](#two-ways-to-use-it) 참고.
40
40
 
41
41
  ## Quick Start
42
42
 
@@ -99,15 +99,12 @@ Issue codes detected:
99
99
 
100
100
  Remediation commands are chosen from `process.platform` — macOS/Linux/WSL get `~/.zshrc` snippets, Windows gets `setx` and the PowerShell equivalent.
101
101
 
102
- ## Three Ways to Use It
103
-
104
- `claude-token-saver` is primarily a **standalone tool**; the plugin mode is just a convenience for users who already run another statusline.
102
+ ## Two Ways to Use It
105
103
 
106
104
  | Mode | What you run | When to pick this |
107
105
  |---|---|---|
108
106
  | **1. Standalone CLI report** | `npx claude-token-saver` | One-off diagnosis. Prints the full report (spikes + cache + cost + trend). Zero setup. |
109
- | **2. Standalone Claude Code statusline** | `claude-token-saver --statusline` wired via `~/.claude/settings.json` | You want the chip (hit rate · TTL countdown · Ctx 200k/1M · spike) visible all the time. The default story. |
110
- | **3. Plugin under another statusline** | `examples/statusline-with-rz1989s.sh` appends our segment to rz1989s or any wrapper script | You already have a rich statusline (repo info, cost, MCP, prayer times, themes) and want to bolt the token-saver segment on the end. |
107
+ | **2. Claude Code statusline** | `claude-token-saver --statusline` wired via `~/.claude/settings.json` | You want the chip (hit rate · TTL countdown · Ctx 200k/1M · spike) visible all the time. |
111
108
 
112
109
  Detail for each mode below.
113
110
 
@@ -193,22 +190,6 @@ Works best in **Windows Terminal** or **PowerShell 7+** (ANSI color + emoji). Cl
193
190
 
194
191
  Same as Linux — install the package in your WSL Node.js and point to the POSIX sh script.
195
192
 
196
- #### Combine with rz1989s/claude-code-statusline
197
-
198
- If you already use [rz1989s/claude-code-statusline](https://github.com/rz1989s/claude-code-statusline) for its rich layout (repo, cost, MCP, prayer times, themes), drop in [`examples/statusline-with-rz1989s.sh`](examples/statusline-with-rz1989s.sh) to append our cache segment at the end — no conflict, no feature overlap.
199
-
200
- ```json
201
- {
202
- "statusLine": {
203
- "type": "command",
204
- "command": "bash ~/.claude/statusline-with-rz1989s.sh",
205
- "refreshInterval": 1
206
- }
207
- }
208
- ```
209
-
210
- ---
211
-
212
193
  Claude Code calls this every ~300ms on events, plus once per `refreshInterval` second while idle. Colors are emitted when the terminal supports them:
213
194
 
214
195
  - **Hit rate** — 🟢 ≥85% · 🟡 70–85% · 🔴 <70%
@@ -261,6 +242,52 @@ Storage paths (cross-platform):
261
242
 
262
243
  Each day's file is plain Markdown — open it in any editor. Transitions are deduped, so the 1Hz statusline refresh doesn't spam.
263
244
 
245
+ ## Model + /usage segments (new in v2.3, generic in v2.4)
246
+
247
+ Three more segments mirror the data Claude Code's `/usage` slash command shows, so you don't have to slash for it every few minutes:
248
+
249
+ | Segment | Icon mode example | Source |
250
+ | ----------- | --------------------- | ------------------------------- |
251
+ | `model` | `🤖 Opus 4.7` | stdin `model.display_name` |
252
+ | `five_hour` | `✦ current ███▒░░ 47% 🔄 21:10` | stdin `rate_limits.five_hour` |
253
+ | `seven_day` | `📅 weekly ▒░░░░░ 9% 🔄 Thu 13:00` | stdin `rate_limits.seven_day` |
254
+
255
+ The window segments stay quiet under 70% (calm emerald), warm to amber at 70–89%, and yield to the leading `🚨 5H █████▓ 94%` / `🚨 7D █████▒ 92%` cap-warn chip at 90%+ — so you never see the same window twice. The gauge is 6 cells wide using a single density family (`█▓▒░`), so the fill→empty boundary reads as one smooth gradient instead of an awkward step between fractional and shaded glyphs. Colors render as a Tailwind-inspired muted palette (emerald-400 / amber-400 / rose-400) on truecolor terminals (`COLORTERM=truecolor`), with a graceful fallback to 8-color ANSI elsewhere. Filter the layout with `--segments=` if you only want a subset:
256
+
257
+ ```bash
258
+ claude-token-saver --statusline --icon --segments=model,five_hour,seven_day,saved
259
+ ```
260
+
261
+ `5h` and `7d` are kept as aliases for the older config files. Any new `rate_limits.*` window Anthropic ships (e.g. a Sonnet-only weekly bucket) renders automatically with a derived label — no config or version bump needed. As of 2026-04-25 the stdin contract exposes only `five_hour` + `seven_day`; the third row in `/usage` ("Current week — Sonnet only") is not in the payload yet, so we mirror what's there.
262
+
263
+ ## Cap-warn + handoff (new in v2.2)
264
+
265
+ Claude Code's statusline payload now includes rate-limit usage (`rate_limits.five_hour.used_percentage`, `rate_limits.seven_day.used_percentage`). claude-token-saver leads the statusline with a `🚨 5H 94%` (or `🚨 7D 92%`) chip the moment either window crosses **90%**, and writes the transition into history:
266
+
267
+ ```
268
+ - 14:32:08 🚨 5H 94% cap warning (resets in 1h 38m)
269
+ - 16:10:21 ✓ 5H cap warning resolved
270
+ ```
271
+
272
+ When you see the chip, back up the work in flight before the cap blocks you:
273
+
274
+ ```bash
275
+ claude-token-saver handoff
276
+ ```
277
+
278
+ That writes `./HANDOFF-YYYY-MM-DD-HHMM.md` in the current directory with:
279
+
280
+ - timestamp, cwd, git branch / HEAD / dirty file list
281
+ - the 5h/7d cap snapshot (and "resets in Hh Mm")
282
+ - empty fillable sections for *what I just did*, *TODO*, *where to pick up next*, *gotchas*
283
+ - a one-line resume prompt for a fresh Claude Code session:
284
+
285
+ ```
286
+ Read the most recent HANDOFF-*.md in this directory and continue the work.
287
+ ```
288
+
289
+ The handoff write is also recorded in history (`📝 handoff written: …`), so `/token-monitor` and `claude-token-saver history` show both the cap-warn and the backup event next to each other.
290
+
264
291
  ## Hook Setup
265
292
 
266
293
  Automatically logs cache stats on every tool call and alerts when hit rate drops below a threshold.
package/bin/cli.js CHANGED
@@ -26,6 +26,66 @@ import { readFileSync } from 'node:fs';
26
26
  import { fileURLToPath } from 'node:url';
27
27
  import { dirname, join } from 'node:path';
28
28
 
29
+ /**
30
+ * Read the JSON blob Claude Code feeds the statusline command on stdin.
31
+ * Returns null when stdin is a TTY or empty (e.g. user invokes `--statusline`
32
+ * by hand) so callers can fall back to flag/env config.
33
+ *
34
+ * The blob shape (subset we consume):
35
+ * {
36
+ * "transcript_path": "...",
37
+ * "rate_limits": {
38
+ * "five_hour": { "used_percentage": 94, "resets_at": 1777099200 },
39
+ * "seven_day": { "used_percentage": 7, "resets_at": 1777521600 }
40
+ * }
41
+ * }
42
+ *
43
+ * extractCaps treats `rate_limits` as a generic object so any future window
44
+ * Anthropic adds (e.g. a Sonnet-only weekly bucket) flows through without code
45
+ * changes — known keys get curated labels, unknowns get derived ones.
46
+ */
47
+ function readStdinJson() {
48
+ if (process.stdin.isTTY) return null;
49
+ try {
50
+ const raw = readFileSync(0, 'utf8');
51
+ if (!raw || !raw.trim()) return null;
52
+ return JSON.parse(raw);
53
+ } catch {
54
+ return null;
55
+ }
56
+ }
57
+
58
+ function extractCaps(stdinJson) {
59
+ if (!stdinJson || !stdinJson.rate_limits || typeof stdinJson.rate_limits !== 'object') return null;
60
+ const windows = [];
61
+ for (const [key, value] of Object.entries(stdinJson.rate_limits)) {
62
+ if (!value || typeof value !== 'object') continue;
63
+ const usedPct = Number(value.used_percentage);
64
+ if (!Number.isFinite(usedPct)) continue;
65
+ const resetsAt = Number(value.resets_at);
66
+ windows.push({
67
+ key,
68
+ usedPct,
69
+ resetsAt: Number.isFinite(resetsAt) ? resetsAt : null,
70
+ });
71
+ }
72
+ return windows.length ? { windows } : null;
73
+ }
74
+
75
+ /**
76
+ * Pull the human-friendly model name out of Claude Code's stdin payload.
77
+ * `model.display_name` is the contract; fall back to `model.id` when it's
78
+ * absent. Returns null when nothing usable is in the JSON.
79
+ */
80
+ function extractModel(stdinJson) {
81
+ if (!stdinJson || !stdinJson.model) return null;
82
+ const m = stdinJson.model;
83
+ if (typeof m === 'string') return m;
84
+ if (typeof m.display_name === 'string' && m.display_name) return m.display_name;
85
+ if (typeof m.id === 'string' && m.id) return m.id;
86
+ return null;
87
+ }
88
+
29
89
  import { parseAllSessions, getLastUserMessageTime } from '../src/parser.js';
30
90
  import {
31
91
  dailyTrend,
@@ -53,15 +113,162 @@ const PKG_VERSION = (() => {
53
113
 
54
114
  function getArg(name) {
55
115
  const idx = args.indexOf(name);
56
- if (idx === -1) return undefined;
57
- return args[idx + 1];
116
+ if (idx !== -1) return args[idx + 1];
117
+ const prefix = `${name}=`;
118
+ const eq = args.find((a) => a.startsWith(prefix));
119
+ if (eq) return eq.slice(prefix.length);
120
+ return undefined;
58
121
  }
59
122
 
60
123
  function hasFlag(name) {
61
124
  return args.includes(name);
62
125
  }
63
126
 
127
+ /**
128
+ * Scan recent history file contents (newest day first) and return the most
129
+ * recent warning event — `{ time, chip, detail, codes, isCap, capLabel, capPct }`.
130
+ * Returns null when no warning is found in the window.
131
+ *
132
+ * Recognized event lines (from history.js appendDayLine output):
133
+ * - HH:MM:SS ⚠ Cache miss — session abc1: LOW_HIT_RATE
134
+ * - HH:MM:SS ⚠ A → ⚠ B — detail
135
+ * - HH:MM:SS 🚨 5H 94% cap warning (resets in ...)
136
+ * - HH:MM:SS ✓ resolved (was ...) ← skip
137
+ * - HH:MM:SS ✓ 5H cap warning resolved ← skip
138
+ * - HH:MM:SS 📝 handoff written: ... ← skip
139
+ */
140
+ function findLatestWarning(historyEntries, chipToCodes) {
141
+ const warnings = [];
142
+ for (const { date, content } of historyEntries) {
143
+ const lines = content.split('\n');
144
+ for (const line of lines) {
145
+ // Skip non-event lines
146
+ const m = line.match(/^- (\d{2}:\d{2}:\d{2})\s+(.+)$/);
147
+ if (!m) continue;
148
+ const time = m[1];
149
+ const rest = m[2];
150
+ // Skip resolutions and handoff entries
151
+ if (rest.startsWith('✓ ') || rest.startsWith('📝 ')) continue;
152
+ // Cap-warn line: `🚨 5H 94% cap warning (...)`
153
+ const cap = rest.match(/^🚨\s+(\S+)\s+(\d+)%\s+cap warning(?:\s*\((.+)\))?$/);
154
+ if (cap) {
155
+ warnings.push({
156
+ date,
157
+ time,
158
+ chip: `🚨 ${cap[1]} ${cap[2]}%`,
159
+ isCap: true,
160
+ capLabel: cap[1],
161
+ capPct: parseInt(cap[2], 10),
162
+ capReset: cap[3] || null,
163
+ codes: [],
164
+ detail: null,
165
+ });
166
+ continue;
167
+ }
168
+ // Chip line — last token after the chip is `— detail` (optional). The
169
+ // chip itself can be a plain `⚠ X` or a `⚠ A → ⚠ B` transition; we want
170
+ // the *current* chip (right side of the arrow if present).
171
+ const arrowMatch = rest.match(/^(.+?)\s+→\s+(.+?)(?:\s+—\s+(.+))?$/);
172
+ let chip;
173
+ let detail = null;
174
+ if (arrowMatch) {
175
+ chip = arrowMatch[2].trim();
176
+ detail = arrowMatch[3] || null;
177
+ } else {
178
+ const plain = rest.match(/^(\S+(?:\s+\S+)*?)(?:\s+—\s+(.+))?$/);
179
+ if (!plain) continue;
180
+ chip = plain[1].trim();
181
+ detail = plain[2] || null;
182
+ }
183
+ // Resolve codes: detail "session ID: A, B" → codes; else CHIP_TO_CODES.
184
+ const codes = [];
185
+ if (detail) {
186
+ const dm = detail.match(/^session [^:]+:\s*(.+)$/);
187
+ if (dm) {
188
+ for (const c of dm[1].split(',').map((s) => s.trim()).filter(Boolean)) {
189
+ if (!codes.includes(c)) codes.push(c);
190
+ }
191
+ }
192
+ }
193
+ if (chipToCodes[chip]) {
194
+ for (const c of chipToCodes[chip]) if (!codes.includes(c)) codes.push(c);
195
+ }
196
+ warnings.push({ date, time, chip, isCap: false, codes, detail });
197
+ }
198
+ }
199
+ return warnings.length ? warnings[warnings.length - 1] : null;
200
+ }
201
+
64
202
  async function main() {
203
+ // Subcommand: last — print the most recent warning + how to handle it.
204
+ // Designed for the /token-monitor slash command and the auto-skill so the
205
+ // user immediately sees "what just fired and how to fix it" without having
206
+ // to read the whole history file.
207
+ // claude-token-saver last # search last 1 day
208
+ // claude-token-saver last --days 7 # widen the lookback
209
+ if (args[0] === 'last') {
210
+ const { readRecent, historyDir } = await import('../src/history.js');
211
+ const { ISSUE_MESSAGES, CHIP_TO_CODES, CAP_TIPS } = await import('../src/advice.js');
212
+ const days = parseFloat(getArg('--days') || '1');
213
+ const recent = readRecent(days);
214
+ const latest = findLatestWarning(recent, CHIP_TO_CODES);
215
+ if (!latest) {
216
+ console.log(`No warnings in the last ${days} day${days === 1 ? '' : 's'}.`);
217
+ console.log(`(History dir: ${historyDir()})`);
218
+ return;
219
+ }
220
+ // Header
221
+ console.log(`Most recent warning — ${latest.date} ${latest.time}`);
222
+ console.log(` ${latest.chip}${latest.detail ? ` — ${latest.detail}` : ''}`);
223
+ console.log('');
224
+ // Cap-warn path: handoff is the recommendation. Print the bilingual tip
225
+ // and a one-line "how to back up" pointer.
226
+ if (latest.isCap) {
227
+ if (latest.capReset) console.log(` Cap window: ${latest.capReset}`);
228
+ console.log('');
229
+ console.log('💡 ' + CAP_TIPS.en);
230
+ console.log('💡 ' + CAP_TIPS.ko);
231
+ console.log('');
232
+ console.log('Run:');
233
+ console.log(' claude-token-saver handoff');
234
+ return;
235
+ }
236
+ // Chip warning path: render full ISSUE_MESSAGES advice for each code,
237
+ // bilingual (English first, `└ Korean` continuation per line — matches
238
+ // the history.md format).
239
+ if (latest.codes.length === 0) {
240
+ console.log('(No diagnostic code attached — open the table view: `claude-token-saver --days 1`)');
241
+ console.log('(진단 코드 없음 — 표 뷰를 열어보세요: `claude-token-saver --days 1`)');
242
+ return;
243
+ }
244
+ for (const code of latest.codes) {
245
+ const msg = ISSUE_MESSAGES[code];
246
+ if (!msg) {
247
+ console.log(`Code: ${code} (no advice registered)`);
248
+ continue;
249
+ }
250
+ console.log(`▎ ${msg.title}`);
251
+ if (msg.titleKo && msg.titleKo !== msg.title) console.log(` └ ${msg.titleKo}`);
252
+ console.log(` ${msg.explain}`);
253
+ if (msg.explainKo && msg.explainKo !== msg.explain) console.log(` └ ${msg.explainKo}`);
254
+ const actions = typeof msg.actions === 'function' ? msg.actions() : msg.actions || [];
255
+ for (const a of actions) {
256
+ console.log('');
257
+ console.log(` ${a.label}:`);
258
+ if (a.labelKo && a.labelKo !== a.label) console.log(` └ ${a.labelKo}:`);
259
+ const cmds = a.commands || [];
260
+ const cmdsKo = a.commandsKo || [];
261
+ for (let i = 0; i < cmds.length; i++) {
262
+ console.log(` - ${cmds[i]}`);
263
+ const ko = cmdsKo[i];
264
+ if (ko && ko !== cmds[i]) console.log(` └ ${ko}`);
265
+ }
266
+ }
267
+ console.log('');
268
+ }
269
+ return;
270
+ }
271
+
65
272
  // Subcommand: history — print recent warning transitions captured by the
66
273
  // statusline. One markdown file per day, persisted under the platform-
67
274
  // specific user-data dir.
@@ -94,6 +301,34 @@ async function main() {
94
301
  return;
95
302
  }
96
303
 
304
+ // Subcommand: handoff — write a HANDOFF-YYYY-MM-DD-HHMM.md template in cwd
305
+ // capturing git status + the latest cap snapshot, so a fresh Claude Code
306
+ // session can pick up where this one stopped. Pairs with the cap-warn chip:
307
+ // when statusline shows 🚨 5H 90%+, run this to back up state before the cap
308
+ // hits.
309
+ // claude-token-saver handoff # write to cwd
310
+ // claude-token-saver handoff --cwd PATH # custom directory
311
+ if (args[0] === 'handoff') {
312
+ const { writeHandoff } = await import('../src/handoff.js');
313
+ const { recordHandoff } = await import('../src/history.js');
314
+ const cwd = getArg('--cwd') || process.cwd();
315
+ // Cap data only flows in via stdin (Claude Code statusline contract).
316
+ // Direct CLI invocations won't have it — that's fine, the template will
317
+ // note the gap.
318
+ const stdinJson = readStdinJson();
319
+ const caps = extractCaps(stdinJson);
320
+ const { path, git } = writeHandoff({ cwd, caps });
321
+ try { recordHandoff(path); } catch { /* non-critical */ }
322
+ console.log(`Handoff written: ${path}`);
323
+ if (git) {
324
+ console.log(` git: ${git.branch}${git.head ? ` @ ${git.head}` : ''}${git.status ? ' (dirty)' : ' (clean)'}`);
325
+ }
326
+ console.log('');
327
+ console.log('Fill in the empty sections, then start a new Claude Code session with:');
328
+ console.log(' Read the most recent HANDOFF-*.md in this directory and continue the work.');
329
+ return;
330
+ }
331
+
97
332
  // Subcommand: install — write the Claude Code Skill and slash command so
98
333
  // /token-monitor and the auto-trigger skill become available without any
99
334
  // manual file editing. Cross-platform (uses node:path + node:fs).
@@ -234,11 +469,16 @@ async function main() {
234
469
  : (hasFlag('--no-verbose') || hasFlag('--compact') ? false : cfg.verbose);
235
470
  const showTimer = hasFlag('--no-timer') ? false : cfg.timer;
236
471
  const colorOk = !hasFlag('--no-color') && !process.env.NO_COLOR && cfg.color;
472
+ const segmentsArg = getArg('--segments');
473
+ const segments = segmentsArg
474
+ ? segmentsArg.split(',').map((s) => s.trim()).filter(Boolean)
475
+ : null;
237
476
  const out = formatReport(data, {
238
477
  color: colorOk,
239
478
  verbose: isVerbose,
240
479
  timer: showTimer,
241
480
  mode: isIcon ? 'icon' : 'text',
481
+ segments,
242
482
  });
243
483
  // For `cycle` mode, prefix with the scenario label so the screen recorder
244
484
  // shows what the viewer is looking at (only when explicitly requested).
@@ -318,6 +558,36 @@ async function main() {
318
558
  const spikeReport = detectSpikes(sessions, { recentHours: 24, multiplier: 3 });
319
559
  const contextWindow = detectContextWindow(sessions, { recentHours: 24 });
320
560
 
561
+ // Claude Code feeds the statusline command a JSON blob on stdin every
562
+ // refresh. Pull rate_limits + model out of it so we can surface cap-warn
563
+ // (>=90%) chips, always-on usage segments, the model chip, record cap
564
+ // transitions, and seed the table view's warning box. The table path falls
565
+ // back to the most-recent cached snapshot so the /token-monitor slash
566
+ // command (which doesn't pipe stdin) still has the data.
567
+ const stdinJson = readStdinJson();
568
+ let caps = extractCaps(stdinJson);
569
+ let model = extractModel(stdinJson);
570
+ if (isStatusline && (caps || model)) {
571
+ try {
572
+ const { persistSnapshot } = await import('../src/caps-cache.js');
573
+ persistSnapshot({ caps, model });
574
+ } catch {
575
+ // non-critical
576
+ }
577
+ }
578
+ if (!isStatusline && (!caps || !model)) {
579
+ try {
580
+ const { loadRecentSnapshot } = await import('../src/caps-cache.js');
581
+ const snap = loadRecentSnapshot();
582
+ if (snap) {
583
+ if (!caps && snap.caps) caps = snap.caps;
584
+ if (!model && snap.model) model = snap.model;
585
+ }
586
+ } catch {
587
+ // ignore
588
+ }
589
+ }
590
+
321
591
  // For statusline: attach a single-word chip only when there's something
322
592
  // actionable right now. 1M context is always shown; otherwise only fire
323
593
  // if the most recent session actually appears in the spike list.
@@ -348,8 +618,13 @@ async function main() {
348
618
  // Persist transitions to ~/.config/claude-token-saver/history/YYYY-MM-DD.md
349
619
  // so /token-monitor and `claude-token-saver history` can replay them.
350
620
  try {
351
- const { recordChip } = await import('../src/history.js');
621
+ const { recordChip, recordCapTransition } = await import('../src/history.js');
352
622
  recordChip(spikeChip, { detail: chipDetail });
623
+ // Cap-warn transitions are tracked independently per window — a session
624
+ // can hit 90% on the 5h window even when no spike chip is firing.
625
+ if (caps && Array.isArray(caps.windows)) {
626
+ for (const win of caps.windows) recordCapTransition(win);
627
+ }
353
628
  } catch {
354
629
  // history is non-critical — don't let it break the statusline render
355
630
  }
@@ -387,6 +662,8 @@ async function main() {
387
662
  spikeReport,
388
663
  contextWindow,
389
664
  spikeChip,
665
+ caps,
666
+ model,
390
667
  };
391
668
 
392
669
  let output;
@@ -412,11 +689,16 @@ async function main() {
412
689
  const colorOk =
413
690
  !hasFlag('--no-color') && !process.env.NO_COLOR && cfg.color;
414
691
 
692
+ const segmentsArg = getArg('--segments');
693
+ const segments = segmentsArg
694
+ ? segmentsArg.split(',').map((s) => s.trim()).filter(Boolean)
695
+ : null;
415
696
  output = formatReport(data, {
416
697
  color: colorOk,
417
698
  verbose: isVerbose,
418
699
  timer: showTimer,
419
700
  mode: isIcon ? 'icon' : 'text',
701
+ segments,
420
702
  });
421
703
  } else {
422
704
  const { formatReport } = await import('../src/formatters/table.js');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-token-saver",
3
- "version": "2.1.0",
3
+ "version": "2.5.0",
4
4
  "description": "Save tokens on Claude Code — spike diagnosis, 1M-context detection, TTL countdown, statusline. (formerly claude-cache-monitor)",
5
5
  "type": "module",
6
6
  "bin": {