SLOPSHOPPER

cache-tax-plus

Stoppt eine teure Nachricht an einen kalten Prompt-Cache einmal und zeigt vorher den Preis. /keepwarm hält den Cache in Pausen warm (kostet Tokens).

newbandcommandstatuspromptmodel
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · cache-tax-plus
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /keepwarm ⎿ cache-tax-plus: Keepwarm an für 6 h. Nach 50 min Pause hält ein Ping den Cache warm: gelesen statt neu geschrieben. ⟨Claude Code's own drawing⟩ ▣ [>] cache-tax · warm · Keepwarm noch 6 h · Ping in 50 min ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
⟨Claude Code's own drawing⟩ ▣ [>] cache-tax · warm · Keepwarm noch 6 h · Ping in 50 min
README

Cache Tax Plus

Claude Code merkt sich deinen Gesprächsverlauf im Prompt-Cache. Nach einer Stunde Pause ist der Cache kalt, und die nächste Nachricht schreibt den ganzen Kontext neu. Bei großen Sessions kostet das schnell ein paar Dollar, auch für ein "Fass kurz zusammen".

Dieses Mod macht zwei Dinge:

  • Es stoppt die teure Nachricht einmal und zeigt dir vorher den Preis. Schickst du sie nochmal, geht sie durch.
  • /keepwarm hält den Cache in Pausen warm. Nach 50 Minuten ohne Nachricht schickt es einen kleinen Ping, damit der Cache gelesen statt neu geschrieben wird. Jeder Ping kostet Tokens.

So sieht der Stopp aus:

cache-tax: Cache ist kalt. Diese Nachricht kostet ca. $6,61
Nochmal senden = bezahlen (danach hält Keepwarm den Cache 3 h warm). Oder /clear und mit einer Notiz neu starten.
Kalt seit 1 h 12 min. Bis zu 330.316 Tokens werden neu geschrieben zu $20 pro Mio. Tokens, warm wären es ca. $0,08.

Installation

/plugin marketplace add Verselo-Orbit/claude-mods
/plugin install cache-tax-plus@verselo-mods

Danach Claude Code neu starten oder /reload-plugins ausführen. Braucht Claude Code 2.1.287 oder neuer.

Installier es nicht zusammen mit dem Original cache-tax. Beide registrieren /cache-tax und /keepwarm, und jede kalte Nachricht würde doppelt geprüft.

Bedienung

/keepwarm               6 Stunden warmhalten
/keepwarm 90m           eigenes Zeitfenster (auch 2h30m, 6h)
/keepwarm always        bei jedem Sessionstart automatisch 6 h, bleibt gespeichert
/keepwarm 6h every 2m   Ping alle 2 Minuten, nur zum Testen (mindestens 1m)
/keepwarm status        Zeitfenster, nächster Ping, letzter Ping
/keepwarm off           ausschalten, auch "always"
/cache-tax              Übersicht: Preis bei kaltem Cache, Status, Kontext, was die Session schon bezahlt hat
/cache-tax guard warn   nur Preis zeigen, Nachricht geht sofort durch
/cache-tax guard refuse einmal stoppen, zweites Senden geht durch (Standard)

Der Stopp greift erst ab 50.000 Tokens Kontext und nie bei Slash-Befehlen. Hast du einmal für einen kalten Cache bezahlt, schaltet sich Keepwarm automatisch für 3 Stunden ein.

Solange Keepwarm läuft, zeigt eine Zeile über dem Eingabefeld den Zustand: warm (grün), kalt (orange), unbekannt oder gestoppt, dazu Restzeit und nächsten Ping. Der letzte Ping-Beleg steht gedimmt dahinter. In VS Code, auf dem Handy und ohne Terminal steht derselbe Text in der Statuszeile.

Grenzen

  • Cache-Lebensdauer prüfen. Mit einem Abo (inklusive Nutzung) hält der Cache eine Stunde. Mit API-Key, Usage-Credits oder Cloud-Anbietern hält er standardmäßig nur 5 Minuten. Dann musst du promptCacheTtl auf "1h" setzen, sonst kann ein Ping nach 50 Minuten nichts mehr retten.
  • Die Uhr ist eine Schätzung: eine Stunde seit der letzten Anfrage. Das Mod fragt den Server nicht, ob der Cache wirklich noch da ist. Andere Änderungen (z. B. am System-Prompt) können den Cache auch vorher ungültig machen.
  • Die Token-Zahl im Stopp ist eine Obergrenze. Nach einem Resume enthält sie auch die Ausgabe der letzten Antwort.
  • Preise sind die Listenpreise der API. Mit Abo ist das ein Vergleichswert, keine Rechnung. Wie Cache-Lesen auf die 5-Stunden- und Wochenlimits angerechnet wird, ist nicht dokumentiert.
  • Pings kosten Tokens, auch für die Antwort des Modells. Wähle ein Zeitfenster, in dem du wirklich zurückkommst.
  • Keepwarm läuft nur, solange Claude Code offen ist. Die Liste der bezahlten Kalt-Starts lebt im Speicher, /clear leert sie.
  • Getestet wurden Timer, Stopp und Preisrechnung mit einer simulierten Uhr. Ob ein Ping den echten Cache trifft, hängt vom Anbieter ab.

Getestet auf macOS mit Claude Code 2.1.295. Windows: nicht getestet.

Credits

Basiert auf cache-tax von Karan Bansal (MIT). Was wir geändert haben, steht in NOTICE.md.

Source 2 files
hooks/register.ts 551 lines
1// cache-tax by Karan Bansal (https://github.com/karanb192/claude-code-mods), MIT License.
2// Modified by Verselo Systems SL, 2026: German UI, readability, fixes (see NOTICE.md)
3import type { EngineInterface, Register } from 'claude-code'
4import { statusIcons } from './status-icons'
5
6const TTL_MS = 60 * 60 * 1000
7const PING_AFTER_MS = 50 * 60 * 1000
8const MIN_PING_MS = 60 * 1000
9const AUTO_WARM_MS = 3 * 60 * 60 * 1000
10const DEFAULT_WINDOW_MS = 6 * 60 * 60 * 1000
11const BIG_TOKENS = 50000
12const PING_PROMPT = 'Reply with the single word: warm'
13const KEY_DEADLINE = 'deadline'
14const KEY_EVERY = 'every'
15const KEY_GUARD = 'guard'
16const KEY_ALWAYS = 'always'
17const STATE_LABEL = { warm: 'warm', cold: 'kalt', unknown: 'unbekannt', stopped: 'gestoppt' } as const
18
19// $ per million tokens, [cache read, 1h cache write, output], list prices September 2026.
20// Longer family names first: a model id matches the first row it contains.
21const PRICES: Array<[string, number, number, number]> = [
22  ['fable-5-1', 0.25, 20, 50],
23  ['fable-5', 1, 20, 50],
24  ['opus-5', 0.5, 10, 25],
25  ['opus-4', 0.5, 10, 25],
26  ['sonnet-5', 0.2, 4, 10],
27  ['sonnet', 0.3, 6, 15],
28  ['haiku', 0.1, 2, 5],
29]
30
31type PingRecord = { at: number; read: number; write: number; usd: number | null; warm: boolean }
32type Miss = { at: number; tokens: number; usd: number | null }
33type GuardMode = 'refuse' | 'warn'
34
35export type State = {
36  hasBand: boolean
37  sid: string
38  deadline: number
39  every: number
40  always: boolean
41  lastRequestAt: number
42  lastModel: string | null
43  ctx: number
44  compacted: boolean
45  guard: GuardMode
46  ackedAt: number
47  coldWritePending: boolean
48  misses: Miss[]
49  pending: { cancel: () => void } | null
50  last: PingRecord | null
51  stopped: string | null
52}
53
54function priceOf(model: string | null): [number, number, number] | null {
55  const m = (model ?? '').toLowerCase().replace(/[\s.]+/g, '-')
56  for (const [family, read, write, output] of PRICES) if (m.includes(family)) return [read, write, output]
57  return null
58}
59
60export function parseDuration(text: string): number | null {
61  const m = /^(?:(\d+)h)?(?:(\d+)m)?$/.exec(text.trim())
62  if (!m || (m[1] === undefined && m[2] === undefined)) return null
63  return (Number(m[1] ?? 0) * 60 + Number(m[2] ?? 0)) * 60 * 1000
64}
65
66/** German display number: 330316 -> "330.316", (6.61, 2) -> "6,61". Display only; the math never sees it. */
67// ponytail: hand-rolled instead of toLocaleString('de-DE'), the mod runtime may ship without full ICU locale data.
68export function fmtNum(n: number, decimals = 0): string {
69  const [int, frac] = Math.abs(n).toFixed(decimals).split('.')
70  const sign = n < 0 && Number(int) + Number(frac ?? 0) > 0 ? '-' : ''
71  return sign + int.replace(/\B(?=(\d{3})+(?!\d))/g, '.') + (frac ? ',' + frac : '')
72}
73
74export function fmtDuration(ms: number): string {
75  const total = Math.max(0, Math.round(ms / 60000))
76  const h = Math.floor(total / 60)
77  const m = total % 60
78  if (h >= 48) return `${Math.floor(h / 24)} Tage` + (h % 24 ? ` ${h % 24} h` : '')
79  if (!h) return `${m} min`
80  return m ? `${h} h ${m} min` : `${h} h`
81}
82
83export function fmtUsd(usd: number | null): string {
84  return usd == null ? 'unbekannt' : '$' + fmtNum(usd, usd >= 100 ? 0 : 2)
85}
86
87export function fmtTok(n: number): string {
88  return n >= 1e6 ? fmtNum(n / 1e6, 1) + 'M' : n >= 1000 ? Math.round(n / 1000) + 'k' : String(n)
89}
90
91function coldUsd(s: State): number | null {
92  const price = priceOf(s.lastModel)
93  return price ? s.ctx * price[1] / 1e6 : null
94}
95
96function warmUsd(s: State): number | null {
97  const price = priceOf(s.lastModel)
98  return price ? s.ctx * price[0] / 1e6 : null
99}
100
101/** Everything a fork bills: the cache read, any cache write, uncached input at the base rate (half the 1h write rate), and the output. */
102function pingUsd(u: { input_tokens: number; output_tokens: number; cache_read_input_tokens: number; cache_creation_input_tokens: number }, price: [number, number, number]): number {
103  return (u.cache_read_input_tokens * price[0] + u.cache_creation_input_tokens * price[1] + u.input_tokens * price[1] / 2 + u.output_tokens * price[2]) / 1e6
104}
105
106/** The read-only upper bound: pings at the cache-read rate that cost as much as one cold write of the same context. */
107function breakEvenPings(s: State): number | null {
108  const price = priceOf(s.lastModel)
109  if (!price || s.ctx <= 0) return null
110  return Math.floor(price[1] / price[0])
111}
112
113function isCold(s: State, now: number): boolean {
114  return s.lastRequestAt > 0 && !s.compacted && now - s.lastRequestAt >= TTL_MS
115}
116
117/** The cold-send price as its own line first, then the facts behind it. */
118function guardText(s: State, now: number, decision: string): string {
119  const price = priceOf(s.lastModel)
120  const cold = coldUsd(s)
121  const warm = warmUsd(s)
122  const head = cold == null ? 'cache-tax: Cache ist kalt. Diese Nachricht schreibt den ganzen Kontext neu (Preis für dieses Modell unbekannt).'
123    : `cache-tax: Cache ist kalt. Diese Nachricht kostet ca. ${fmtUsd(cold)}`
124  const rate = price ? ` zu $${fmtNum(price[1], price[1] % 1 ? 2 : 0)} pro Mio. Tokens` : ''
125  return `${head}\n${decision}\n` +
126    `Kalt seit ${fmtDuration(now - s.lastRequestAt - TTL_MS)}. Bis zu ${fmtNum(s.ctx)} Tokens werden neu geschrieben${rate}` +
127    (warm == null ? '.' : `, warm wären es ca. ${fmtUsd(warm)}.`)
128}
129
130export type ResumeFields = {
131  source: string
132  model?: string
133  context_tokens?: number
134  seconds_since_last_response?: number
135  prompt_cache_likely_expired?: boolean
136  estimated_cache_write_usd?: number
137}
138
139/** /clear starts a new conversation in the same process; nothing priced before it still exists. */
140export function resetForClear(s: State) {
141  s.ctx = 0
142  s.lastRequestAt = 0
143  s.compacted = false
144  s.ackedAt = 0
145  s.coldWritePending = false
146  s.misses = []
147  disarm(s)
148}
149
150/** Applies a resumed session's fields to the state; returns the line to log, if any. */
151export function seedFromResume(s: State, e: ResumeFields, now: number): string | null {
152  if (e.source !== 'resume' && e.source !== 'fork') return null
153  if (typeof e.context_tokens === 'number' && e.context_tokens > 0) s.ctx = e.context_tokens
154  if (typeof e.seconds_since_last_response === 'number') s.lastRequestAt = now - e.seconds_since_last_response * 1000
155  if (typeof e.model === 'string') s.lastModel = e.model
156  s.compacted = false
157  if (e.prompt_cache_likely_expired !== true || s.ctx < BIG_TOKENS) return null
158  const usd = typeof e.estimated_cache_write_usd === 'number' ? fmtUsd(e.estimated_cache_write_usd) : fmtUsd(coldUsd(s))
159  return `Fortsetzung mit kaltem Cache: die erste Nachricht kostet ca. ${usd} (${fmtNum(s.ctx)} Tokens neu schreiben). Brauchst du nur die Ergebnisse, dann /clear und eine Zusammenfassung einfügen.`
160}
161
162/** The status in two parts: the window and next ping, then the last receipt (drawn dim in the band). */
163function statusParts(s: State, now: number): [string, string] | undefined {
164  if (s.stopped) return [`Keepwarm gestoppt: ${s.stopped}`, '']
165  if (!s.deadline) return undefined
166  const pingText = s.last ? ` · letzter Ping: ${fmtTok(s.last.read)} gelesen, ${fmtUsd(s.last.usd)}` : ''
167  const nextText = !s.lastRequestAt ? ' · wartet auf die erste Nachricht'
168    : s.compacted ? ' · wartet auf die erste Nachricht nach dem Komprimieren'
169    : isCold(s, now) ? ` · gerade kalt, erster Ping ${fmtDuration(s.every)} nach der nächsten Nachricht`
170    : ` · Ping in ${fmtDuration(s.lastRequestAt + s.every - now)}`
171  return [`Keepwarm noch ${fmtDuration(s.deadline - now)}${nextText}`, pingText]
172}
173
174function statusText(s: State, now: number): string | undefined {
175  const parts = statusParts(s, now)
176  return parts && parts.join('')
177}
178
179function updateStatus($: EngineInterface, s: State, now: number) {
180  if (s.hasBand) $.ui.invalidate('ui.render')
181  else $.ui.status(statusText(s, now))
182}
183
184function disarm(s: State) {
185  if (s.pending) s.pending.cancel()
186  s.pending = null
187}
188
189// The window and its ping period belong to the session that armed them, so a
190// second session, or one resumed from another transcript, never inherits them
191// and cannot turn them off. The always switch and the guard mode stay global.
192function deadlineKey(s: State): string {
193  return `${KEY_DEADLINE}:${s.sid}`
194}
195
196function everyKey(s: State): string {
197  return `${KEY_EVERY}:${s.sid}`
198}
199
200/** Clears this session's own dead window and the bare keys a store written before 2.1.1 still holds. Other sessions' keys are never touched: a read followed by a delete cannot be made atomic against their renewal. */
201async function prune($: EngineInterface, s: State, now: number) {
202  for (const key of [KEY_DEADLINE, deadlineKey(s)]) {
203    const deadline = await $.store.get(key)
204    if (deadline === undefined) continue
205    if (typeof deadline === 'number' && deadline > now) continue
206    await $.store.delete(key)
207    await $.store.delete(KEY_EVERY + key.slice(KEY_DEADLINE.length))
208  }
209}
210
211async function stop($: EngineInterface, s: State, why: string | null, forgetAlways = false) {
212  s.deadline = 0
213  s.every = PING_AFTER_MS
214  s.stopped = why
215  disarm(s)
216  await $.store.delete(deadlineKey(s))
217  await $.store.delete(everyKey(s))
218  if (forgetAlways) {
219    s.always = false
220    await $.store.delete(KEY_ALWAYS)
221  }
222  updateStatus($, s, await $.clock.now())
223}
224
225async function arm($: EngineInterface, s: State) {
226  disarm(s)
227  if (!s.deadline) return
228  const now = await $.clock.now()
229  if (now >= s.deadline) return stop($, s, null)
230  // A cold window still needs expiry cleanup, but must not send a model request.
231  if (s.lastRequestAt && !s.compacted && !isCold(s, now)) {
232    const untilCold = s.lastRequestAt + TTL_MS - now
233    const delay = Math.min(s.deadline - now, untilCold, Math.max(1000, s.lastRequestAt + s.every - now))
234    s.pending = $.clock.after(delay, () => { void ping($, s) })
235  } else {
236    s.pending = $.clock.after(s.deadline - now, () => { void arm($, s) })
237  }
238  updateStatus($, s, now)
239}
240
241async function ping($: EngineInterface, s: State) {
242  s.pending = null
243  if (!s.deadline) return
244  const now = await $.clock.now()
245  if (now >= s.deadline) return arm($, s)
246  // A turn in the meantime re-armed the timer; this callback is stale.
247  if (isCold(s, now)) return arm($, s)
248  if (now - s.lastRequestAt < s.every - 1000) return
249  let reply
250  try {
251    reply = await $.model.fork({ prompt: PING_PROMPT })
252  } catch (err) {
253    return stop($, s, `der Ping ist fehlgeschlagen, ${err instanceof Error ? err.message : String(err)}`)
254  }
255  if (reply === null) return stop($, s, 'die Engine hat den Ping nicht gesendet, entweder war der Cache kalt oder der API-Aufruf ist fehlgeschlagen')
256  if (reply.isAnswered === false) {
257    const reason = reply.reason === 'nothing-to-fork' ? 'noch kein Gespräch zum Warmhalten'
258      : reply.reason === 'api-error' ? `der API-Aufruf ist fehlgeschlagen${reply.status === null ? '' : ` (${reply.status})`}`
259      : reply.reason === 'aborted' ? 'der Ping wurde abgebrochen'
260      : 'der Ping kam ohne Text zurück'
261    return stop($, s, reason)
262  }
263  const u = reply.usage
264  const price = priceOf(s.lastModel)
265  // A warm ping reads the prefix and writes only its own message; a write of a tenth of the read or more means the prefix broke.
266  const warm = u.cache_read_input_tokens > 0 && u.cache_creation_input_tokens < 0.1 * u.cache_read_input_tokens
267  const usd = price ? pingUsd(u, price) : null
268  s.last = { at: now, read: u.cache_read_input_tokens, write: u.cache_creation_input_tokens, usd, warm }
269  if (!warm) return stop($, s, `der Ping hat ${fmtTok(u.cache_read_input_tokens)} Tokens gelesen und ${fmtTok(u.cache_creation_input_tokens)} geschrieben (${fmtUsd(usd)}), der Cache war schon weg`)
270  s.lastRequestAt = now
271  await arm($, s)
272}
273
274/** The reply to an arming command; on a cold cache it says when the first ping can come. */
275function armedText(s: State, now: number, windowMs: number): string {
276  if (isCold(s, now)) return `Keepwarm an für ${fmtDuration(windowMs)}. ${coldNowText(s)}`
277  return `Keepwarm an für ${fmtDuration(windowMs)}. Nach ${fmtDuration(s.every)} Pause hält ein Ping den Cache warm: gelesen statt neu geschrieben.`
278}
279
280function coldNowText(s: State): string {
281  return `Der Cache ist gerade kalt, der erste Ping kommt ${fmtDuration(s.every)} nach deiner nächsten Nachricht.`
282}
283
284async function startWindow($: EngineInterface, s: State, windowMs: number, every: number) {
285  const now = await $.clock.now()
286  s.every = every
287  if (every === PING_AFTER_MS) await $.store.delete(everyKey(s))
288  else await $.store.set(everyKey(s), every)
289  s.deadline = now + windowMs
290  s.stopped = null
291  await $.store.set(deadlineKey(s), s.deadline)
292  await arm($, s)
293}
294
295function card(s: State, now: number): string {
296  // The price first, the rest as aligned rows below it.
297  const row = (label: string, value: string) => `${label.padEnd(12)}${value}`
298  const lines: string[] = []
299  lines.push(`Preis bei kaltem Cache: ${fmtUsd(coldUsd(s))}   (warm: ${fmtUsd(warmUsd(s))})`)
300  lines.push('')
301  lines.push(row('Modell', s.lastModel ?? 'noch nicht gesehen'))
302  if (s.compacted) lines.push(row('Status', 'zurückgesetzt durch Komprimieren, wartet auf die erste Nachricht'))
303  else if (!s.lastRequestAt) lines.push(row('Status', 'noch keine Anfrage in dieser Session'))
304  else if (isCold(s, now)) lines.push(row('Status', `KALT, letzte Anfrage vor ${fmtDuration(now - s.lastRequestAt)}`))
305  else lines.push(row('Status', `warm, noch ${fmtDuration(s.lastRequestAt + TTL_MS - now)}`))
306  lines.push(row('Kontext', `${fmtNum(s.ctx)} Tokens`))
307  const always = s.always ? ' (always)' : ''
308  const idle = s.always ? `aus bis zum nächsten Sessionstart, dann ${fmtDuration(DEFAULT_WINDOW_MS)} (always)` : `aus (/keepwarm schaltet ${fmtDuration(DEFAULT_WINDOW_MS)} an)`
309  lines.push(row('Keepwarm', s.deadline ? (statusText(s, now) ?? '').replace(/^Keepwarm /, 'an, ') + always : s.stopped ? `gestoppt, ${s.stopped}${always}` : idle))
310  const pings = breakEvenPings(s)
311  if (pings != null) lines.push(row('Break-even', `bis zu ${pings} Pings zum Lesepreis kosten so viel wie ein Neuschreiben bei kaltem Cache, ca. ${fmtDuration(pings * s.every)} Pause bei einem Ping alle ${fmtDuration(s.every)}`))
312  lines.push(row('Guard', s.guard === 'refuse' ? 'einmal stoppen (/cache-tax guard warn: nur Preis zeigen)' : 'nur warnen (/cache-tax guard refuse: einmal stoppen)'))
313  const paid = s.misses.reduce((a, m) => a + (m.usd ?? 0), 0)
314  lines.push(row('Session', `${s.misses.length}x kalten Cache bezahlt, ${fmtUsd(paid)}`))
315  return lines.join('\n')
316}
317
318export function freshState(): State {
319  return {
320    hasBand: false, sid: '', deadline: 0, every: PING_AFTER_MS, always: false, lastRequestAt: 0, lastModel: null, ctx: 0, compacted: false,
321    guard: 'refuse', ackedAt: 0, coldWritePending: false, misses: [], pending: null, last: null, stopped: null,
322  }
323}
324
325export const register: Register = on => {
326  const s = freshState()
327  let light = false
328  let noColor = false
329
330  on('config.set', { key: 'theme' }, async ($, e, next) => {
331    const result = await next(e)
332    if (result.deny === undefined) {
333      light = typeof result.value === 'string' && result.value.startsWith('light')
334      if (s.hasBand) $.ui.invalidate('ui.render')
335    }
336    return result
337  })
338
339  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
340    const rest = await next(e)
341    if (!s.hasBand || e.props.hasSurvey) return rest
342    const now = await $.clock.now()
343    const parts = statusParts(s, now)
344    if (!parts || (s.deadline && now >= s.deadline)) return rest
345    const state = s.stopped ? 'stopped' : !s.lastRequestAt || s.compacted ? 'unknown' : isCold(s, now) ? 'cold' : 'warm'
346    const color = noColor ? undefined : state === 'warm' ? 'success' : state === 'cold' ? (light ? '#c15f3c' : '#d97757') : undefined
347    const { Box, Text } = $.ui.resolve(e)
348    const asset = statusIcons[light ? 'light' : 'dark'][state === 'warm' || state === 'cold' ? state : 'neutral']
349    const icon = noColor ? Text({ bold: true, children: ['[>]'] })
350      : e.surface === 'terminal' ? $.ui.resolve(e).Image({ source: { png: asset.png }, columns: 3, rows: 1, alt: '[>]' })
351      : e.surface === 'desktop' ? $.ui.resolve(e).Svg({ source: asset.svg, width: 18, height: 18, alt: 'Cache Tax' })
352      : Text({ color, bold: true, children: ['[>]'] })
353    return Box({ flexDirection: 'column', children: [
354      rest,
355      Box({ flexDirection: 'row', alignItems: 'center', children: [
356        Box({ flexShrink: 0, children: [icon] }),
357        Text({ children: [
358          ' cache-tax · ',
359          Text({ color, bold: true, children: [STATE_LABEL[state]] }),
360          ` · ${parts[0]}`,
361          ...(parts[1] ? [Text({ dimColor: true, children: [parts[1]] })] : []),
362        ] }),
363      ] }),
364    ] })
365  })
366
367  on('session.start', async ($, e, next) => {
368    const r = await next(e)
369    s.hasBand = e.surface === 'terminal' || e.surface === 'desktop'
370    if (s.hasBand) {
371      const theme = (await $.config.list()).find(row => row.key === 'theme')?.value
372      light = typeof theme === 'string' && theme.startsWith('light')
373      noColor = Boolean(await $.env.get('NO_COLOR'))
374      $.ui.status(undefined)
375    }
376    s.sid = await $.session.id()
377    const now = await $.clock.now()
378    await prune($, s, now)
379    const saved = await $.store.get(deadlineKey(s))
380    const savedEvery = await $.store.get(everyKey(s))
381    const savedGuard = await $.store.get(KEY_GUARD)
382    s.deadline = typeof saved === 'number' && saved > now ? saved : 0
383    s.every = typeof savedEvery === 'number' && savedEvery >= MIN_PING_MS ? savedEvery : PING_AFTER_MS
384    s.guard = savedGuard === 'warn' ? 'warn' : 'refuse'
385    s.always = (await $.store.get(KEY_ALWAYS)) === true
386    // Always means a fresh default window every session, whatever the last one left behind.
387    if (s.always) await startWindow($, s, DEFAULT_WINDOW_MS, PING_AFTER_MS)
388    const usage = await $.session.usage()
389    if (usage.context.tokens) s.ctx = usage.context.tokens
390    await $.command.register({
391      name: 'keepwarm',
392      description: 'Hält den Prompt-Cache warm: ohne Angabe 6 h, oder z. B. 90m, always, off, status',
393      argumentHint: '[6h | always | off | status]',
394      immediate: true,
395    })
396    await $.command.register({
397      name: 'cache-tax',
398      description: 'Zeigt Cache-Status, Preis bei kaltem Cache und was diese Session schon bezahlt hat; guard warn|refuse',
399      argumentHint: '[status | guard warn | guard refuse]',
400      immediate: true,
401    })
402    // The hook form of cache-tax ships a /cache-tax:status skill; both installed means two guards.
403    const commands = await $.command.list()
404    if (commands.some(c => c.name === 'cache-tax:status')) {
405      $.ui.log('Die Hook-Version (cache-tax@claude-code-hooks) ist auch installiert, eine Nachricht an kalten Cache wird doppelt gewarnt oder gestoppt. Deinstalliere sie, oder stell hier /cache-tax guard warn ein.')
406    }
407    updateStatus($, s, now)
408    return r
409  })
410
411  // Resume fields seed the guard before any turn of the resumed session has run.
412  on('classic.SessionStart', async ($, e, next) => {
413    const r = await next(e)
414    if (e.source === 'clear') {
415      await stop($, s, null)
416      s.stopped = null
417      resetForClear(s)
418      s.sid = await $.session.id()
419      updateStatus($, s, await $.clock.now())
420      return r
421    }
422    const line = seedFromResume(s, e, await $.clock.now())
423    // The resume payload may omit the model; without it the guard cannot price the cold write.
424    if (!s.lastModel) s.lastModel = await $.session.model()
425    if (line) $.ui.log(line)
426    // The seeded clock decides whether a restored or always window pings before the first turn: never when it is cold.
427    await arm($, s)
428    return r
429  })
430
431  on('command.run', { command: 'keepwarm' }, async ($, e) => {
432    const words = String(e.args ?? '').trim().split(/\s+/).filter(Boolean)
433    const now = await $.clock.now()
434    if (words[0] === 'off') {
435      const wasAlways = s.always
436      await stop($, s, null, true)
437      return { text: wasAlways ? 'Keepwarm aus, und startet nicht mehr automatisch bei jedem Sessionstart' : 'Keepwarm aus' }
438    }
439    if (words[0] === 'always') {
440      s.always = true
441      await $.store.set(KEY_ALWAYS, true)
442      await startWindow($, s, DEFAULT_WINDOW_MS, PING_AFTER_MS)
443      const cold = isCold(s, now) ? ` ${coldNowText(s)}` : ''
444      return { text: `Keepwarm immer an: jede Session startet mit ${fmtDuration(DEFAULT_WINDOW_MS)} Warmhalten. /keepwarm off schaltet das dauerhaft aus.${cold}` }
445    }
446    if (!words.length) {
447      await startWindow($, s, DEFAULT_WINDOW_MS, PING_AFTER_MS)
448      return { text: armedText(s, now, DEFAULT_WINDOW_MS) }
449    }
450    if (words[0] !== 'status') {
451      const window = parseDuration(words[0])
452      if (window == null) return { text: 'Keepwarm braucht eine Dauer wie 6h oder 90m, oder always, off, status' }
453      // "every 2m" is a testing knob and lasts only for the window it was given with.
454      let every = PING_AFTER_MS
455      if (words[1] === 'every') {
456        const period = parseDuration(words[2] ?? '')
457        if (period == null || period < MIN_PING_MS) return { text: 'every braucht einen Abstand von mindestens 1m' }
458        every = period
459      }
460      await startWindow($, s, window, every)
461      return { text: armedText(s, now, window) }
462    }
463    return { text: statusText(s, now) ?? 'Keepwarm aus' }
464  })
465
466  on('command.run', { command: 'cache-tax' }, async ($, e) => {
467    const words = String(e.args ?? '').trim().split(/\s+/).filter(Boolean)
468    const now = await $.clock.now()
469    if (words[0] === 'guard') {
470      if (words[1] !== 'warn' && words[1] !== 'refuse') return { text: '/cache-tax guard braucht warn oder refuse' }
471      s.guard = words[1]
472      await $.store.set(KEY_GUARD, s.guard)
473      return { text: s.guard === 'refuse' ? 'Guard: einmal stoppen. Eine Nachricht an kalten Cache wird mit Preis angehalten, nochmal senden geht durch.' : 'Guard: nur warnen. Eine Nachricht an kalten Cache geht durch, der Preis wird angezeigt.' }
474    }
475    return { text: card(s, now) }
476  })
477
478  // The message that pays. Only its first character is read.
479  on('prompt.submit', async ($, e, next) => {
480    if (e.origin.kind === 'plugin') return next(e)
481    if (typeof e.text !== 'string' || e.text.trimStart().startsWith('/')) return next(e)
482    const now = await $.clock.now()
483    if (!isCold(s, now) || s.ctx < BIG_TOKENS) return next(e)
484    if (s.guard === 'warn') {
485      $.ui.log(guardText(s, now, `Wird trotzdem gesendet. Danach hält Keepwarm den Cache ${fmtDuration(AUTO_WARM_MS)} warm.`))
486      s.coldWritePending = true
487      return next(e)
488    }
489    if (s.ackedAt === s.lastRequestAt) {
490      s.ackedAt = 0
491      s.coldWritePending = true
492      return next(e)
493    }
494    s.ackedAt = s.lastRequestAt
495    return { drop: guardText(s, now, `Nochmal senden = bezahlen (danach hält Keepwarm den Cache ${fmtDuration(AUTO_WARM_MS)} warm). Oder /clear und mit einer Notiz neu starten.`) }
496  })
497
498  on('turn.step', async function* ($, e, next) {
499    if (!e.agentId) s.lastRequestAt = await $.clock.now()
500    yield* next(e)
501  })
502
503  on('turn.complete', async ($, e, next) => {
504    const r = await next(e)
505    if (e.agentId) return r
506    const now = await $.clock.now()
507    // A sleeping host may deliver this turn before the expired window's timer.
508    if (s.deadline && now >= s.deadline) await stop($, s, null)
509    // turn.step stamps the exact request time; when no step of this turn did, the turn's end is the floor.
510    if (now - s.lastRequestAt > e.durationMs) s.lastRequestAt = now
511    s.compacted = false
512    s.ackedAt = 0
513    const u = e.usage
514    if (u) {
515      if (u.model) s.lastModel = u.model
516      const prev = s.ctx
517      // A turn's usage is its responses summed, so a ten-step turn reports ten
518      // reads of the context. The live window is the engine's figure; the sum
519      // is only the fallback for a host that reports no tokens.
520      const write = u.cache_creation_input_tokens
521      const live = (await $.session.usage()).context.tokens
522      s.ctx = live && live > 0 ? live : u.input_tokens + u.cache_read_input_tokens + write
523      const full = prev > 20000 && write >= 0.5 * prev
524      if (full || s.coldWritePending) {
525        const price = priceOf(s.lastModel)
526        const usd = price ? write * price[1] / 1e6 : null
527        s.misses.push({ at: now, tokens: write, usd })
528        if (s.deadline < now + AUTO_WARM_MS) {
529          await startWindow($, s, AUTO_WARM_MS, s.every)
530          $.ui.log(`Kalten Cache bezahlt: ${fmtTok(write)} Tokens neu geschrieben, ca. ${fmtUsd(usd)}. Keepwarm hält den Cache jetzt ${fmtDuration(AUTO_WARM_MS)} warm, damit das heute nicht nochmal passiert. /keepwarm off stoppt das.`)
531        }
532      }
533    }
534    s.coldWritePending = false
535    await arm($, s)
536    return r
537  })
538
539  on('session.compact', async ($, e, next) => {
540    const r = await next(e)
541    if (!e.agentId) {
542      s.compacted = true
543      s.ctx = 0
544      s.ackedAt = 0
545      disarm(s)
546      await arm($, s)
547    }
548    return r
549  })
550}
551
hooks/status-icons.ts 32 lines
1// Generated by tools/generate-status-icons.py from the official SVG assets.
2export const statusIcons = {
3  "dark": {
4    "warm": {
5      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n  <title>Cache Tax</title>\n  <g stroke=\"#faf9f5\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n    <path fill=\"#78b85a\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n    <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n    <path d=\"m40 32 8 5-8 5\"/>\n  </g>\n</svg>\n",
6      "png": "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"
7    },
8    "cold": {
9      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n  <title>Cache Tax</title>\n  <g stroke=\"#faf9f5\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n    <path fill=\"#d97757\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n    <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n    <path d=\"m40 32 8 5-8 5\"/>\n  </g>\n</svg>\n",
10      "png": "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"
11    },
12    "neutral": {
13      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n  <title>Cache Tax</title>\n  <g stroke=\"#faf9f5\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n    <path fill=\"#92928b\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n    <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n    <path d=\"m40 32 8 5-8 5\"/>\n  </g>\n</svg>\n",
14      "png": "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"
15    }
16  },
17  "light": {
18    "warm": {
19      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n  <title>Cache Tax</title>\n  <g stroke=\"#141413\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n    <path fill=\"#397527\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n    <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n    <path d=\"m40 32 8 5-8 5\"/>\n  </g>\n</svg>\n",
20      "png": "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"
21    },
22    "cold": {
23      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n  <title>Cache Tax</title>\n  <g stroke=\"#141413\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n    <path fill=\"#c15f3c\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n    <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n    <path d=\"m40 32 8 5-8 5\"/>\n  </g>\n</svg>\n",
24      "png": "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"
25    },
26    "neutral": {
27      "svg": "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\" viewBox=\"0 0 64 64\" fill=\"none\">\n  <title>Cache Tax</title>\n  <g stroke=\"#141413\" stroke-width=\"4\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n    <path fill=\"#686861\" d=\"M8 18 32 5 56 18 32 31Z\"/>\n    <path d=\"M8 18v28l24 13 24-13V18M32 31v28\"/>\n    <path d=\"m40 32 8 5-8 5\"/>\n  </g>\n</svg>\n",
28      "png": "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"
29    }
30  }
31} as const
32