SLOPSHOPPER

usage-limits

Grafický pruh s 5hodinovým a týdenním limitem a časem do resetu

newbandtoaststatustimer
v0.4.0no licenseupdated 2026-10-08kbwmpmrp2z-beep/claude-usage-limits
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · usage-limits
› 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 5h █████░░░░░░░░░░░ 31% ↻ NaNm ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
5h █████░░░░░░░░░░░ 31% ↻ NaNm
README

usage-limits

Mod pro Claude Code: nad polem pro zprávu ukazuje barevný pruh s 5hodinovým a týdenním limitem předplatného a časem do resetu. Když limit dosáhne 90 %, zobrazí upozornění.

5h ████░░░░░░░░░░░░ 23% ↻ 2h 15m    týden ███████░░░░░░░░░ 41% ↻ 2d 3h

Instalace

V Claude Code (terminál, desktopová aplikace nebo VS Code):

/plugin install usage-limits --marketplace kbwmpmrp2z-beep/claude-usage-limits

Potvrďte y a zvolte user scope. Repozitář je soukromý, takže počítač musí mít přístup ke GitHubu (např. gh auth login).

Poznámky

  • Vyžaduje aktuální Claude Code (claude update).
  • Limity existují jen u předplatného Pro/Max; s API klíčem se pruh nezobrazí.
  • Vypnutí: claude plugin disable usage-limits
Source 2 files
hooks/register.tsx 117 lines
1import { atom, read, update } from 'claude-code'
2import type { Register, EngineInterface, SessionRateLimit } from 'claude-code'
3
4import type { Reading } from '../types'
5
6const LABELS: Record<string, string> = { five_hour: '5h', seven_day: 'týden' }
7
8const reading = atom({ plugin: 'usage-limits', key: 'reading' } as const, { limits: [], now: 0 })
9
10export function until(resetsAt: string | undefined, now: number): string {
11  if (!resetsAt) return ''
12  const mins = Math.max(0, Math.round((Date.parse(resetsAt) - now) / 60000))
13  const d = Math.floor(mins / 1440)
14  const h = Math.floor((mins % 1440) / 60)
15  const m = mins % 60
16  if (d > 0) return `${d}d ${h}h`
17  if (h > 0) return `${h}h ${m}m`
18  return `${m}m`
19}
20
21export function windows(limits: SessionRateLimit[]): SessionRateLimit[] {
22  return limits
23    .filter(l => l.kind in LABELS)
24    .sort((a, b) => (a.kind === 'five_hour' ? -1 : b.kind === 'five_hour' ? 1 : 0))
25}
26
27export function bar(percent: number, width: number): [string, string] {
28  const filled = Math.min(width, Math.max(0, Math.round((percent / 100) * width)))
29  return ['█'.repeat(filled), '░'.repeat(width - filled)]
30}
31
32export function tone(percent: number): 'success' | 'warning' | 'error' {
33  return percent >= 90 ? 'error' : percent >= 70 ? 'warning' : 'success'
34}
35
36export const WARN_AT = 90
37
38// one warning per window per reset period: the key changes when the window resets
39export function toWarn(limits: SessionRateLimit[], warned: Set<string>): SessionRateLimit[] {
40  return windows(limits).filter(
41    l => l.percentUsed >= WARN_AT && !warned.has(`${l.kind}|${l.resetsAt ?? ''}`),
42  )
43}
44
45const warned = new Set<string>()
46
47// last reading from an earlier session: a window whose reset has passed starts over at 0 %
48export function revive(stored: unknown, now: number): SessionRateLimit[] {
49  if (!Array.isArray(stored)) return []
50  return windows(stored as SessionRateLimit[]).map(l =>
51    l.resetsAt && Date.parse(l.resetsAt) <= now ? { kind: l.kind, percentUsed: 0 } : l,
52  )
53}
54
55async function refresh($: EngineInterface, limits?: SessionRateLimit[], warn = true) {
56  const now = await $.clock.now()
57  for (const l of limits && warn ? toWarn(limits, warned) : []) {
58    warned.add(`${l.kind}|${l.resetsAt ?? ''}`)
59    const reset = until(l.resetsAt, now)
60    $.ui.toast(
61      `⚠️ Limit ${LABELS[l.kind]} je na ${Math.round(l.percentUsed)} %${reset ? ` (reset za ${reset})` : ''}`,
62      { timeoutMs: 10_000 },
63    )
64  }
65  await update($, reading, (r: Reading) => ({ limits: limits ?? r.limits, now }))
66}
67
68export const register: Register = on => {
69  on('session.start', async ($, e, next) => {
70    const result = await next(e)
71    // the first version wrote to the status line; the band replaces it
72    $.ui.status(undefined)
73    const live = (await $.session.usage()).rateLimits
74    if (windows(live).length > 0) await refresh($, live)
75    // nothing measured yet: show the last known reading until the first response
76    else await refresh($, revive(await $.store.get('last'), await $.clock.now()), false)
77    // the reset countdown moves even when no response arrives
78    $.clock.every(60_000, () => void refresh($))
79    return result
80  })
81
82  on('session.measure', async ($, e, next) => {
83    if (windows(e.rateLimits).length > 0) await $.store.set('last', e.rateLimits)
84    await refresh($, e.rateLimits)
85    return next(e)
86  })
87
88  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
89    const r = await read($, reading)
90    const shown = windows(r.limits)
91    if (e.props.hasSurvey || shown.length === 0) return next(e)
92
93    const { Box, Text } = $.ui.resolve(e)
94    const width = e.props.bodyColumns >= 90 ? 16 : 8
95
96    return (
97      <Box flexDirection="row" gap={4}>
98        {shown.map(l => {
99          const [full, empty] = bar(l.percentUsed, width)
100          const reset = until(l.resetsAt, r.now)
101          return (
102            <Box key={l.kind} flexDirection="row" gap={1}>
103              <Text bold>{LABELS[l.kind]}</Text>
104              <Text>
105                <Text color={tone(l.percentUsed)}>{full}</Text>
106                <Text dimColor>{empty}</Text>
107              </Text>
108              <Text color={tone(l.percentUsed)}>{Math.round(l.percentUsed)}%</Text>
109              {reset ? <Text dimColor>↻ {reset}</Text> : null}
110            </Box>
111          )
112        })}
113      </Box>
114    )
115  })
116}
117
types/index.d.ts 9 lines
1export type Window = { kind: string; percentUsed: number; resetsAt?: string }
2export type Reading = { limits: Window[]; now: number }
3
4declare module 'claude-code' {
5  interface PluginState {
6    'usage-limits': { reading: Reading }
7  }
8}
9