Строка под окном ввода: контекст, 5-часовой и недельный лимиты

Мод для Claude Desktop: строка под окном ввода с тремя значениями: заполненность контекста, расход 5-часового лимита, расход недельного лимита.
Значения читаются через $.session.usage() при каждой отрисовке подсказки под окном ввода (PromptHint, её рисуют терминал и Desktop). Значение от 85% помечается «!». Пока лимит не пришёл (нет подписки или ещё не было ответа), показывается «—». Подсказка заменяет стандартную строку подсказок под вводом.
Язык: берётся из настройки language в settings.json (иначе из LANG/LC_ALL): ru, en, de, fr, es, иначе английский. Язык интерфейса самого приложения мод узнать не может, только эту настройку.
На макете проверено, что подсказка рисуется на терминале и Desktop; в живом приложении мод ещё не видели.
Установка (только по согласию Максима): положить папку в ~/.claude/dev-mods/<сессия>/usage-bar/ или подключить через --plugin-dir.
Проверка: claude plugin validate mods/usage-bar и claude plugin test mods/usage-bar.
Установка одной строкой в терминальной сессии Claude Code (после слияния PR):
/plugin install usage-bar --marketplace GafarovMaxim/Claudehooks/register.tsx 51 lines1import type { Register } from 'claude-code'
2
3const words = {
4 ru: { context: 'Контекст', five: '5 ч', week: 'неделя' },
5 en: { context: 'Context', five: '5h', week: 'week' },
6 de: { context: 'Kontext', five: '5 Std', week: 'Woche' },
7 fr: { context: 'Contexte', five: '5 h', week: 'semaine' },
8 es: { context: 'Contexto', five: '5 h', week: 'semana' }
9} as const
10
11type Lang = keyof typeof words
12
13// Язык берём из настройки language (язык ответов Claude), иначе из LANG/LC_ALL; по умолчанию английский.
14const pickLang = (raw: unknown): Lang => {
15 const s = String(raw ?? '').toLowerCase()
16 const code = (Object.keys(words) as Lang[]).find(
17 l => s === l || s.startsWith(l + '_') || s.startsWith(l + '-') || s.startsWith(l === 'ru' ? 'рус' : l === 'en' ? 'english' : '\0')
18 )
19
20 return code ?? 'en'
21}
22
23const find = (limits: readonly { kind: string; percentUsed: number }[], kind: string) =>
24 limits.find(l => l.kind === kind)?.percentUsed ?? null
25
26const fmt = (v: number | null) => (v === null ? '—' : `${Math.round(v)}%${v >= 85 ? '!' : ''}`)
27
28export const register: Register = on => {
29 let lang: Lang = 'en'
30
31 on('session.start', async ($, e, next) => {
32 const { language } = (await $.settings.read()) as { language?: string }
33 const env = language ?? (await $.env.get('LC_ALL')) ?? (await $.env.get('LANG'))
34 lang = pickLang(env)
35
36 return next(e)
37 })
38
39 // Подсказка под окном ввода (PromptHint) рисуется и в терминале, и в Desktop.
40 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
41 const u = await $.session.usage()
42 const w = words[lang]
43 const ctx = fmt(u.context.percent ?? null)
44 const five = fmt(find(u.rateLimits, 'five_hour'))
45 const week = fmt(find(u.rateLimits, 'seven_day'))
46 const { Text } = $.ui.resolve(e)
47
48 return <Text dimColor>{`${w.context} ${ctx} · ${w.five} ${five} · ${w.week} ${week}`}</Text>
49 })
50}
51