Warnt, bevor eine Nachricht bei kaltem Prompt-Cache das ganze Gespräch neu einliest

Ein Claude-Code-Mod, der den Prompt-Cache im Blick behält. Wenn du nach einer längeren Pause eine Nachricht schickst und der Cache vermutlich kalt ist, hält der Mod die Nachricht an und zeigt dir, wie viele Tokens dann neu gelesen werden müssten. Du entscheidest, ob du sie trotzdem sendest. Mit /keep-warm schickt der Mod kurz vor Ablauf eine Mini-Nachricht, damit der Cache warm bleibt.
Im Terminal in Claude Code eintippen:
/plugin install cache-waechter --marketplace boonpflug/claude-mods-deutsch
Danach y zum Hinzufügen des Marketplace und mit Enter den Bereich „user“ wählen. Der Mod läuft sofort.
Aus der Shell geht es auch: claude plugin install cache-waechter --marketplace boonpflug/claude-mods-deutsch
hooks/register.tsx 79 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4const lastActive = atom({ plugin: 'cache-waechter', key: 'lastActive' } as const, 0)
5const keepWarm = atom({ plugin: 'cache-waechter', key: 'keepWarm' } as const, false)
6
7const PING = 'Kurzer Ping, damit der Cache warm bleibt. Antworte nur mit „ok“.'
8
9async function coldAfterMs($: EngineInterface): Promise<number> {
10 // Standard: etwa eine Stunde. Für Demos per CACHE_WAECHTER_MINUTEN verkürzbar.
11 const minutes = Number((await $.env.get('CACHE_WAECHTER_MINUTEN')) ?? '60')
12 return (Number.isFinite(minutes) && minutes > 0 ? minutes : 60) * 60_000
13}
14
15async function warmLoop($: EngineInterface) {
16 const limit = await coldAfterMs($)
17 while (await read($, keepWarm)) {
18 const idle = (await $.clock.now()) - (await read($, lastActive))
19 const wait = Math.max(5_000, limit - idle - Math.min(60_000, limit / 6))
20 await $.clock.sleep(wait)
21 if (!(await read($, keepWarm))) return
22 const stillIdle = (await $.clock.now()) - (await read($, lastActive))
23 if (stillIdle >= limit - Math.min(60_000, limit / 6)) {
24 $.ui.status('🔥 Cache wird warm gehalten')
25 await $.prompt.submit({ text: PING })
26 }
27 }
28}
29
30export const register: Register = on => {
31 on('session.start', async ($, e, next) => {
32 await update($, lastActive, () => 0)
33 await $.command.register({
34 name: 'keep-warm',
35 description: 'Prompt-Cache warm halten, solange du weg bist (an/aus)',
36 })
37 return next(e)
38 })
39
40 on('command.run', { command: 'keep-warm' }, async $ => {
41 const on_ = !(await read($, keepWarm))
42 await update($, keepWarm, () => on_)
43 $.ui.status(on_ ? '🔥 keep-warm an' : undefined)
44 if (on_) void warmLoop($)
45 return { text: on_ ? 'keep-warm ist an: Kurz bevor der Cache kalt wird, schicke ich einen Mini-Ping.' : 'keep-warm ist aus.' }
46 })
47
48 on('turn.complete', async ($, e, next) => {
49 const done = await next(e)
50 const now = await $.clock.now()
51 await update($, lastActive, () => now)
52 return done
53 })
54
55 on('prompt.submit', async ($, e, next) => {
56 const last = await read($, lastActive)
57 const typed = e.origin === undefined || e.origin.kind === 'composer'
58 if (e.command || e.isMeta || !typed || last === 0) return next(e)
59
60 const idle = (await $.clock.now()) - last
61 if (idle < (await coldAfterMs($))) return next(e)
62
63 const usage = await $.session.usage()
64 const tokens = usage.context.tokens ?? 0
65 if (tokens < 1_000) return next(e)
66
67 const minutes = Math.round(idle / 60_000)
68 const answer = await $.ui.ask(
69 `🧊 Cache ist kalt (letzte Antwort vor ${minutes} min).\n` +
70 `Diese Nachricht liest das ganze Gespräch neu ein: ca. ${tokens.toLocaleString('de-DE')} Tokens ` +
71 `(${usage.context.percent ?? '?'} % deines Kontexts).\n\nTrotzdem senden?`,
72 { header: 'Cache-Wächter', options: ['Trotzdem senden', 'Lieber neu starten (/clear)'] },
73 )
74
75 if (answer === 'Trotzdem senden') return next(e)
76 return { drop: 'Cache-Wächter: Nachricht nicht gesendet. Tipp: /clear und mit einer kurzen Zusammenfassung neu starten.' }
77 }).catch(($, e, next) => next(e))
78}
79types/index.d.ts 8 lines1export type KeepWarm = boolean
2
3declare module 'claude-code' {
4 interface PluginState {
5 'cache-waechter': { lastActive: number; keepWarm: KeepWarm }
6 }
7}
8