Mostra contexto, custo e uso da assinatura na status line

Meus mods para o Claude Code.
Uma linha acima do prompt com o contexto da sessão, o custo e quanto sobra dos limites da assinatura:
ctx 50k/200k (25%) · $1.23 · 5h: 40% livre (reset 1h20) · 7d: 82% livre (reset 3d4h)
/cost. Numa assinatura é o equivalente em API, não o que você paga./plugin install uso-sessao --marketplace diogosarti/my-claude-code
Responda y para adicionar o marketplace e escolha o escopo de usuário.
hooks/register.tsx 99 lines1import { atom, read, update } from 'claude-code'
2import type { Register, SessionRateLimit } from 'claude-code'
3
4import type { Cor, Parte } from '../types'
5
6const NOMES: Record<string, string> = { five_hour: '5h', seven_day: '7d' }
7
8const DURACAO_MS: Record<string, number> = { five_hour: 5 * 3600_000, seven_day: 7 * 86400_000 }
9
10export function corDoUso(percentUsado: number): Cor {
11 if (percentUsado >= 80) return 'error'
12 if (percentUsado >= 50) return 'warning'
13 return 'success'
14}
15
16// Projeta o uso no fim da janela pelo ritmo atual. O decorrido tem piso de 10%
17// para o começo da janela não projetar estouro com 1% usado.
18export function corDoRitmo(r: SessionRateLimit, agora: number): Cor {
19 const duracao = DURACAO_MS[r.kind]
20 const reset = r.resetsAt ? Date.parse(r.resetsAt) : Number.NaN
21 if (r.percentUsed >= 95) return 'error'
22 if (!duracao || Number.isNaN(reset)) return corDoUso(r.percentUsed)
23 const decorrido = Math.min(1, Math.max(0.1, 1 - (reset - agora) / duracao))
24 const projetado = r.percentUsed / decorrido
25 if (projetado >= 100) return 'error'
26 if (projetado >= 85) return 'warning'
27 return 'success'
28}
29
30export function faltaPara(resetsAt: string | undefined, agora: number): string {
31 const ms = resetsAt ? Date.parse(resetsAt) - agora : Number.NaN
32 if (Number.isNaN(ms) || ms <= 0) return ''
33 const min = Math.round(ms / 60_000)
34 if (min < 60) return ` (reset ${min}min)`
35 const h = Math.floor(min / 60)
36 if (h < 24) return ` (reset ${h}h${String(min % 60).padStart(2, '0')})`
37 return ` (reset ${Math.floor(h / 24)}d${h % 24}h)`
38}
39
40export function formatar(
41 context: { tokens?: number; window: number; percent?: number },
42 rateLimits: SessionRateLimit[],
43 usd: number | undefined,
44 agora: number,
45): Parte[] {
46 const k = (n: number) => `${Math.round(n / 1000)}k`
47 const partes: Parte[] = [
48 {
49 texto:
50 `ctx ${context.tokens === undefined ? '-' : k(context.tokens)}/${k(context.window)}` +
51 (context.percent === undefined ? '' : ` (${Math.round(context.percent)}%)`),
52 cor: context.percent === undefined ? 'subtle' : corDoUso(context.percent),
53 },
54 ]
55 if (usd !== undefined) partes.push({ texto: `$${usd.toFixed(2)}`, cor: 'subtle' })
56 for (const r of rateLimits) {
57 const resta = Math.max(0, 100 - Math.round(r.percentUsed))
58 partes.push({
59 texto: `${NOMES[r.kind] ?? r.kind}: ${resta}% livre${faltaPara(r.resetsAt, agora)}`,
60 cor: corDoRitmo(r, agora),
61 })
62 }
63 return partes
64}
65
66const linha = atom({ plugin: 'uso-sessao', key: 'linha' } as const, null)
67
68export const register: Register = on => {
69 on('session.start', async ($, e, next) => {
70 const u = await $.session.usage()
71 const agora = await $.clock.now()
72 await update($, linha, () => formatar(u.context, u.rateLimits, u.cost?.usd, agora))
73 return next(e)
74 })
75
76 on('session.measure', async ($, e, next) => {
77 const u = await $.session.usage()
78 const agora = await $.clock.now()
79 await update($, linha, () => formatar(e.context, e.rateLimits, u.cost?.usd, agora))
80 return next(e)
81 })
82
83 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
84 const partes = await read($, linha)
85 if (partes === null || e.props.hasSurvey) return next(e)
86 const { Text } = $.ui.resolve(e)
87 return (
88 <Text>
89 {partes.map((p, i) => (
90 <Text key={String(i)} color={p.cor}>
91 {i > 0 ? ' · ' : ''}
92 {p.texto}
93 </Text>
94 ))}
95 </Text>
96 )
97 })
98}
99types/index.d.ts 10 lines1export type Cor = 'success' | 'warning' | 'error' | 'subtle'
2export type Parte = { texto: string; cor: Cor }
3export type Linha = Parte[] | null
4
5declare module 'claude-code' {
6 interface PluginState {
7 'uso-sessao': { linha: Linha }
8 }
9}
10