SLOPSHOPPER

usage-line

Modelis, effort, konteksto %, 5h ir 7d limitai su reset laikais

newspinnertoaststatus
v0.1.1no licenseupdated 2026-10-24VytenisDulskis/claude-usage-line
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · usage-line
› 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 ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts Opus 5.5 – ###########------------- 49% 5h #######----------------- 31% 09:53 7d ------------------------ 0% --

Draws

Prompt hint
? for shortcuts Opus 5.5 – ###########------------- 49% 5h #######----------------- 31% 09:53 7 ------------------------ 0% --
README

usage-line

Claude Code modas, kuris apatinėje eilutėje po įvedimo laukeliu rodo spalvotą naudojimo suvestinę: modelį, tikrąjį sesijos effort lygį, konteksto užpildymą, 5 valandų ir 7 dienų limitus su reset laikais. Juostos žalios iki 50 %, geltonos iki 80 %, raudonos virš to, o jų ilgis prisitaiko prie terminalo pločio.

⏵⏵ auto mode on · Opus 5.5 medium ##------------ 11%  5h ####---------- 25% 06:40  7d ############-- 90% Sk 04:00

Pirmą kartą paleistas, modas iš vartotojo settings.json (CLAUDE_CONFIG_DIR arba ~/.claude) išima seną statusLine įrašą, kad eilutė nesidubliuotų.

Įdiegimas

Claude Code terminale:

/plugin install usage-line --marketplace VytenisDulskis/claude-usage-line

Atsakyk y į „Add marketplace?“, tada Enter (user scope).

Iš komandinės eilutės:

claude plugin install usage-line --marketplace VytenisDulskis/claude-usage-line

Reikia naujos Claude Code versijos su modų (hooks modulių) palaikymu.

Source 2 files
hooks/register.tsx 169 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Usage } from '../types'
5
6const usage = atom({ plugin: 'usage-line', key: 'usage' } as const, null)
7const effort = atom({ plugin: 'usage-line', key: 'effort' } as const, null)
8
9const DAYS = ['Sk', 'Pr', 'An', 'Tr', 'Kt', 'Pn', 'Št']
10
11const pad = (n: number) => String(n).padStart(2, '0')
12
13function resetTime(iso: string | undefined, withDay: boolean) {
14  if (!iso) return null
15  const d = new Date(iso)
16  const hm = `${pad(d.getHours())}:${pad(d.getMinutes())}`
17  return withDay ? `${DAYS[d.getDay()]} ${hm}` : hm
18}
19
20const level = (pct: number) => (pct >= 80 ? 'error' : pct >= 50 ? 'warning' : 'success')
21
22function bar(pct: number | null, width: number) {
23  const p = pct ?? 0
24  const filled = Math.min(width, Math.floor((p / 100) * width))
25  return { bar: '#'.repeat(filled) + '-'.repeat(width - filled), pct: `${p.toFixed(0).padStart(3)}%` }
26}
27
28async function refresh($: EngineInterface) {
29  const u = await $.session.usage()
30  const h5 = u.rateLimits.find(r => r.kind === 'five_hour')
31  const d7 = u.rateLimits.find(r => r.kind === 'seven_day')
32  const next: Usage = {
33    model: prettyModel(await $.session.model()),
34    effort: await read($, effort),
35    ctx: u.context.percent ?? null,
36    h5: h5?.percentUsed ?? null,
37    h5Reset: resetTime(h5?.resetsAt, false),
38    d7: d7?.percentUsed ?? null,
39    d7Reset: resetTime(d7?.resetsAt, true),
40  }
41  await update($, usage, () => next)
42  $.ui.status(undefined)
43}
44
45// "claude-opus-5-5" -> "Opus 5.5", "claude-opus-5" -> "Opus 5", "claude-sonnet-4-5-20250929" -> "Sonnet 4.5"
46function prettyModel(id: string) {
47  const m = /^claude-([a-z]+)-(\d+)(?:-(\d{1,2}))?(?!\d)/.exec(id)
48  if (!m) return id
49  const name = `${m[1][0].toUpperCase()}${m[1].slice(1)}`
50  return m[3] === undefined ? `${name} ${m[2]}` : `${name} ${m[2]}.${m[3]}`
51}
52
53// Senas komandinis statusline dubliuotų šią eilutę: išimam jį iš settings failų.
54// Vartotojo ir projekto vietinius (settings.local.json) tvarkom patys, apie bendrą projekto failą tik pranešam.
55let oldStatusLineChecked = false
56
57async function removeOldStatusLine($: EngineInterface) {
58  if (oldStatusLineChecked) return
59  oldStatusLineChecked = true
60
61  const configDir =
62    (await $.env.get('CLAUDE_CONFIG_DIR')) ??
63    `${(await $.env.get('USERPROFILE')) ?? (await $.env.get('HOME'))}/.claude`
64  const files = {
65    user: `${configDir.replace(/[\\/]+$/, '')}/settings.json`,
66    local: '.claude/settings.local.json',
67    project: '.claude/settings.json',
68  } as const
69
70  for (const source of ['user', 'local', 'project'] as const) {
71    if ((await $.settings.read({ source })).statusLine === undefined) continue
72    const path = files[source]
73
74    if (source === 'project') {
75      $.ui.toast(`usage-line: senas statusLine yra projekto ${path}, išimk jį ranka`)
76      continue
77    }
78
79    try {
80      const settings = JSON.parse(await $.fs.read(path))
81      if (settings.statusLine === undefined) {
82        $.ui.toast(`usage-line: senas statusLine įjungtas, bet ${path} jo neturi`)
83        continue
84      }
85      delete settings.statusLine
86      await $.fs.write(path, JSON.stringify(settings, null, 2) + '\n')
87      $.ui.toast(`usage-line: senas statusLine išimtas iš ${path}`)
88    } catch (err) {
89      $.ui.toast(`usage-line: nepavyko išimti seno statusLine iš ${path}: ${err}`)
90    }
91  }
92}
93
94// Vieta, kurią eilutėje užima Claude Code režimo ženklas (⏵⏵ auto mode on ·) ir paraštės
95const RESERVED = 24
96
97export const register: Register = on => {
98  on('session.start', async ($, e, next) => {
99    const r = await next(e)
100    await removeOldStatusLine($)
101    await refresh($)
102    return r
103  })
104
105  on('session.measure', async ($, e, next) => {
106    const r = await next(e)
107    await removeOldStatusLine($)
108    await refresh($)
109    return r
110  })
111
112  on('turn.step', async function* ($, e, next) {
113    if (e.effort !== undefined && String(e.effort) !== (await read($, effort))) {
114      await update($, effort, () => String(e.effort))
115      await refresh($)
116    }
117    return yield* next(e)
118  })
119
120  // Apatinė eilutė po įvedimo laukeliu: Claude Code užuomina, po jos mūsų duomenys
121  on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
122    const u = await read($, usage)
123    if (u === null) return next(e)
124
125    const hint = e.props.hint.replace(/\(shift\+tab to cycle\)/g, '').trim()
126
127    // Juostų plotis pagal terminalo plotį: 3 juostos dalinasi likusia vieta;
128    // siaurame lange juostos trumpėja, o kai nebetelpa net trumpos, lieka tik skaičiai
129    const head = `${u.model} ${u.effort ?? '–'} `
130    const tail = `  5h  ${u.h5Reset ?? '--:--'}  7d  ${u.d7Reset ?? '--'}` + '100%'.repeat(3)
131    const free = (e.viewport?.columns ?? 120) - RESERVED - (hint ? hint.length + 2 : 0) - head.length - tail.length
132    const each = Math.floor(free / 3) - 1
133    const width = each >= 4 ? Math.min(30, each) : 0
134
135    const { Box, Text } = $.ui.resolve(e)
136    const Bar = ({ pct }: { pct: number | null }) => {
137      const b = bar(pct, width)
138      if (width === 0) return <Text color={level(pct ?? 0)}>{b.pct.trim()}</Text>
139      return (
140        <Text>
141          <Text color={level(pct ?? 0)}>{b.bar}</Text> {b.pct}
142        </Text>
143      )
144    }
145
146    return (
147      <Box flexDirection="row" columnGap={2}>
148        {hint ? (
149          <Box flexShrink={1}>
150            <Text dimColor wrap="truncate-end">
151              {hint}
152            </Text>
153          </Box>
154        ) : null}
155        <Box flexShrink={0}>
156          <Text>
157            <Text color="cyan">{u.model}</Text> <Text color="#87afaf">{u.effort ?? '–'}</Text>{' '}
158            <Bar pct={u.ctx} />
159            {'  5h '}
160            <Bar pct={u.h5} /> {u.h5Reset ?? '--:--'}
161            {'  7d '}
162            <Bar pct={u.d7} /> {u.d7Reset ?? '--'}
163          </Text>
164        </Box>
165      </Box>
166    )
167  })
168}
169
types/index.d.ts 16 lines
1export type Usage = {
2  model: string
3  effort: string | null
4  ctx: number | null
5  h5: number | null
6  h5Reset: string | null
7  d7: number | null
8  d7Reset: string | null
9}
10
11declare module 'claude-code' {
12  interface PluginState {
13    'usage-line': { usage: Usage | null; effort: string | null }
14  }
15}
16