▚TokenMetercustom
Context window usage bar with zone colors and io stats
$ termino token-meter — context window usage
╭────────────────────────────────╮
│ctx 120k / 200k 60% │
│╭██████████████████ ╮│
│╰──────────────────────────────╯│
│ 61.0k↑ 8.4k↓ 16.8k☍ │
╰────────────────────────────────╯
live fills to 198k then resets — color zones at 60% / 85%
##API
Raised label row + recessed (inset) track: dark well with the fill as █ bars. Fill color crosses green → yellow at `warnAt` → red at `dangerAt`. Bottom stats row shows ↑input ↓output ☍cache.
properties
| prop | type | default | description |
|---|---|---|---|
| used / total | number | 0 / 200000 | Window usage |
| input / output / cached | number | 0 | Per-direction token stats |
| width | number | 34 | Track width |
| warnAt / dangerAt | number | 0.6 / 0.85 | Zone thresholds |
| color | string | "#a5d98f" | Fill color under warnAt |
| colors | Partial<NeoColors> | NEO | Bevel palette |
##Keymap
—Display only
##Notes
- ▸Inset track is the recessed twin of the raised panels — light/dark edges flipped.
- ▸At 85% the fill goes red: context pressure at a glance, LSP-server style.
##Source
Real implementation — this is the code that runs in your terminal.
1import { createElement as h, useMemo } from "react";2import { Canvas } from "./canvas";3import { cellsWidth, strWidth, type CursorCell } from "./chart";4import { NEO, neoFill, neoPanel, type NeoColors } from "./neo";56export interface TokenMeterProps {7 used?: number;8 total?: number;9 input?: number;10 output?: number;11 cached?: number;12 label?: string;13 width?: number;14 color?: string;15 warnAt?: number;16 dangerAt?: number;17 colors?: Partial<NeoColors>;18}1920export function zoneColor(ratio: number, warnAt: number, dangerAt: number, base: string): string {21 if (ratio >= dangerAt) return "#f0929e";22 if (ratio >= warnAt) return "#f0c674";23 return base;24}2526export function TokenMeter({27 used = 0,28 total = 200000,29 input = 0,30 output = 0,31 cached = 0,32 label = "ctx",33 width = 34,34 color = "#a5d98f",35 warnAt = 0.6,36 dangerAt = 0.85,37 colors,38}: Readonly<TokenMeterProps>) {39 const c = { ...NEO, ...colors };40 const ratio = total > 0 ? used / total : 0;41 const fillColor = zoneColor(ratio, warnAt, dangerAt, color);4243 const rows = useMemo(() => {44 const fmt = (n: number) => `${(n / 1000).toFixed(n >= 100000 ? 0 : 1)}k`;45 const labelRow: CursorCell[] = [46 { ch: `${label} `, fg: c.dim },47 { ch: `${fmt(used)}`, fg: fillColor },48 { ch: ` / ${fmt(total)}`, fg: c.dim },49 ];50 const pct = `${Math.round(ratio * 100)}%`;51 const pctCell = ` ${pct}`;52 while (cellsWidth(labelRow) < width + 2 - strWidth(pctCell)) {53 labelRow.push({ ch: " ", fg: c.text });54 }55 labelRow.push({ ch: pctCell, fg: fillColor });5657 const fill = neoFill(used, total, width, fillColor, c);58 const body: CursorCell[][] = [labelRow];59 body.push([60 { ch: "╭", fg: c.dark },61 ...fill,62 { ch: "╮", fg: c.dark },63 ]);64 body.push([65 { ch: "╰", fg: c.light },66 { ch: "─".repeat(width), fg: c.light },67 { ch: "╯", fg: c.light },68 ]);69 if (input || output || cached) {70 const stats = [71 input ? `${fmt(input)}↑` : "",72 output ? `${fmt(output)}↓` : "",73 cached ? `${fmt(cached)}☍` : "",74 ]75 .filter(Boolean)76 .join(" ");77 body.push([{ ch: ` ${stats}`, fg: c.dim }]);78 }79 return body;80 // eslint-disable-next-line react-hooks/exhaustive-deps81 }, [used, total, input, output, cached, label, width, fillColor, color, colors]);8283 const panel = neoPanel(rows, c);84 const widthFix = cellsWidth(panel[0] ?? []);85 return h("box", { flexDirection: "column", gap: 0, width: widthFix }, h(Canvas, { rows: panel, width: widthFix }));86}87