Zeigt den Füllstand des Kontexts und sagt, wann eine Zusammenfassung und eine neue Session Token spart.

A mod for Claude Code that shows how full your session's context is, tells you when it is worth switching to a fresh session, and writes a handover note for you with one click. Fewer wasted tokens, better answers in long sessions.
/plugin install kontext-waechter --marketplace KaelanTesseract/kontext-waechter

With every reply, Claude sends the whole conversation again. The longer a session runs, the more is carried along on each message: it gets more expensive, and answers tend to get less precise. Starting fresh at the right moment, with a clean summary, saves money and keeps quality up. The Context Watcher does the watching for you.
Context fill at a glance. A bar above the prompt box shows the percentage, the tokens in use and the cost so far. It updates after every reply and also appears in the status line.
A recommendation for when to stop.
| Fill level | Advice |
|---|---|
| below 50 % | Keep working |
| from 50 % | Start fresh when you change topic |
| from 70 % | A summary and a new session pay off now |
| from 85 % | Summarize and start fresh now |
A short notice also appears when you cross 70 % and 85 %.
Handover in one click. The "Übergabe" button (handover) has Claude write a note for the next session: goal, current state, decisions made, important files, next steps, known pitfalls and how you like to work. It is saved in your project folder as .claude/uebergabe/LETZTE.md, plus a dated copy.
New session opened for you (desktop app). The second button, "+ neue Session", also asks Claude to open a fresh session in the same folder that reads the handover and carries on. The mod cannot open sessions itself, so it asks Claude to do it; this works in the desktop app only.
Seamless continuation. In the new session, type /weiter. The handover is loaded, Claude summarizes where you stand and carries on with the first next step. If an unread handover is waiting at startup, the mod tells you.
Subagents counted separately. Subagents have their own context and do not fill the main session's. They are not part of the bar, but their runs and tokens are shown next to it.
| Command | Effect |
|---|---|
/uebergabe | Creates the handover, same as the button |
/weiter | Loads the last handover into the new session |
/waechter | Shows or hides the bar |
In a terminal session:
/plugin install kontext-waechter --marketplace KaelanTesseract/kontext-waechter
From a shell:
claude plugin install kontext-waechter --marketplace KaelanTesseract/kontext-waechter
In the desktop app, /plugin does not run. Click + next to the prompt box, then Plugins, Add marketplace with KaelanTesseract/kontext-waechter, then Add plugin.
To update: claude plugin update kontext-waechter.
/compact?/compact replaces the conversation inside the running session with a summary that Claude chooses itself. It is quick, but details can get lost unnoticed. A new session with a handover starts empty and only knows what you hand over. You can read and edit the handover first.
Rule of thumb: same topic and 50 to 70 % full, /compact is enough. From 70 %, or for a new topic, the handover is the better choice.
The thresholds (50, 70, 85 percent) are at the top of hooks/register.tsx. The text that asks for the handover is there too (it is written in German; change it to your language if you like).
.claude/uebergabe/ to your .gitignore.MIT, see LICENSE.
hooks/register.tsx 294 lines1import type { Register } from 'claude-code'
2
3import type { Stand } from '../types'
4
5const stand = { plugin: 'kontext-waechter', key: 'stand' } as const
6const isHidden = { plugin: 'kontext-waechter', key: 'isHidden' } as const
7const isBusy = { plugin: 'kontext-waechter', key: 'isBusy' } as const
8
9const ORDNER = '.claude/uebergabe'
10const LETZTE = `${ORDNER}/LETZTE.md`
11const GELESEN = `${ORDNER}/gelesen.txt`
12
13// Schwellen in Prozent des Kontextfensters. Jede Antwort schickt den ganzen
14// Verlauf erneut mit, darum wird jede weitere Runde teurer.
15const BEOBACHTEN = 50
16const SINNVOLL = 70
17const DRINGEND = 85
18
19const UEBERGABE_PROMPT = `Schreibe jetzt eine Übergabe für eine neue Session, die dieses Gespräch nicht kennt und nahtlos weiterarbeiten soll. Antworte nur mit dem Dokument, ohne Einleitung, auf Deutsch, in Markdown, so knapp wie möglich, aber vollständig:
20
21# Übergabe
22## Ziel
23Was soll am Ende erreicht sein, und warum.
24## Stand
25Was ist fertig, was läuft gerade, was ist noch offen.
26## Entscheidungen
27Getroffene Entscheidungen samt Begründung, damit sie nicht neu diskutiert werden.
28## Wichtige Orte
29Dateien, Ordner, Befehle, Adressen, die gebraucht werden (mit vollständigen Pfaden).
30## Nächste Schritte
31Geordnete Liste, der erste Punkt ist sofort ausführbar.
32## Stolpersteine
33Fehlschläge, Sackgassen und Eigenheiten, die man sonst wieder erleben würde.
34## Vorlieben des Nutzers
35Wie der Nutzer arbeiten möchte, soweit im Gespräch sichtbar.
36
37Keine Vermutungen als Tatsachen darstellen. Nichts aufnehmen, was sich leicht aus dem Code oder Git-Verlauf ablesen lässt.`
38
39type Stufe = { text: string; color: 'success' | 'warning' | 'error'; rank: number }
40
41const stufe = (percent: number): Stufe => {
42 if (percent >= DRINGEND) {
43 return { rank: 3, color: 'error', text: 'Jetzt zusammenfassen lassen und neue Session starten' }
44 }
45 if (percent >= SINNVOLL) {
46 return { rank: 2, color: 'warning', text: 'Zusammenfassung und neue Session lohnen sich jetzt' }
47 }
48 if (percent >= BEOBACHTEN) {
49 return { rank: 1, color: 'warning', text: 'Kontext füllt sich, bei einem Themenwechsel neu starten' }
50 }
51 return { rank: 0, color: 'success', text: 'Kontext frei, weiterarbeiten' }
52}
53
54const balken = (percent: number) => {
55 const full = Math.max(0, Math.min(10, Math.round(percent / 10)))
56 return '█'.repeat(full) + '░'.repeat(10 - full)
57}
58
59const k = (n: number) => (n >= 1000 ? `${Math.round(n / 1000)}k` : `${n}`)
60
61const stempel = (text: string) => {
62 const m = /<!-- erstellt: (\d+) -->/.exec(text)
63 return m === null ? '' : m[1]
64}
65
66const dateiname = (ms: number) =>
67 new Date(ms).toISOString().slice(0, 16).replace('T', '_').replace(':', '-')
68
69export const register: Register = on => {
70 let turns = 0
71 let lastRank = 0
72 let agentRuns = 0
73 let agentTokens = 0
74
75 on('session.start', async ($, e, next) => {
76 await $.command.register({
77 name: 'waechter',
78 description: 'Kontext-Wächter: Anzeige ein-/ausblenden',
79 })
80 await $.command.register({
81 name: 'uebergabe',
82 description: 'Zusammenfassung für die nächste Session erstellen und speichern',
83 })
84 await $.command.register({
85 name: 'weiter',
86 description: 'Letzte Übergabe laden und damit weiterarbeiten',
87 })
88
89 try {
90 const letzte = await $.fs.read(LETZTE)
91 let gelesen = ''
92 try {
93 gelesen = await $.fs.read(GELESEN)
94 } catch {
95 gelesen = ''
96 }
97 if (stempel(letzte) !== '' && stempel(letzte) !== gelesen.trim()) {
98 $.ui.toast('Übergabe der letzten Session gefunden. Mit /weiter laden.')
99 }
100 } catch {
101 // keine Übergabe vorhanden
102 }
103
104 return next(e)
105 })
106
107 on('turn.complete', async ($, e, next) => {
108 if (e.agentId === undefined) {
109 turns += 1
110 const u = await $.session.usage()
111 const percent = u.context.percent ?? 0
112 const s: Stand = {
113 percent,
114 tokens: u.context.tokens ?? 0,
115 window: u.context.window,
116 usd: u.cost?.usd ?? null,
117 turns,
118 agentRuns,
119 agentTokens,
120 }
121 await $.state.set(stand, s)
122
123 const st = stufe(percent)
124 $.ui.status(`Kontext ${percent}% ${balken(percent)}`)
125 if (st.rank > lastRank && st.rank >= 2) {
126 $.ui.toast(`Kontext ${percent}% voll: ${st.text}.`)
127 }
128 lastRank = st.rank
129 } else if (e.usage !== undefined) {
130 // Subagenten haben ein eigenes Fenster: sie zählen nur in den Verbrauch,
131 // nicht in den Füllstand der Hauptsession.
132 agentRuns += 1
133 agentTokens +=
134 e.usage.input_tokens + e.usage.output_tokens + e.usage.cache_creation_input_tokens
135 const { value: s = null } = await $.state.get(stand)
136 if (s !== null) {
137 await $.state.set(stand, { ...s, agentRuns, agentTokens })
138 }
139 }
140 return next(e)
141 })
142
143 on('command.run', { command: 'waechter' }, async $ => {
144 const { value: hidden = false } = await $.state.get(isHidden)
145 await $.state.set(isHidden, !hidden)
146
147 // Gleich messen: in einer Session, in der die Mod eben erst geladen wurde,
148 // gibt es sonst bis zur nächsten Antwort keinen Wert und keine Leiste.
149 const u = await $.session.usage()
150 const percent = u.context.percent ?? 0
151 const s: Stand = {
152 percent,
153 tokens: u.context.tokens ?? 0,
154 window: u.context.window,
155 usd: u.cost?.usd ?? null,
156 turns,
157 agentRuns,
158 agentTokens,
159 }
160 await $.state.set(stand, s)
161 $.ui.status(`Kontext ${percent}% ${balken(percent)}`)
162 lastRank = stufe(percent).rank
163
164 const st = stufe(percent)
165 const stand_ = s.tokens === 0
166 ? 'Noch keine Antwort in dieser Session gemessen, die Leiste füllt sich nach der nächsten Antwort.'
167 : `Stand: ${percent}% (${k(s.tokens)} von ${k(s.window)} Tokens). ${st.text}.`
168
169 return { text: `Kontext-Wächter ${hidden ? 'eingeblendet' : 'ausgeblendet'}. ${stand_}` }
170 })
171
172 on('command.run', { command: 'uebergabe' }, async $ => {
173 const { value: busy = false } = await $.state.get(isBusy)
174 if (busy) {
175 return { text: 'Die Übergabe wird gerade erstellt.' }
176 }
177 await $.state.set(isBusy, true)
178 const r = await $.model.fork({ prompt: UEBERGABE_PROMPT })
179 await $.state.set(isBusy, false)
180
181 if (!r.isAnswered) {
182 return { text: `Übergabe nicht möglich (${r.reason}). Nach der ersten Antwort noch einmal versuchen.` }
183 }
184
185 const jetzt = await $.clock.now()
186 const d = dateiname(jetzt)
187 const doc = `<!-- erstellt: ${jetzt} -->\n${r.text.trim()}\n`
188 await $.fs.write(`${ORDNER}/uebergabe-${d}.md`, doc)
189 await $.fs.write(LETZTE, doc)
190
191 return {
192 text:
193 `Übergabe gespeichert in ${LETZTE} (Kopie: uebergabe-${d}.md). ` +
194 'In der neuen Session einfach /weiter eingeben.',
195 }
196 })
197
198 on('command.run', { command: 'weiter' }, async $ => {
199 let doc = ''
200 try {
201 doc = await $.fs.read(LETZTE)
202 } catch {
203 return { text: `Keine Übergabe gefunden (${LETZTE}). Erst /uebergabe in der alten Session ausführen.` }
204 }
205 await $.fs.write(GELESEN, stempel(doc))
206 void $.prompt.submit({
207 text:
208 'Hier ist die Übergabe der vorherigen Session. Lies sie, fasse in zwei Sätzen zusammen, ' +
209 'wo wir stehen, und mach dann mit dem ersten nächsten Schritt weiter.\n\n' +
210 doc,
211 })
212
213 return { text: 'Übergabe wird geladen.' }
214 })
215
216 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
217 const { value: s = null } = await $.state.get(stand)
218 const { value: hidden = false } = await $.state.get(isHidden)
219 const { value: busy = false } = await $.state.get(isBusy)
220
221 if (e.props.hasSurvey || s === null || hidden) {
222 return next(e)
223 }
224
225 const { Box, Text, Button } = $.ui.resolve(e)
226 // Erstellt die Übergabe; mit neu=true bittet es danach Claude, eine neue
227 // Session zu öffnen (das kann nur die Desktop-App, die Mod selbst nicht).
228 const erstellen = async (neu: boolean) => {
229 await $.state.set(isBusy, true)
230 const r = await $.model.fork({ prompt: UEBERGABE_PROMPT })
231 await $.state.set(isBusy, false)
232 if (!r.isAnswered) {
233 $.ui.toast(`Übergabe nicht möglich (${r.reason}).`)
234 return
235 }
236 const jetzt = await $.clock.now()
237 const doc = `<!-- erstellt: ${jetzt} -->
238${r.text.trim()}
239`
240 await $.fs.write(`${ORDNER}/uebergabe-${dateiname(jetzt)}.md`, doc)
241 await $.fs.write(LETZTE, doc)
242
243 if (!neu) {
244 $.ui.toast('Übergabe gespeichert. In der neuen Session /weiter eingeben.')
245 return
246 }
247
248 const cwd = await $.session.cwd()
249 await $.fs.write(GELESEN, stempel(doc))
250 $.ui.toast('Übergabe gespeichert, neue Session wird geöffnet.')
251 void $.prompt.submit({
252 text:
253 `Die Übergabe liegt in ${cwd}/${LETZTE}. Öffne jetzt mit dem Werkzeug zum Starten einer Session ` +
254 `eine neue Session im Ordner ${cwd} (frischer Kontext, ohne diesen Verlauf), deren Auftrag lautet: ` +
255 `"Lies ${cwd}/${LETZTE}, fasse in zwei Sätzen zusammen, wo wir stehen, und mach mit dem ersten ` +
256 `nächsten Schritt weiter." Antworte hier danach nur mit einem Satz, dass die neue Session läuft. ` +
257 'Gibt es dieses Werkzeug nicht, sag das kurz und nenne den Befehl /weiter für die neue Session.',
258 })
259 }
260
261 const st = stufe(s.percent)
262 const cost = s.usd === null ? '' : ` · $${s.usd.toFixed(2)}`
263 const runs = s.agentRuns ?? 0
264 const agents = runs === 0 ? '' : ` · Agenten ${runs}×, ${k(s.agentTokens ?? 0)}`
265
266 return (
267 <Box>
268 <Text color={st.color}>
269 {balken(s.percent)} {s.percent}%{' '}
270 </Text>
271 <Text dimColor>
272 ({k(s.tokens)}/{k(s.window)}
273 {cost}
274 {agents}){' '}
275 </Text>
276 <Text color={st.color}>{st.text} </Text>
277 {busy ? (
278 <Text dimColor>Übergabe wird erstellt… </Text>
279 ) : (
280 <Box flexDirection="row" flexShrink={0}>
281 <Button key="uebergabe" label="Übergabe" onPress={() => erstellen(false)} />
282 <Text> </Text>
283 {e.surface === 'desktop' && (
284 <Button key="neu" label="+ neue Session" onPress={() => erstellen(true)} />
285 )}
286 <Text> </Text>
287 </Box>
288 )}
289 <Button key="hide" label="✕" onPress={() => $.state.set(isHidden, true)} />
290 </Box>
291 )
292 })
293}
294types/index.d.ts 16 lines1export type Stand = {
2 percent: number
3 tokens: number
4 window: number
5 usd: number | null
6 turns: number
7 agentRuns: number
8 agentTokens: number
9}
10
11declare module 'claude-code' {
12 interface PluginState {
13 'kontext-waechter': { stand: Stand | null; isHidden: boolean; isBusy: boolean }
14 }
15}
16