Wetterbericht für das Kontextfenster: Füllstand, Tokens, Verlauf der letzten 12 Runden und Zuwachs pro Runde, über dem Prompt

Ein Wetterbericht für das Kontextfenster, als Zeile über dem Claude-Code-Prompt. Er aktualisiert sich nach jeder Runde.
☂ Regenschauer (showers) 67% · 134,4k / 200k ▂▆ ▲ +98,3k letzte Runde (last turn)
| Füllstand | Anzeige |
|---|---|
| unter 25 % | ☀ Heiter (clear), gelb |
| 25–49 % | ☁ Bewölkt (cloudy), cyan |
| 50–74 % | ☂ Regenschauer (showers), blau |
| 75–89 % | ☇ Gewitter (storm), magenta |
| ab 90 % | ↯ Bald komprimieren (compact soon), rot |
Danach folgen der Füllstand in Prozent, die belegten Tokens von der Fenstergröße, ein Verlauf der letzten 12 Runden (jeder Balken gemessen am ganzen Fenster) und der Zuwachs der letzten Runde. Nach einem Komprimieren zeigt er ▼ und den Rückgang.
Benötigt eine Claude-Code-Version mit Mod-Unterstützung (Function Hooks).
/plugin marketplace add reifen01/claude-mods
/plugin install token-weather@reifen01-mods
session.measure: Die Engine misst nach jeder Runde; ändert sich der Kontext, speichert der Mod den neuen Stand. Kostet keine Tokens.$.state: Die letzten 12 Stände, das Fenster und der Zuwachs. Ein Schreiben zeichnet die Zeile neu.ui.render auf AbovePrompt: zeichnet die Zeile unter dem, was andere Plugins dort zeichnen.claude plugin test plugins/token-weather
claude plugin test plugins/blast-radius
claude plugin test plugins/replay
Hält gefährliche Bash-Befehle von Claude an, bevor sie laufen, misst was sie anrichten würden, und fragt dich.
⚠ Rekursives Löschen angehalten
$ rm -rf build
Würde 1.204 Dateien und Ordner (48,3 MB) unwiderruflich löschen
build: 1.204 Einträge, 48,3 MB
[ Abbrechen ] [ Trotzdem ausführen ]
| Erkannt | Gemessen mit |
|---|---|
rm -r / rm -rf | du, find (Anzahl und Größe) |
git reset --hard [ref] | git status, git log ref..HEAD |
git clean -f… | git clean -n (Probelauf) |
git checkout -- …, git restore … | git diff |
git push --force / -f | git log HEAD..@{u} (Stand letzter fetch) |
git branch -D | Commits, die nur auf dem Branch liegen |
find … -delete | derselbe find mit -print |
n, auch ✕ oder Esc): Claude bekommt eine Absage mit dem, was der Befehl angerichtet hätte.j): der Befehl läuft normal.claude -p) läuft der Befehl wie ohne Mod.*.log oder $VAR werden genannt, nicht ausgewertet.Installieren:
/plugin install blast-radius@reifen01-mods
Nach jeder Runde merkt sich der Mod, welche Dateien Claude geändert hat. /replay öffnet ein Pane und blättert Schritt für Schritt durch die Diffs. Die Beschriftungen sind deutsch, mit dem englischen Begriff in Klammern.
Schritt (step) 2/5 …/src/app.ts (geändert (changed)) +12 −3
@@ -40,7 +40,16 @@
…
[ ◀ Zurück (back) ] [ Weiter (next) ▶ ] [ Schließen (close) ]
Edit, Write und NotebookEdit, auch die von Subagenten derselben Runde.w weiter, z zurück, q oder Esc schließen.Installieren:
/plugin install replay@reifen01-mods
MIT
hooks/register.tsx 112 lines1import { atom, read, update } from 'claude-code'
2import type { Register, SessionContextUsage } from 'claude-code'
3
4import type { Forecast } from '../types'
5
6const forecast = atom({ plugin: 'token-weather', key: 'forecast' } as const, null)
7
8const TURNS = 12
9const BLOCKS = '▁▂▃▄▅▆▇█'
10
11type Weather = { icon: string; word: string; color: string }
12
13export function weatherOf(percent: number): Weather {
14 if (percent < 25) return { icon: '☀', word: 'Heiter (clear)', color: 'yellow' }
15 if (percent < 50) return { icon: '☁', word: 'Bewölkt (cloudy)', color: 'cyan' }
16 if (percent < 75) return { icon: '☂', word: 'Regenschauer (showers)', color: 'blue' }
17 if (percent < 90) return { icon: '☇', word: 'Gewitter (storm)', color: 'magenta' }
18 return { icon: '↯', word: 'Bald komprimieren (compact soon)', color: 'red' }
19}
20
21// German style: 134400 → "134,4k", 200000 → "200k", 1000000 → "1M"
22export function tokensOf(n: number): string {
23 const abs = Math.abs(n)
24 const [value, unit] = abs >= 1_000_000 ? [n / 1_000_000, 'M'] : abs >= 1000 ? [n / 1000, 'k'] : [n, '']
25 return `${value.toFixed(unit ? 1 : 0).replace(/\.0$/, '').replace('.', ',')}${unit}`
26}
27
28// each bar is that turn's fill of the whole window, so a full row of █ is a full window
29export function sparklineOf(history: number[], window: number): string {
30 return history
31 .map(t => BLOCKS[Math.min(BLOCKS.length - 1, Math.max(0, Math.floor((t / window) * BLOCKS.length)))])
32 .join('')
33}
34
35export function deltaOf(delta: number): string {
36 if (delta > 0) return `▲ +${tokensOf(delta)} letzte Runde (last turn)`
37 if (delta < 0) return `▼ −${tokensOf(-delta)} letzte Runde (last turn)`
38 return '= ±0 letzte Runde (last turn)'
39}
40
41function percentOf(f: Forecast, tokens: number): number {
42 return Math.min(100, Math.round((tokens / f.window) * 100))
43}
44
45// one reading per turn: the input side of the turn's last response against the window
46function record(prev: Forecast | null, context: SessionContextUsage): Forecast | null {
47 const tokens = context.tokens
48 if (tokens === undefined || !context.window) return prev
49 const last = prev?.history.at(-1)
50 return {
51 history: [...(prev?.history ?? []), tokens].slice(-TURNS),
52 window: context.window,
53 delta: last === undefined ? null : tokens - last,
54 }
55}
56
57export const register: Register = on => {
58 // a resumed session already has a fill: start the chart from it
59 on('session.start', async ($, e, next) => {
60 const result = await next(e)
61 if ((await read($, forecast)) === null) {
62 const { context } = await $.session.usage()
63 await update($, forecast, f => record(f, context))
64 }
65 return result
66 })
67
68 // the engine measures after each main-thread turn; `context` in `changed` means the fill moved
69 on('session.measure', async ($, e, next) => {
70 if (e.changed.includes('context')) {
71 await update($, forecast, f => record(f, e.context))
72 }
73 return next(e)
74 })
75
76 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
77 const f = await read($, forecast)
78 const tokens = f?.history.at(-1)
79 if (!f || tokens === undefined || e.props.hasSurvey) {
80 return next(e)
81 }
82
83 const { Box, Text } = $.ui.resolve(e)
84 const percent = percentOf(f, tokens)
85 const w = weatherOf(percent)
86
87 const line = (
88 <Box key="token-weather" flexDirection="row" gap={2} width={e.props.bodyColumns}>
89 <Text color={w.color} bold wrap="truncate">{`${w.icon} ${w.word}`}</Text>
90 <Text wrap="truncate">
91 {`${percent}% · ${tokensOf(tokens)} / ${tokensOf(f.window)}`}
92 </Text>
93 <Text color={w.color} wrap="truncate">{sparklineOf(f.history, f.window)}</Text>
94 {f.delta === null ? null : (
95 <Text dimColor wrap="truncate">{deltaOf(f.delta)}</Text>
96 )}
97 </Box>
98 )
99
100 // what the host and other plugins draw here (plan-progress's bars) stays, above this line
101 const below = await next(e)
102 return below ? (
103 <Box flexDirection="column">
104 {below}
105 {line}
106 </Box>
107 ) : (
108 line
109 )
110 })
111}
112types/index.d.ts 15 lines1export type Forecast = {
2 /** Input tokens the context held after each of the last turns, oldest first (at most 12). */
3 history: number[]
4 /** The model's context window, in tokens. */
5 window: number
6 /** What the last turn added (negative after a compaction); null before the second reading. */
7 delta: number | null
8}
9
10declare module 'claude-code' {
11 interface PluginState {
12 'token-weather': { forecast: Forecast | null }
13 }
14}
15