SLOPSHOPPER

cache-waechter

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

newcommandstatusprompt
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · cache-waechter
› 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 › /keep-warm ⎿ cache-waechter: keep-warm ist an: Kurz bevor der Cache kalt wird, schicke ich einen Mini-Ping. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts ⚠ cache-waechter: 🔥 keep-warm an
README

cache-waechter

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.

Installieren

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

Source 2 files
hooks/register.tsx 79 lines
1import { 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}
79
types/index.d.ts 8 lines
1export type KeepWarm = boolean
2
3declare module 'claude-code' {
4  interface PluginState {
5    'cache-waechter': { lastActive: number; keepWarm: KeepWarm }
6  }
7}
8