SLOPSHOPPER

token-weather

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

newband
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · token-weather
› 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 ⟨Claude Code's own drawing⟩ ☁ Bewölkt (cloudy) 49% · 97,4k / 200k ▄▄ = ±0 letzte Runde (last turn) ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
⟨Claude Code's own drawing⟩ ☁ Bewölkt (cloudy) 49% · 97,4k / 200k ▄▄ = ±0 letzte Runde (last turn)
README

Claude-Code-Mods

token-weather

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üllstandAnzeige
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.

Installieren

Benötigt eine Claude-Code-Version mit Mod-Unterstützung (Function Hooks).

/plugin marketplace add reifen01/claude-mods
/plugin install token-weather@reifen01-mods

Wie es funktioniert

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

Tests

claude plugin test plugins/token-weather
claude plugin test plugins/blast-radius
claude plugin test plugins/replay

blast-radius

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 ]
ErkanntGemessen mit
rm -r / rm -rfdu, 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 / -fgit log HEAD..@{u} (Stand letzter fetch)
git branch -DCommits, die nur auf dem Branch liegen
find … -deletederselbe find mit -print
  • Die Karte erscheint als Pane, oder über dem Prompt, wenn kein Platz für ein Pane ist.
  • Abbrechen (Taste n, auch ✕ oder Esc): Claude bekommt eine Absage mit dem, was der Befehl angerichtet hätte.
  • Trotzdem ausführen (Taste j): der Befehl läuft normal.
  • Ohne Antwort nach 10 Minuten wird abgebrochen. Ohne Oberfläche (claude -p) läuft der Befehl wie ohne Mod.
  • Gemessen wird nur lesend; Muster wie *.log oder $VAR werden genannt, nicht ausgewertet.

Installieren:

/plugin install blast-radius@reifen01-mods

replay

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) ]
  • Erfasst werden Edit, Write und NotebookEdit, auch die von Subagenten derselben Runde.
  • Der Diff kommt aus dem Patch, den die Engine zum Edit liefert; nichts wird neu berechnet oder erneut gelesen.
  • Tasten im Pane: w weiter, z zurück, q oder Esc schließen.
  • Eine neue Runde mit Änderungen ersetzt die Wiedergabe; eine Runde ohne Änderungen lässt sie stehen.

Installieren:

/plugin install replay@reifen01-mods

Lizenz

MIT

Source 2 files
hooks/register.tsx 112 lines
1import { 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}
112
types/index.d.ts 15 lines
1export 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