@neuralempowerment/tui
v0.1.0
Published
Zero-dependency terminal UI primitives for Node and Bun.
Maintainers
Readme
@neuralempowerment/tui
Zero-dependency terminal UI primitives for Node and Bun. Color helpers, prompts, spinners, arrow-key menus — no chalk, no ora, no enquirer, no transitive supply chain.
- Zero runtime dependencies.
dependenciesandpeerDependenciesinpackage.jsonare empty objects. Enforced by CI. - Runs on Bun and Node ≥ 20. ESM-only. CI matrix proves both runtimes.
- Strict types. No
any, noascasts, nounknowninsrc/(CI-checked). Public APIs are generic where it matters —select<T>returnsT, no string coercion. - 100% line + function coverage in
src/.
Install
bun add @neuralempowerment/tui
# or npm/pnpm/yarn equivalents
npm i @neuralempowerment/tuiQuick start
import { select, multiSelect, confirm, success, spinner } from "@neuralempowerment/tui";
const env = await select(
[
{ label: "Production", value: "prod" },
{ label: "Staging", value: "stg" },
{ label: "Local", value: "dev" },
],
{ title: "Pick an environment" },
);
const services = await multiSelect(
[
{ label: "api", value: "api" },
{ label: "worker", value: "worker" },
{ label: "scheduler", value: "scheduler" },
],
{ minSelections: 1 },
);
if (await confirm(`Deploy ${services.join(", ")} to ${env}?`)) {
const s = spinner("deploying…");
// …do work…
s.stop("done");
success("all set");
}See examples/ for runnable demos of each primitive (bun examples/04-select.ts).
For an interactive tour, run just demo (or bun scripts/demo-launcher.ts) — it uses this library to render an arrow-key menu of every example and runs the one you pick. The launcher is itself a working dogfood of the API.
API
| Export | Kind | Summary |
| --- | --- | --- |
| bold dim red green yellow blue magenta cyan | fn | SGR wrappers; honour NO_COLOR and FORCE_COLOR. |
| isColorSupported | const | true if the runtime stdout is a TTY (or FORCE_COLOR is set) and NO_COLOR is not. |
| BOX boxLine visibleLength | const + fn | Box-drawing glyphs and width helpers (strips SGR for length math). |
| setTotalSteps step info success warn fail | fn | Numbered/log line printers with injectable LogStream. |
| Spinner spinner | class + fn | Single-line indeterminate spinner. update(msg), stop(final?). |
| prompt confirm | fn | Line-based input over node:readline, with injectable PromptIO. |
| promptSecret | fn | Raw-mode masked input. Backspace, Ctrl-C abort. |
| Select select<T> | class + fn | Arrow / vim j/k single-choice menu. Supports disabled items with disabledReason. |
| MultiSelect multiSelect<T> | class + fn | Space-toggle multi-choice. minSelections enforced before commit. |
All interactive primitives accept an optional { input, output } (or LogStream) for tests and headless contexts.
Why zero dependencies?
This package is consumed across many of my projects. Every transitive dep is a supply-chain seat at the table. Owning every line of runtime code eliminates that vector entirely. The library uses only node:readline and raw process.stdin / process.stdout.
Repo setup (after first push)
Branch protection on main (configure in the host UI):
- Require a pull request before merging
- Require status check
qato pass - Disallow force-pushes
- Disallow branch deletion
- Require linear history
Terminal compatibility note
select and multiSelect render in the alternate screen buffer (the same mechanism vim, less, and htop use). This is intentionally isolated from your scrollback and immune to relative-cursor drift.
iTerm2 users: if menu items appear to "drift up" during navigation, check Settings → Profiles → Terminal → "Save lines to scrollback in alternate screen mode" — when that is enabled, iTerm2 captures every line that scrolls off the alt-screen into your scrollback, producing the apparent drift. Disable it for clean rendering. Most other terminals (WezTerm, Ghostty, Alacritty, macOS Terminal) do not have this setting.
Local development
bun install
bun run qa # typecheck → lint:no-escape → test (100% coverage gate) → build → lint:zero-dep
bun run test # bun test --coverage
bun run typecheck # tsc --noEmit
bun run lint:zero-dep # parse package.json + walk dist/ — fails on any non-node import
bun run lint:no-escape # grep src/ for `: any`, ` as `, `: unknown`A justfile is also provided. With just installed:
just # interactive demo launcher
just qa # full QA sweep
just example 04-select # run a single example by name
just examples # list available examplesLicense
MIT — see LICENSE.
