Ligne d'etat sous la saisie dans l'app Claude Code : contexte (pourcentage et tokens), limite 5 h et de la semaine avec leur reset, prix equivalent API.

Ligne d'état sous la saisie de l'app Claude Code : contexte, limite des 5 heures et limite de la semaine.
Contexte ▓▓░░░░░░ 19% 38k/200k │ 5h 21% ↻4h25 │ Semaine 33% ↻5j │ ≈ $0.42
/plugin install statusline-app --marketplace LouiSvon/claude-code-mods
task-board, qui peut les colorer./clear conseillé au-delà de 60 % de contexte.Le modèle n'est pas affiché : l'app l'affiche déjà à côté de la saisie.
session.start, turn.step et turn.complete. Une lecture qui échoue est ignorée, sans casser le tour.claude plugin test .
Cinq tests : jauge et teintes, dossier réel ou non, temps avant reset, tokens et prix abrégés, ligne épinglée par la session.
hooks/register.tsx 89 lines1import type { EngineInterface, Register } from 'claude-code'
2
3const ORANGE = '#ff9f43'
4const BLUE = '#4da3ff'
5
6// Blue while fresh, orange past 60 %, red past 85 %.
7export const tone = (pct: number): string => (pct >= 85 ? 'error' : pct >= 60 ? ORANGE : BLUE)
8
9export const gauge = (pct: number) => {
10 const filled = Math.min(8, Math.floor(pct / 12.5 + 0.5))
11 return { on: '▓'.repeat(filled), off: '░'.repeat(8 - filled) }
12}
13
14// 842, 12k, 200k, 1.2M: the same short form as the task-board's token counters.
15export const short = (n: number): string =>
16 n < 1000 ? String(Math.round(n)) : n < 999_500 ? `${Math.round(n / 1000)}k` : `${(n / 1_000_000).toFixed(1)}M`
17
18// "≈ $0.42": the API-equivalent price, not a charge (a subscription pays a flat fee); "" when unknown.
19export const price = (usd: number | undefined): string =>
20 usd === undefined || !Number.isFinite(usd) || usd <= 0 ? '' : `≈ $${usd < 10 ? usd.toFixed(2) : usd.toFixed(1)}`
21
22// The session's folder, or null when it runs in the app's scratch folder or the home directory.
23export const folderOf = (cwd: string, home = ''): string | null => {
24 if (cwd.includes('/scratch-workspaces/') || (home !== '' && cwd === home)) return null
25 return cwd.split('/').filter(Boolean).pop() ?? null
26}
27
28// "2h13" or "45min" until a window resets; "" when unknown.
29export const until = (resetsAt: string | undefined, nowMs: number): string => {
30 const ms = resetsAt === undefined ? NaN : Date.parse(resetsAt) - nowMs
31 if (!Number.isFinite(ms) || ms <= 0) return ''
32 const min = Math.round(ms / 60000)
33 if (min < 60) return `${min}min`
34 if (min < 48 * 60) return `${Math.floor(min / 60)}h${String(min % 60).padStart(2, '0')}`
35 return `${Math.floor(min / 1440)}j`
36}
37
38// The app draws no coloured tree under the prompt, only a status line of text: build that line.
39async function refresh($: EngineInterface) {
40 try {
41 const [cwd, usage, now] = await Promise.all([$.session.cwd(), $.session.usage(), $.clock.now()])
42 const ctx = Math.round(usage.context.percent ?? 0)
43 const g = gauge(ctx)
44 const limit = (kind: string, label: string) => {
45 const l = usage.rateLimits.find(r => r.kind === kind)
46 if (l === undefined) return null
47 const reset = until(l.resetsAt, now)
48 return `${label} ${Math.round(l.percentUsed)}%${reset !== '' ? ` ↻${reset}` : ''}`
49 }
50 const fill = usage.context.tokens === undefined ? '' : ` ${short(usage.context.tokens)}/${short(usage.context.window)}`
51 const cost = price(usage.cost?.usd)
52 const parts = [
53 folderOf(cwd),
54 `Contexte ${g.on}${g.off} ${ctx}%${fill}`,
55 limit('five_hour', '5h'),
56 limit('seven_day', 'Semaine'),
57 cost === '' ? null : cost,
58 ctx >= 60 ? '/clear conseillé' : null,
59 ].filter((p): p is string => p !== null)
60 $.ui.status(parts.join(' │ '))
61 } catch {
62 // the status line is a comfort: a failed read must not break a turn
63 }
64}
65
66// Learnt at session.start (a reload raises it again); the terminal has its own shell status line.
67let surface: string | null = null
68
69export const register: Register = on => {
70 on('session.start', async ($, e, next) => {
71 surface = e.surface
72 if (surface !== 'terminal') await refresh($)
73
74 return next(e)
75 })
76
77 on('turn.step', async function* ($, e, next) {
78 if (surface !== 'terminal') await refresh($)
79
80 return yield* next(e)
81 })
82
83 on('turn.complete', async ($, e, next) => {
84 if (surface !== 'terminal') await refresh($)
85
86 return next(e)
87 })
88}
89