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).

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:
/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.
/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.
/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.
promptCacheTtl auf "1h" setzen, sonst kann ein Ping nach 50 Minuten nichts mehr retten./clear leert sie.Getestet auf macOS mit Claude Code 2.1.295. Windows: nicht getestet.
Basiert auf cache-tax von Karan Bansal (MIT). Was wir geändert haben, steht in NOTICE.md.
hooks/register.ts 551 lines1// 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}
551hooks/status-icons.ts 32 lines1// 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": "iVBORw0KGgoAAAANSUhEUgAAAGAAAABACAYAAADlNHIOAAAABmJLR0QA/wD/AP+gvaeTAAAMn0lEQVR4nO2ce3AV1R3Hv2fvM5cEAuQhEFMBGyEBlFhbpakvgtWMrRUjMpSGaFtMp0YDVOQRq1YkgUICdcZiEU3TFAUhHSpm2hKojxEcH6EtWsLLWIgJJCGPe+/u3XM29/76B1kIuZsn90Ein5n9Z/e3u7/z++45u+d3zh7gClfoL0LIN2pcLhdccQquODUulwsh3xhuv4Y8Ho8nUajyy4LLXsFl6rL5NC5vV1X12nD7OeQgco7mXCkUXFYFl8nZdpaKi9dTSvJkSkmeTMXF68nZdlYXQghVfpnc7vhw+z3oISKHxt1PCS63Ci6T6nFR+c5tNH36dIqJGX3RNnXqFHp1y2byKE5dCDfnSiHR2eHhLsegg4gkIdxZnMt1ehNTWfl3uvXWNL/Ad91uueU7VL5z24WmSZUbhao8QUTmcJfLCBZuB7qiqe50YqwIwFQAqKo6iBdWF+DAgQ/7dZ1bbrkZK1csR2rq9I497AgBT1utETsYYxRgtwfMZSOAEMrN8PnWgLFbAaCmpgarC9aioqICRP7xskgMGdeMBABUfNkCzedvwxhDRkYGVixfivHjx+u7P2KStNRiiXg3aIXpB2EXgHNXsgSpgIAfAkBzczM2bXoZf9i8BUIIP3uJAd+5ajiyJsciLsIKADiranjzWBP2nmqDz0Ass9mMuXPnYMmSxYiPiwMAMOCvPviW22xR/w1m+XojbAIoijLOLNFzYMgGYFIUBa++9ke8+OKLcLnchudMixmGBZPjcc1wm+HxWjfHG0ebcKDeaXjc4XDgkYcX4LHHHsPw4VEA4AWhpN3HnnE4HF8FpGD9JOQCEFFku5B/SWD5ACI1TcO2bW9i3br1aGhsNDzn2mg75l8Xh6kxw/p0jyMtCkoPN6K6RTE8PmrUSOTk5GDhz38Kq9UKAB4C+53VygsZG9k6sJINjJAJQERWTVOyATwPQhwR4e2330ZB4W9RU1NjeE6sw4K534zFbQkjBuToJ2fcKDl8BvWyf1MGAAkJCXg89zHMmzcXkiQBQDMDrTVbh21kjKkDuGW/CboARMSE8GQyUCGACQDwySef4PlVBfj4448Nz4mymHHfxFH4wfhRMEuX5mK7j/DP2ja8cbQRrbzd0Ob666dh5coVSPvuDH3XKTCsslgcWxhj3ktyoBeCKkDHJ+UaAKkAcPToMaxbX4zdu3cb2ttMEjKuGYnZ18bAYZYC6gv3+lDxZQvKjzdBafcZ2tz6vTQ8/XQ+UlKS9V2fE9hzNpvjzYA604mgCKCq6iyT5C0jQhwA1NfXo3jDRrz++jZ4vf4PlFliuCNhBB5KisVIW3D7Sy6tHbtONOOtmma0G3y6SpKEjIwM5K9chsTERAAAY2jw+kzz7Xb7nkD7E3ABVFW9S4L3b2Bgra1teOmll/DKltegqsZN6rfiIpGdHI8xw6yBdqVHGhUNrx9rxHu1bTDqlVmtVsyZk4mlS59EzOjRAIF8MN1tt9v/EUg/Ai6Aprq/IsbGFhVvQFFRMXwGTxkApIxyYP7kOCRFRwTahX5xtNWDssMN+LzZ+ItJkhgWL16ExYvywIjqLPbIcYG8f8AFEFzxAcSuThxv2NwkRtkwf1IcboyLDPStL4lPG9woq27ASRf3O2YymXDqZA0ARlabI6Avp8C+6QAAxAAYBn+wcqEsFPAHNgg1QCYAGDsusUe7wdIE6dR9dRIAYLUNC2jMwiaAzrSYYXg4OR6JUcbphWBRJwtsPdKID+udhi9hP/sgCRCEJsiY+fPnwWQy+e3/T5OMX71fg02H6tHSTUcpkLi0dpRVN2DRe1/ggEHwJUnCvffeG3Q/dEJWA+q+Ooljx47jt+uKeu+ITRwNh8VfrEuhvx0xI/+BQdwE6QUAzqUiVr1QiI8++sjwGlFWE+6bMDpkqYgbbrgeK1Ys75yKGMICMDSgo4e8Z08lnn3u+R6TcQ9MjMHMq6PRXx0IwIf1Tvz5SGOPybgnf7UEmZmzwRgDgGYAo3ryf9ALYLE6os6lo6WVAEX1JR09cYQdP5kUyHT0KOTkPNo5Ha0Q2ItWKy/UhLWlJ/8HvQB6AYIxIHPKxbHtWO8DMrm5uYiKigQMBmT66n+gCJsAOgMdkvzJpDjEOywAeh+StFgseOihB/s0JPm1E+D8eZfJoPzXVgCd4E1LARHYLqs14seMsW67vaEWIGQdsb5isUdWWqyOVDDkEFCfmjodO3dsR2lpCSZNuq7X8ydNug6lpSXIyXkUP/3ZQkxPvQklJaXw+XyMgX6kCeWQprq/H4KihAd9RlrXGWv6/v5c61KmJk6ZknLR8bvuSqd/Hfy402Re91Zyu68Kpv994bJrgowgco4WwvwkAz0BwK6qKiZMTLrI5osTR2G320EEMAak3vhtnD59+iIbs9mMn//sESxZshgOhwMAWsGwzGJxbGaM+YLlf09cdk2QEYwNP2uzOZZ5fdJ1AGC32/1sLuyjfwDAmsLV+tyf87S3t+P3m/6A2+9IR+XefQAQDcImTSjvq6qz9/YtCAwKAXQiIiJO9mZDMOcCaJk1Kx3vvfcOMjNn+9nU1tYiKysbCxY8jLq6OgCYITHTAVVVk/yMg8ygEqAv2O32oz4y3Qxgf1xsLH63cQNKS0uQkJDgZ7unci/unDlLrw0jTcxbEGp/h5wAwDkRLFbH98CQA6A1feadeOeflfhFzkKYzRfPunA6XVi6dBkAgID0UPs6JAUYTFyWPy1cKqqqJmlCeQ3ADACo3LsPK1bko7a21s92+PAorF1bCABgQCUB/i+NIDLkaoCqqkkS834IYEZDYyMefyIPWVnZhsGflT4T+/buQfrMOwGgxUum5aH2d8jVABPzFhAwcs+eSuQ+/gScTpefTUJCAlavXqUHHgD2+8j7iN0+7Kjgckj9HXIC6C/Sp5at8At+XzpioWbICdAdqanTsXZNAZKT9Ym39LpFw2IWGXm6xxODTMjfAaqqTgjm9RlQCQBr1hQgPj4e8fHxWP3CKvx111/04H/BiO622iLndQ1+sH0zImQ1QFVV2O12SMxbLVT5NUs7/ZpFRp4J9H28ZFouMe8ds9JnjjxYddH/BxqBrbdaI37DGPN0PnAh1+TN030NFSGrAd9Nuw1lZVvh9XotYFioWdiJYPxIrfeEGVAOMBfAXAwoJ/hSbTbH8s7B17OtmjCdYKCnfD6fbffu3bjt9pD3xwKH4HKbUTpY39LSZlBFxVvn07ucy3VCyI/29UfqQKSLicgshPxo5x/BKyreorS0GYY+T5mSol+/7dKi40/Aa8D5NrhwNaKjR/gdr64+gqysbDyQOQdVVQfBgDHnMpKeQ5wr9wfan65wrtyvCc8hEDYxYExV1UE8kDkHWVnZqK4+4mcfHT0CawpXA7hQtssaVVWTBJebBZep4UwdPftMPiUkjOt2aYHZ999H//l3VedVT/Zrmietu+sPtAZomidNcHm/bld9+BBlZy+g2NgYQ7/Gjh1DeXm5VF93Sr92cziypQNCVdUkIZRKvbD/+/I45eXlUnx8nGFhr7oqnvLycunUqZrzgdS4vItzV3LXa/dXAM5dyRqXd+nHT9efomefyaexY8cY+hIXF0vZ2QvoxPHDFx4KoVQOmuB3RlPd6YLLn+oF+fyzf1N29oJua0Ni4tX07DP51NR4Wi+8V+Pydo/Hc41+zb4KoCjKuI61hTTBZWptaaSionU0fvw3eqyNB6s6D1vKn3GuPBiW4AUKImKcKw8KLh/XC7b/g3cpI+OebgORlHQtFRWtI7erRQ+Ecu6LqSW6NwGIKLJjHNkluEyyu5Ve3bKZkidP6vZ+6el30r59ezoH/qQQ8kIiCuwM4XBCRDahKosEl5v0gr69exfddNO3ug3MDTdcT69u2Uyqx6UHpqk3AXQbrrqpfOe2Xq//57JS4qr7wrmqsoiIQvuzQii58IQqzr4+oTNn3tH1Ce1JANr/wbt0zz1391DDvtm1hsl6DQt3fEJGRxv9iuBy+0DaaCMBPjv0rz69Y842ndED3y5U+RVFUQL652N/uOyWq2lsakJRUTG2bn0Dmqb52UuShMzM2di+fcdF++fMycSOHeXw+fyTmhaLBfPmzcXixYsQGxMD4MpyNX70d25oX7iyYNMA+LotWXZZ0t9cTSBzTVfoBBEN07icryf4PIqTyv5UQtOmTfUL/LRpU6nsTyWdl61s07icT0R9+63mCt1D5IoVwr1RcJmLjoVbN2woopSUZEpJSaYNG4o6L9zKhXBvJHLFhtvvIYeqqhMEd28VXPZ1/u7v2HyCu7eGY1Tra0fH4t07Oy3evfPK4t1XGBD/B0HDzNOE5wCHAAAAAElFTkSuQmCC"
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