One-line session bar above the prompt: model, effort, repo, branch, context gauge, prompt cache health, rate limits, cost

Six mods for Claude Code: a one-line status bar, image previews for pasted screenshots, boxed cards for tool calls, a keepalive for the prompt cache, a weekly calendar of your sessions, and animated diagrams Claude can draw in the transcript.
Mods are Claude Code plugins built from function hooks. They need Claude Code 2.1.287 or newer. Mods are an early-access feature: the hooks API can change between releases.
| Mod | What it does |
|---|---|
slick-bar | Replaces the hint line under the prompt with a one-line bar: model, effort, folder, branch, context gauge, prompt-cache health, rate-limit gauges |
image-peek | Shows a preview of a pasted image above the prompt and under the message that sent it |
tool-cards | Draws each tool call as a boxed card: highlighted command, output preview, timing footer |
cache-warm | Keeps the prompt cache of an idle chat warm with a small capped ping, and counts cache hits and misses |
week-calendar | /week draws a calendar of the week's sessions with commits, hours per project, and a 3-line report |
diagram-mod | Gives Claude a show_diagram tool: animated diagrams that play a story step by step, with packets, gauges and a live log |
git clone https://github.com/mustafa89/my-claude-code-mods.git ~/.claude/mods
env block of ~/.claude/settings.json (paths separated by :; ~ is allowed): {
"env": {
"CLAUDE_CODE_PLUGIN_DIRS": "~/.claude/mods/slick-bar:~/.claude/mods/image-peek:~/.claude/mods/tool-cards:~/.claude/mods/cache-warm:~/.claude/mods/week-calendar:~/.claude/mods/diagram-mod"
}
}
Leave out the mods you do not want. slick-bar lists cache-warm as a dependency for its cache segment, so load the two together.
<name>: loaded in the transcript.To try one mod for a single session only:
claude --plugin-dir ~/.claude/mods/slick-bar
If you use a statusLine command, the bar does not replace it. The bar sits on the hint line under the prompt; your status line stays below it. Remove statusLine from your settings if you want only the bar.

One line under the prompt, after Claude Code's own permission-mode label:
✻ Opus 5.5 1M · medium. The ✻ turns into ◉ while a turn runs.± when the tree has changes, ↑/↓ for commits ahead of or behind the upstream.↻ 3h12m), and a ┃ tick that marks how much of the window has passed. Usage to the right of the tick means you are using it faster than the window allows; the percentage turns amber when the current pace projects past 100%.cache ● 97% 52m ✗1 ⟳2. The dot is green while the cache is warm, amber in the last minutes before it expires, and ○ cold after. Then the share of the last request served from cache, the time until the cache expires, misses (✗) and keepalive pings (⟳).When the terminal is narrow, segments drop in this order: hint text, 7-day gauge, token count, cache, branch, folder. The model and the context gauge always stay.
The bar needs a Nerd Font for the pill shapes and icons.

Image #3 · 1246×846).image-peek draws real images through the kitty graphics protocol, so it needs a terminal that supports it, such as kitty or Ghostty. Elsewhere it shows the image's label instead.
It also needs macOS: it uses sips to make a smaller copy of each image.
Terminal multiplexers: inside a multiplexer that passes kitty graphics through but identifies itself differently (for example herdr, which reports libghostty), Claude Code turns graphics off. To turn them on, add this to the same env block:
"CLAUDE_CODE_FORCE_TERMINAL_IMAGES": "1"
Only do this if your terminal really supports kitty graphics. Otherwise images draw as empty boxes.

Every tool call becomes a card:
✓ done, ◌ running, ✗ error, ⊘ interrupted. The command's description follows.+N -M header with a bar shows the size of the change. Narrow terminals stack old over new. A new file from Write keeps Claude Code's own view.▾ output · N lines. JSON is indented; errors show in red.src/cart/total.js:10-30 for a Read.◇ diagram, as wide as the diagram, with the animation inside and ▴ collapse / ▾ expand.Claude Code normally folds runs of reads and searches into one line (Searched for 2 patterns). tool-cards unfolds them, so each call gets its own card.
▾ expand on a card to see all of its output or diff (up to 400 lines). Clicks reach the card in fullscreen mode./cards full expands every card; /cards compact goes back to the preview (5 output lines, 16 diff rows, MCP output folded); /cards switches between the two.Ctrl+o does not expand a card. Claude Code does not tell mods when the Ctrl+o view is open.

Claude Code caches the conversation on the API side. A request that reads the cache pays a small part of the input price; a request after the cache expired writes the whole conversation again. cache-warm sends one small request just before the cache expires, so an idle chat stays warm.
$.model.fork. The fork sends the session's last request again with a one-line prompt. The API reads the conversation from the cache, and that read starts the cache lifetime again. The fork runs no tools and does not show in the transcript.Prompt caching prices, as a multiple of the normal input price:
| Request | Price |
|---|---|
| Cache write, 1-hour cache | 2× |
| Cache write, 5-minute cache | 1.25× |
| Cache read | 0.1× (Opus 5.5: 0.05×, Fable 5.1: 0.025×) |
Example: a 400k-token chat on Opus 5.5 with the 1-hour cache.
A ping costs 1/40 of a cold restart. Four pings cost 80k, one tenth of a restart, even if you never come back to the chat. The keepalive pays for itself when there is a better than 1-in-40 chance that you come back within the next hour. At the general read price of 0.1×, the break-even is 20 pings.
maxPings pings (default 4) per idle stretch. A prompt you type resets the count.| Setting | Values | Default |
|---|---|---|
ttl | 1h, 5m | 1h |
maxPings | a number | 4 |
Change them in /config. Claude Code sessions use the 1-hour cache on most plans. Use 5m for the API default, or when your account runs in usage overage.
/cache-warm status shows the cache state, /cache-warm off stops the pings, /cache-warm on starts them again.

The screenshot shows a real week with every title, project and commit replaced.
/week builds a calendar of this week's Claude Code and Codex sessions, opens it in your browser, and prints a 3-line report:
Shipped: rate limiting, CI runner migration, deploy runbook
Most time: api-service, 22h 27m
Next: Prototype caching layer, Upgrade database driver
/week last shows last week; /week -2 goes two weeks back.
git config user.email.bin/build.mjs reads ~/.claude/projects/**/*.jsonl and ~/.codex/sessions/**/*.jsonl, runs git log in each project, and writes one HTML file to ~/.calendar/week-<monday>.html. Node only, no network.The calendar file holds the first message of each session as plain text. It stays on your machine.
~/.calendar/config.json is created on the first run:
| Setting | Default | What it does |
|---|---|---|
dayStartHour | 6 | Work before this hour counts toward the previous day |
gapMinutes | 30 | A gap longer than this starts a new block |
minNoCommitMinutes | 15 | Shorter blocks are not flagged as ending without a commit |
weekStart | mon | mon or sun |
theme | dark | dark or light |
accent, palette | Colours for today, the busiest day, and the projects |
It needs Node and git. It opens the file with open on macOS or xdg-open on Linux.

Ask Claude for a diagram or a visual explanation and it calls the mod's show_diagram tool. The diagram draws in the tool's row in the transcript:
✓ ok, ✗ error, ! warning.meters) that fill in eighths of a cell, and a moving activity wave (spark).side: "left" or "right" becomes a tall panel beside the tree, with rows a step can highlight.Give the spec steps and the diagram plays them in a loop. Each step names the edges carrying traffic (only those light up), changes node statuses, meters and panel highlights, adds log rows, and moves the counters. Meters ease to their new values and counters count up across the step. Without steps, packets loop on the edges you name.
examples/agent-tree.json is the story in the GIF; examples/dispatcher.json is a simple diagram without steps. The bundled skill tells Claude when to use the tool and how to keep a diagram readable: one idea, about 12 nodes at most, labels of 1-3 words, 3-6 steps.
| Setting | Default | What it does |
|---|---|---|
display | inline | inline: the diagram animates in the transcript row. pane: it opens in a side pane, closed with q or Esc (Claude Code only seats a pane it did not ask for from 144 columns) |
Each mod is a folder:
<mod>/
.claude-plugin/plugin.json name, version, description, types
hooks/hooks.json { "modules": ["./register.tsx"] }
hooks/register.tsx the hooks
types/index.d.ts the state the mod keeps
tests/*.test.tsx tests
Check and test a mod:
claude plugin validate ./slick-bar
claude plugin test ./slick-bar
Claude Code writes the API types into <mod>/.claude-plugin/types/ the first time it loads a mod; those files are not committed. A session started with --plugin-dir reloads a mod when you save its files.
These come from the mods API, not from the mods:
Ctrl+o: tool rows do not report the expanded view (see Expanding output).ttl setting.mcp__<mod>__<tool>, so diagram-mod's tool is mcp__diagram-mod__show_diagram though no MCP server is involved.hooks/register.tsx 369 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register, RenderElement } from 'claude-code'
3
4import type { Git, Usage } from '../types'
5
6const git = atom({ plugin: 'slick-bar', key: 'git' } as const, null)
7const usage = atom({ plugin: 'slick-bar', key: 'usage' } as const, null)
8const model = atom({ plugin: 'slick-bar', key: 'model' } as const, '')
9const effort = atom({ plugin: 'slick-bar', key: 'effort' } as const, '')
10// Written by the cache-warm mod; undefined while it is not loaded.
11const cache = { plugin: 'cache-warm', key: 'health' } as const
12
13const C = {
14 accent: '#D97757',
15 ink: '#1A1B1F',
16 surface: '#2A2E37',
17 text: '#D7DCE4',
18 muted: '#7F8796',
19 track: '#3B414D',
20 green: '#7EE787',
21 amber: '#F2CC60',
22 red: '#FF7B72',
23 blue: '#79C0FF',
24}
25
26// Nerd Font glyphs: rounded pill caps, folder, branch.
27const CAP_L = ''
28const CAP_R = ''
29const ICON_DIR = ''
30const ICON_BRANCH = ''
31
32const GIT_TOOLS = new Set(['Bash', 'Edit', 'Write', 'NotebookEdit'])
33const LIMIT_LABEL: Record<string, string> = { five_hour: '5h', seven_day: '7d' }
34const WINDOW_MS: Record<string, number> = { five_hour: 5 * 3_600_000, seven_day: 7 * 24 * 3_600_000 }
35const MAX_BRANCH = 24
36// The footer line starts with the engine's own mode label (`⏵⏵ auto mode on · `); keep room for it.
37const MODE_COLS = 22
38const CYCLE_HINT = '(shift+tab to cycle)'
39
40type $ = EngineInterface
41
42async function refreshGit($: $) {
43 const cwd = await $.session.cwd()
44 const run = (argv: string[]) => $.process.run(['git', ...argv], { cwd, timeoutMs: 5000 })
45 const top = await run(['rev-parse', '--show-toplevel'])
46
47 if (top.exitCode !== 0) {
48 const home = await $.env.get('HOME')
49 const dir = home !== undefined && cwd.startsWith(home) ? `~${cwd.slice(home.length)}` : cwd
50 await update($, git, () => ({ dir, branch: null, isDirty: false, ahead: 0, behind: 0 }))
51 return
52 }
53
54 const root = top.stdout.trim()
55 const sub = cwd.slice(root.length).replace(/^\//, '')
56 const repo = root.split('/').pop() ?? root
57 const status = await run(['status', '--porcelain=v2', '--branch'])
58 const next: Git = { dir: sub ? `${repo}/${sub}` : repo, branch: null, isDirty: false, ahead: 0, behind: 0 }
59
60 for (const line of status.stdout.split('\n')) {
61 if (line.startsWith('# branch.head ')) {
62 next.branch = line.slice(14)
63 } else if (line.startsWith('# branch.oid ') && next.branch === '(detached)') {
64 next.branch = line.slice(13, 20)
65 } else if (line.startsWith('# branch.ab ')) {
66 const [a = '+0', b = '-0'] = line.slice(12).split(' ')
67 next.ahead = Number(a.slice(1))
68 next.behind = Number(b.slice(1))
69 } else if (line !== '' && !line.startsWith('#')) {
70 next.isDirty = true
71 }
72 }
73
74 await update($, git, () => next)
75}
76
77async function refreshUsage($: $) {
78 const [u, m] = await Promise.all([$.session.usage(), $.session.model()])
79 const next: Usage = {
80 pct: u.context.percent ?? null,
81 tokens: u.context.tokens ?? null,
82 window: u.context.window,
83 limits: u.rateLimits.map(l => ({
84 kind: l.kind,
85 pct: l.percentUsed,
86 resetsAt: l.resetsAt !== undefined ? Date.parse(l.resetsAt) : null,
87 })),
88 }
89 await update($, usage, () => next)
90 await update($, model, () => m)
91}
92
93const refreshAll = ($: $) => Promise.all([refreshGit($), refreshUsage($)])
94
95// claude-opus-5-5[1m] -> Opus 5.5 · 1M
96function prettyModel(id: string) {
97 const m = /claude-([a-z]+)-(\d+)(?:-(\d+))?/.exec(id)
98 const name = m ? `${m[1]![0]!.toUpperCase()}${m[1]!.slice(1)} ${m[2]}${m[3] ? `.${m[3]}` : ''}` : id
99 return /\[1m\]/i.test(id) && m ? `${name} 1M` : name
100}
101
102function compact(n: number) {
103 if (n >= 1_000_000) return `${+(n / 1_000_000).toFixed(1)}M`
104 if (n >= 1_000) return `${Math.round(n / 1_000)}k`
105 return String(n)
106}
107
108function level(pct: number) {
109 return pct < 50 ? C.green : pct < 80 ? C.amber : C.red
110}
111
112function hex(c: string) {
113 return parseInt(c.slice(1), 16)
114}
115
116function lerp(a: number, b: number, t: number) {
117 const ch = (s: number) => Math.round(((a >> s) & 255) + (((b >> s) & 255) - ((a >> s) & 255)) * t)
118 return (ch(16) << 16) | (ch(8) << 8) | ch(0)
119}
120
121// Heat gradient by position along the track: green -> amber -> red.
122function heat(t: number) {
123 return t < 0.5 ? lerp(hex(C.green), hex(C.amber), t * 2) : lerp(hex(C.amber), hex(C.red), (t - 0.5) * 2)
124}
125
126// 3h12m, 42m, 2d4h
127function countdown(ms: number) {
128 const mins = Math.ceil(ms / 60_000)
129 if (mins < 60) return `${mins}m`
130 const hours = Math.floor(mins / 60)
131 if (hours < 24) return `${hours}h${mins % 60 ? `${mins % 60}m` : ''}`
132 return `${Math.floor(hours / 24)}d${hours % 24 ? `${hours % 24}h` : ''}`
133}
134
135// Slim gauge cells: '━' filled, '╸' half step, '━' in track colour for the rest;
136// `marker` (0..1) draws a '┃' tick where an even pace through the window would be.
137function gaugeCells(pct: number, columns: number, marker: number | null = null) {
138 // Anything above 0% shows at least a half step.
139 const exact = (Math.min(Math.max(pct, 0), 100) / 100) * columns * 2
140 const halves = pct > 0 ? Math.max(1, Math.round(exact)) : 0
141 const words = new Uint32Array(columns * 3)
142 for (let i = 0; i < columns; i++) {
143 const t = columns === 1 ? 0 : i / (columns - 1)
144 const isFull = i * 2 + 2 <= halves
145 const isHalf = !isFull && i * 2 + 1 === halves
146 words[i * 3] = isHalf ? 0x2578 : 0x2501
147 words[i * 3 + 1] = isFull || isHalf ? heat(t) : hex(C.track)
148 words[i * 3 + 2] = 0x01000000
149 }
150 if (marker !== null) {
151 const i = Math.min(columns - 1, Math.floor(marker * columns))
152 words[i * 3] = 0x2503
153 words[i * 3 + 1] = hex(C.text)
154 }
155 let bin = ''
156 for (const b of new Uint8Array(words.buffer)) bin += String.fromCharCode(b)
157 return btoa(bin)
158}
159
160function textGauge(pct: number, columns: number) {
161 const filled = Math.round((Math.min(Math.max(pct, 0), 100) / 100) * columns)
162 return { on: '━'.repeat(filled), off: '━'.repeat(columns - filled) }
163}
164
165const width = (s: string) => [...s].length
166
167function clip(s: string, max: number) {
168 return width(s) <= max ? s : `${[...s].slice(0, max - 1).join('')}…`
169}
170
171type Seg = { key: string; rank: number; w: number; node: RenderElement }
172
173export const register: Register = on => {
174 on('session.start', async ($, e, next) => {
175 const result = await next(e)
176 await refreshAll($)
177 $.clock.every(5000, () => void refreshAll($).catch(() => undefined))
178 return result
179 })
180
181 on('prompt.submit', async ($, e, next) => {
182 void refreshUsage($).catch(() => undefined)
183 return next(e)
184 })
185
186 on('turn.step', async function* ($, e, next) {
187 if (e.agentId === undefined && e.effort !== undefined) {
188 await update($, effort, () => String(e.effort))
189 }
190 const result = yield* next(e)
191 if (e.agentId === undefined) await refreshUsage($)
192 return result
193 })
194
195 on('tool.call', async ($, e, next) => {
196 const result = await next(e)
197 if (GIT_TOOLS.has(String(e.tool))) await refreshGit($)
198 return result
199 })
200
201 on('turn.complete', async ($, e, next) => {
202 const result = await next(e)
203 await refreshAll($)
204 return result
205 })
206
207 // The bar is the footer line under the prompt, after the engine's mode label.
208 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
209 const [g, u, m, eff, { value: ch }] = await Promise.all([
210 read($, git),
211 read($, usage),
212 read($, model),
213 read($, effort),
214 $.state.get(cache),
215 ])
216 if (u === null && m === '') return next(e)
217
218 const { Box, Text } = $.ui.resolve(e)
219 const Raster = e.surface === 'terminal' ? $.ui.resolve(e).Raster : undefined
220 const cols = (e.viewport?.columns ?? 120) - MODE_COLS
221
222 const pill = (key: string, bg: string, fg: string, body: string, bold = false): Seg => ({
223 key,
224 rank: 0,
225 w: width(body) + 4,
226 node: (
227 <Box key={key}>
228 <Text color={bg}>{CAP_L}</Text>
229 <Text backgroundColor={bg} color={fg} bold={bold}>
230 {` ${body} `}
231 </Text>
232 <Text color={bg}>{CAP_R}</Text>
233 </Box>
234 ),
235 })
236
237 const gauge = (
238 key: string,
239 label: string,
240 pct: number,
241 gcols: number,
242 extra = '',
243 marker: number | null = null,
244 tone = level(pct),
245 ): Seg => {
246 const bar =
247 Raster !== undefined ? (
248 <Raster key={`${key}-r`} columns={gcols} rows={1} cells={gaugeCells(pct, gcols, marker)} />
249 ) : (
250 <Text>
251 <Text color={level(pct)}>{textGauge(pct, gcols).on}</Text>
252 <Text color={C.track}>{textGauge(pct, gcols).off}</Text>
253 </Text>
254 )
255 const pctText = `${Math.round(pct)}%`
256 return {
257 key,
258 rank: 0,
259 w: width(label) + 1 + gcols + 1 + width(pctText) + (extra ? 1 + width(extra) : 0),
260 node: (
261 <Box key={key} gap={1}>
262 <Text color={C.muted}>{label}</Text>
263 {bar}
264 <Text color={tone} bold>
265 {pctText}
266 </Text>
267 {extra ? <Text color={C.muted}>{extra}</Text> : null}
268 </Box>
269 ),
270 }
271 }
272
273 const left: Seg[] = []
274 const right: Seg[] = []
275 const now = await $.clock.now()
276
277 const dot = e.props.isWorking ? '◉' : '✻'
278 left.push(pill('model', C.accent, C.ink, `${dot} ${prettyModel(m)}${eff ? ` · ${eff}` : ''}`, true))
279
280 if (g !== null) {
281 left.push({ ...pill('dir', C.surface, C.blue, `${ICON_DIR} ${g.dir}`), rank: 2 })
282 if (g.branch !== null) {
283 const sync = `${g.ahead ? ` ↑${g.ahead}` : ''}${g.behind ? ` ↓${g.behind}` : ''}`
284 const body = `${ICON_BRANCH} ${clip(g.branch, MAX_BRANCH)}${g.isDirty ? ' ±' : ''}${sync}`
285 left.push({ ...pill('branch', C.surface, g.isDirty ? C.amber : C.green, body), rank: 3 })
286 }
287 }
288
289 if (u !== null) {
290 if (u.pct !== null) {
291 const tok = u.tokens !== null ? `${compact(u.tokens)}/${compact(u.window)}` : ''
292 right.push(gauge('ctx', 'ctx', u.pct, 6))
293 if (tok) right.push({ key: 'tok', rank: 5, w: width(tok), node: <Text key="tok" color={C.muted}>{tok}</Text> })
294 }
295 // Prompt cache: hit share of the last request, time to expiry, misses and keepalive pings.
296 if (ch !== undefined && ch.lastAt > 0) {
297 const remain = ch.ttlMs - (now - ch.lastAt)
298 const isWarm = ch.isWarm && remain > 0
299 const tone = !isWarm ? C.blue : remain < ch.marginMs ? C.amber : C.green
300 const parts = [
301 { text: 'cache', color: C.muted },
302 { text: isWarm ? '●' : '○', color: tone },
303 ...(ch.lastRatio !== null ? [{ text: `${Math.round(ch.lastRatio * 100)}%`, color: level(100 - ch.lastRatio * 100) }] : []),
304 { text: isWarm ? countdown(remain) : 'cold', color: isWarm ? C.muted : C.blue },
305 ...(ch.misses > 0 ? [{ text: `✗${ch.misses}`, color: C.red }] : []),
306 ...(ch.pings > 0 ? [{ text: `⟳${ch.pings}`, color: C.muted }] : []),
307 ]
308 right.push({
309 key: 'cache',
310 rank: 4,
311 w: parts.reduce((n, x) => n + width(x.text), 0) + parts.length - 1,
312 node: (
313 <Box key="cache" gap={1}>
314 {parts.map((x, i) => (
315 <Text key={`cache-${i}`} color={x.color}>
316 {x.text}
317 </Text>
318 ))}
319 </Box>
320 ),
321 })
322 }
323 for (const l of u.limits) {
324 const label = LIMIT_LABEL[l.kind] ?? l.kind
325 const span = WINDOW_MS[l.kind]
326 const remain = l.resetsAt !== null ? l.resetsAt - now : null
327 // Share of the window gone; usage ahead of it projects past the limit before reset.
328 const elapsed = span !== undefined && remain !== null ? Math.min(Math.max(1 - remain / span, 0), 1) : null
329 const projected = elapsed !== null && elapsed > 0.05 ? l.pct / elapsed : l.pct
330 const tone = l.pct >= 80 ? C.red : projected >= 100 ? C.amber : C.green
331 const reset = remain !== null && remain > 0 ? `↻ ${countdown(remain)}` : ''
332 right.push({
333 ...gauge(`rl-${l.kind}`, label, l.pct, 6, reset, elapsed, tone),
334 rank: l.kind === 'five_hour' ? 1 : 6,
335 })
336 }
337 }
338
339 // The engine's hint (`esc to interrupt`, `← 1 agent`) goes last, first to drop.
340 const hint = e.props.hint
341 .split(' · ')
342 .filter(part => part !== '' && !part.includes(CYCLE_HINT))
343 .join(' · ')
344 if (hint) right.push({ key: 'hint', rank: 7, w: width(hint), node: <Text key="hint" color={C.muted}>{hint}</Text> })
345
346 // Drop the lowest-priority segments until the row fits on one line.
347 const all = [...left, ...right]
348 // Exact width: gap 1 between left pills, 2 between right gauges, at least 1 between the groups.
349 const row = (xs: Seg[], gap: number) => {
350 const kept = xs.filter(x => all.includes(x))
351 return kept.reduce((s, x) => s + x.w, 0) + Math.max(kept.length - 1, 0) * gap
352 }
353 const total = () => row(left, 1) + 1 + row(right, 2)
354 while (total() > cols) {
355 const victim = all.filter(x => x.rank > 0).sort((a, b) => b.rank - a.rank)[0]
356 if (victim === undefined) break
357 all.splice(all.indexOf(victim), 1)
358 }
359 const keep = (xs: Seg[]) => xs.filter(x => all.includes(x)).map(x => x.node)
360
361 return (
362 <Box flexDirection="row" justifyContent="space-between" width={cols}>
363 <Box gap={1}>{keep(left)}</Box>
364 <Box gap={2}>{keep(right)}</Box>
365 </Box>
366 )
367 })
368}
369types/index.d.ts 28 lines1export type Git = {
2 dir: string
3 branch: string | null
4 isDirty: boolean
5 ahead: number
6 behind: number
7}
8
9export type Limit = { kind: string; pct: number; resetsAt: number | null }
10
11export type Usage = {
12 pct: number | null
13 tokens: number | null
14 window: number
15 limits: Limit[]
16}
17
18declare module 'claude-code' {
19 interface PluginState {
20 'slick-bar': {
21 git: Git | null
22 usage: Usage | null
23 model: string
24 effort: string
25 }
26 }
27}
28