An animated status bar pinned to the top row of the terminal: a thin true-24-bit-color line spans the full width, with a wide band of light sliding smoothly across it — a flat full-brightness core with straight linear ramps fading to near-black on either side, at a constant hue — while your process is running. Call succeed()/fail() to stop the animation and hold solid green/red.
Uses a terminal scroll region (DECSTBM) to reserve row 1 for the bar, so your program's own console.log output keeps scrolling normally underneath it without any conflict.
Works cleanly in traditional terminal emulators: iTerm2, Terminal.app, WezTerm, Alacritty, Kitty, and similar.
Warp is not supported. Warp's block-based UI doesn't render a classic scroll region the way this library needs — the bar can appear at less than full width with stray [/] characters at its edges (Warp's own block-boundary decorations, not bytes this library emits). start() prints a one-time warning when it detects Warp (TERM_PROGRAM=WarpTerminal); there's no escape-sequence fix for it, so use a traditional terminal instead.
npm install cli-status-borderimport { StatusBorder } from 'cli-status-border';
const border = new StatusBorder({ color: 'cyan' });
border.start();
console.log('doing some work...');
await doSomeWork();
border.succeed(); // stops the pulse, holds a solid green bar
// or: border.fail(); // holds a solid red bar
border.stop(); // give the terminal back whenever you're readyYou can also change the color while it's animating:
border.setColor('#ff8800');To cycle a long-lived bar between "busy" and "settled" repeatedly without ever calling stop() in between, use pulse() — the counterpart to succeed()/fail() that resumes the animation:
border.pulse('yellow'); // resume animating in a new colorIf you want the bar to disappear between busy periods without releasing the reserved row (what the shell-wide daemon does between commands), use hide() instead of stop(). Unlike stop()/start(), which is meant to run once per terminal session, hide() only blanks the bar's own row and never touches the rest of the screen — safe to call repeatedly without disturbing whatever else has been printed:
border.start({ silent: true }); // reserve the row once, up front, without drawing yet
// ...later, per busy period...
border.pulse('yellow');
// ...later, once settled...
border.settle('green');
border.hide(); // blank the row; pulse()/settle() show it againWant the bar to react to every command run in a terminal — Python, git, bash, anything — with zero code changes? One-time setup:
npm install -g cli-status-border
cli-status-borderThat opens a create-vite-style wizard: pick your color with the arrow keys, hit Enter, done — it saves the choice to ~/.cli-status-border.json and installs the shell integration into your ~/.zshrc / ~/.bashrc automatically. Open a new terminal and the bar reacts to every command you run: an idle terminal shows nothing — the bar appears the moment a command starts (pulsing in your chosen color), holds solid green or red for a couple seconds once it finishes, then disappears again.
Change the color anytime (running terminals update live, no restart):
cli-status-border colorUnder the hood: each terminal gets its own little daemon that draws the full-width bar, and preexec/precmd hooks (zsh) or the DEBUG trap + PROMPT_COMMAND (bash) write busy/ok/error into a per-terminal state file the daemon watches. Its writes are synchronous so a complete frame reaches the terminal together instead of interleaving with foreground-command output. Colors can also be overridden per-terminal with CLI_STATUS_BORDER_BUSY_COLOR / _OK_COLOR / _ERROR_COLOR env vars, and how long the solid result holds before hiding with CLI_STATUS_BORDER_HOLD_MS (default 2000).
new StatusBorder({
color: 'green', // color name (red, green, yellow, blue, magenta, cyan, white, gray) or a hex string like "#ff8800"
pulseWidth: 10, // length of the moving pulse's glow, in columns (default: the full terminal width)
dimBrightness: 0.04, // brightness (0-1) of the dimmest, unlit part of the line (default fades to near-black)
plateauFraction: 0.33, // fraction of the glow that's a flat, full-intensity core
bloom: 0, // how hard the core blooms toward white (0 = constant hue, 1 = white-hot);
// off by default — the white shift reads as per-cell banding at the core
gradientSteps: 96, // color steps in the pulse (lower values make each frame smaller)
taper: false, // opt-in: vary the line's height by intensity too (thin edges, fat middle) —
// off by default because the per-column height steps (▁▂▃▄) read as boxes
char: '▔', // the character the line is drawn with (default: upper one-eighth block —
// a thin line hugging the top edge of the row)
fps: 40, // redraw rate
speed: 6, // columns the pulse travels per frame
stream: process.stdout,
});- The bar stays up for exactly as long as your process is: it appears on
start()and disappears onstop()— nothing releases it automatically. succeed()/fail()stop the pulse animation and hold a solid color, but don't release the bar themselves; callstop()when you're ready to give the terminal back.- If your process exits (normally, via Ctrl+C, or the terminal closing) without calling
stop(), a safety net still restores the terminal. - No-ops safely when stdout isn't an interactive TTY (piped output, CI logs, etc.) — safe to leave enabled unconditionally in any CLI tool.
Installing the package also gives you a small CLI to preview every color before picking one in code:
npx cli-status-borderUse ↑/↓ to move, Enter to preview a color live in your terminal, Ctrl+C to quit. It prints the exact snippet to use once you've decided.
git clone https://github.com/shxwat/cli-status-border
cd cli-status-border
npm install && npm run build
node examples/demo.js magenta
node examples/long-demo.js green
node bin/cli.js # interactive color picker
node bin/daemon.js & # shell-wide daemon (see "Shell-wide mode" above)MIT