Claude Code skill that builds, debugs, audits and packages Claude Code mods from a plain-language description

A thin bar above the box where you type that shows how full Claude's memory of the conversation is.
Claude Code is Anthropic's assistant for working with code, in the terminal or in the Claude desktop app. A mod is a small add-on for it: it can add a panel, a bar above the box where you type, a new /command, or a rule that checks what Claude is about to do. This folder is one complete mod you can install and use as it is, or copy as a starting point.
Claude can only keep a limited amount of text in mind at once. That limit is called the context window. When it fills up, Claude Code compacts the conversation (it summarizes older parts to make room), and some detail can be lost. This mod shows, all the time, how much of that space is in use, so you know when it is a good moment to start a fresh conversation or wrap up a task.
/context-band.| Key | What it does |
|---|---|
Tab then Enter | Reach the "hide" button and press it |
| Type | What it does |
|---|---|
/context-band | Show the bar again, or hide it if it is showing |
Claude Code asks for these when you enable the mod. To change them later, open /config and find the mod by name.
| Setting | What it means | Default |
|---|---|---|
language (Language) | Language of the text the mod shows: en (English) or pt-BR (Brazilian Portuguese). | en |
You need Claude Code 2.1.287 or newer in the terminal, or 2.1.286 or newer in the desktop app.
_claude-plugin to .claude-plugin (with a dot at the start). It ships without the dot so the examples never load by accident.claude --plugin-dir path/to/context-band
To keep it installed for good, ask the claude-mod-forge skill to package it ("package this mod").
From the skill folder (skills/claude-mod-forge), this runs the manifest check, a strict type check and the 4 automated tests of this mod:
node scripts/check-mod.mjs examples/context-band
The tests cover the mod's logic and what it draws in the terminal and the desktop app. How it looks in your own window is worth a quick look in a real session.
For developers: the examples table lists the hooks and API calls this mod demonstrates.
hooks/register.tsx 97 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Reading } from '../types'
5import { strings } from './i18n'
6import type { Strings } from './i18n'
7
8// Data source: $.session.usage().context — the same figures the status line shows.
9// No estimate, no made-up number: when the call gives nothing, the band shows nothing.
10const HISTORY = 12
11const BARS = '▁▂▃▄▅▆▇█'
12
13const readings = atom({ plugin: 'context-band', key: 'readings' } as const, [])
14const isHidden = atom({ plugin: 'context-band', key: 'isHidden' } as const, false)
15
16function level(percent: number, t: Strings): { color: string; word: string } {
17 if (percent < 50) return { color: 'success', word: t.roomy }
18 if (percent < 75) return { color: 'warning', word: t.filling }
19 if (percent < 90) return { color: 'warning', word: t.tight }
20 return { color: 'error', word: t.compactSoon }
21}
22
23function short(n: number): string {
24 if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`
25 if (n >= 1_000) return `${(n / 1_000).toFixed(1)}k`
26 return String(n)
27}
28
29function chart(list: Reading[]): string {
30 const top = Math.max(...list.map(r => r.tokens), 1)
31 return list.map(r => BARS[Math.min(7, Math.floor((r.tokens / top) * 7))] ?? BARS[0]).join('')
32}
33
34async function takeReading($: EngineInterface) {
35 try {
36 const { context } = await $.session.usage()
37 if (!context.window || context.tokens === undefined) return
38 const percent = Math.round(context.percent ?? (context.tokens / context.window) * 100)
39 const reading: Reading = { tokens: context.tokens, window: context.window, percent }
40 // The reading before the first response is 0; drop zeros once real ones arrive.
41 await update($, readings, list => [...list.filter(r => r.tokens > 0), reading].slice(-HISTORY))
42 } catch {
43 // No reading this time; the band keeps the last one.
44 }
45}
46
47export const register: Register = (on, options) => {
48 const t = strings(options.language)
49
50 on('session.start', async ($, e, next) => {
51 const result = await next(e)
52 await takeReading($)
53 try {
54 // The way back after "hide": every control needs an undo.
55 await $.command.register({ name: 'context-band', description: t.commandDescription, immediate: true })
56 } catch {
57 // Name taken: the band still works, only the toggle is missing.
58 }
59 return result
60 })
61
62 on('command.run', { command: 'context-band' }, async $ => {
63 const hidden = await read($, isHidden)
64 await update($, isHidden, () => !hidden)
65 return { text: hidden ? t.shown : t.hidden }
66 })
67
68 on('turn.complete', async ($, e, next) => {
69 const result = await next(e)
70 if (e.agentId === undefined) await takeReading($) // subagent turns use their own window
71 return result
72 })
73
74 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
75 const list = await read($, readings)
76 const now = list[list.length - 1]
77 if (e.props.hasSurvey || now === undefined || (await read($, isHidden))) return next(e)
78
79 const { Box, Button, Text } = $.ui.resolve(e)
80 const lv = level(now.percent, t)
81 const prev = list[list.length - 2]
82 const delta = prev === undefined ? 0 : now.tokens - prev.tokens
83 const isWide = e.props.bodyColumns >= 70
84
85 return (
86 <Box gap={1} paddingX={1}>
87 <Text color={lv.color} bold>● {now.percent}%</Text>
88 <Text dimColor>{t.context} · {lv.word}</Text>
89 <Text dimColor>{short(now.tokens)} / {short(now.window)}</Text>
90 {isWide && list.length > 1 && <Text color="claude">{chart(list)}</Text>}
91 {isWide && delta !== 0 && <Text dimColor>{delta > 0 ? `▲ +${short(delta)}` : `▼ ${short(-delta)}`} {t.lastTurn}</Text>}
92 <Button key="hide" label={t.hide} plain dimColor onPress={() => update($, isHidden, () => true)} />
93 </Box>
94 )
95 })
96}
97hooks/i18n.ts 38 lines1// The text this mod shows, per language. Pure: no $ calls, so tests can cover it directly.
2// Anything other than a known language falls back to English.
3
4export type Language = 'en' | 'pt-BR'
5
6const en = {
7 roomy: 'roomy',
8 filling: 'filling',
9 tight: 'tight',
10 compactSoon: 'compact soon',
11 context: 'context',
12 lastTurn: 'last turn',
13 hide: 'hide (/context-band)',
14 commandDescription: 'Show or hide the context band above the prompt',
15 shown: 'Context band shown.',
16 hidden: 'Context band hidden. Run /context-band to show it again.',
17}
18
19const ptBR: typeof en = {
20 roomy: 'folgado',
21 filling: 'enchendo',
22 tight: 'apertado',
23 compactSoon: 'compactar logo',
24 context: 'contexto',
25 lastTurn: 'no último turno',
26 hide: 'ocultar (/context-band)',
27 commandDescription: 'Mostra ou oculta a faixa de contexto acima do prompt',
28 shown: 'Faixa de contexto visível.',
29 hidden: 'Faixa de contexto oculta. Use /context-band para mostrar de novo.',
30}
31
32export type Strings = typeof en
33const TABLE: Record<Language, Strings> = { en, 'pt-BR': ptBR }
34
35export function strings(language: unknown): Strings {
36 return language === 'pt-BR' ? TABLE['pt-BR'] : TABLE.en
37}
38types/index.d.ts 8 lines1export type Reading = { tokens: number; window: number; percent: number }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'context-band': { readings: Reading[]; isHidden: boolean }
6 }
7}
8