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

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ų.
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.
hooks/register.tsx 169 lines1import { 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}
169types/index.d.ts 16 lines1export 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