Línea de estado con el uso de contexto, los límites de 5h/7d y el costo de la sesión

Mods para la terminal de Claude Code.
Línea de estado con el uso de contexto, los límites de uso de 5 horas y 7 días (con lo que falta para que se reinicien) y el costo de la sesión.
ctx 42% (84k/200k) · 5h 23.5% ↻2h05m · 7d 61% ↻3d3h · $1.23
/cost, calculado a precio de lista de la API. Con una suscripción no se cobra: sirve como referencia.Cuenta regresiva del prompt cache desde la última respuesta del modelo, y qué parte de esa respuesta salió del cache.
cache ● 47:12 · hit 98%
cache ○ frío (venció hace 3:05) · hit 98%
Claude Code no les dice a los mods cuánto dura el cache, así que es una opción: ttl, 1h por defecto (lo normal con suscripción) o 5m (lo normal con una API key). Se cambia desde /config.
En la terminal de Claude Code:
/plugin install usage-meter --marketplace nima8998/claude-mods
/plugin install cache-clock --marketplace nima8998/claude-mods
Desde una terminal común:
claude plugin install usage-meter --marketplace nima8998/claude-mods
claude plugin install cache-clock --marketplace nima8998/claude-mods
En la app de escritorio, donde /plugin no funciona: hacé clic en el + que está al lado del cuadro del prompt, después en Plugins, Add marketplace con nima8998/claude-mods y Add plugin.
La primera vez Claude Code pregunta si querés agregar el catálogo: respondé y y elegí el alcance de usuario para que los mods se carguen en todas tus sesiones.
hooks/register.ts 70 lines1import type { Register, SessionCost, SessionContextUsage, SessionRateLimit } from 'claude-code'
2
3type Figures = {
4 context: SessionContextUsage
5 rateLimits: readonly SessionRateLimit[]
6 cost?: SessionCost
7}
8
9const LABELS: Record<string, string> = { five_hour: '5h', seven_day: '7d', spend_limit: 'gasto' }
10
11export function kilo(tokens: number): string {
12 return tokens >= 1000 ? `${Math.round(tokens / 1000)}k` : String(tokens)
13}
14
15export function until(resetsAt: string | undefined, now: number): string {
16 if (resetsAt === undefined) return ''
17 const minutes = Math.max(0, Math.round((Date.parse(resetsAt) - now) / 60000))
18 const days = Math.floor(minutes / 1440)
19 const hours = Math.floor((minutes % 1440) / 60)
20 const rest = minutes % 60
21
22 if (days > 0) return ` ↻${days}d${hours}h`
23 if (hours > 0) return ` ↻${hours}h${String(rest).padStart(2, '0')}m`
24 return ` ↻${rest}m`
25}
26
27export function line(figures: Figures, now: number): string {
28 const { context, rateLimits, cost } = figures
29 const parts: string[] = []
30
31 parts.push(
32 context.tokens === undefined
33 ? `ctx —/${kilo(context.window)}`
34 : `ctx ${context.percent ?? 0}% (${kilo(context.tokens)}/${kilo(context.window)})`,
35 )
36
37 for (const limit of rateLimits) {
38 const label = LABELS[limit.kind] ?? limit.kind
39 parts.push(`${label} ${limit.percentUsed}%${until(limit.resetsAt, now)}`)
40 }
41
42 if (cost !== undefined) parts.push(`$${cost.usd.toFixed(2)}`)
43
44 return parts.join(' · ')
45}
46
47export const register: Register = on => {
48 let last: Figures | undefined
49
50 on('session.start', async ($, e, next) => {
51 const usage = await $.session.usage()
52 last = usage
53 $.ui.status(line(usage, await $.clock.now()))
54
55 // The reset countdowns move on their own between measurements.
56 $.clock.every(60_000, async () => {
57 if (last !== undefined) $.ui.status(line(last, await $.clock.now()))
58 })
59
60 return next(e)
61 })
62
63 on('session.measure', async ($, e, next) => {
64 last = e
65 $.ui.status(line(e, await $.clock.now()))
66
67 return next(e)
68 })
69}
70