@dbwebb/tui
v1.5.0
Published
A Node.js library for building interactive terminal UIs with a class-based command dispatcher.
Downloads
248
Readme
Terminal UI (TUI)
A Node.js library for building interactive terminal UIs with a dynamic, class-based command dispatcher.
If you are a developer and want to understand or enhance the library itself, then read DEVELOPMENT.md.
Read on to learn how to install and use the library to create your own TUI.
Install
npm install @dbwebb/tuiQuick start
Create a tui.js in your project:
import { CommandRegistry, TuiShell, BaseCommand } from '@dbwebb/tui';
class ServerCommands extends BaseCommand {
static descriptions = {
status: 'status Show server status',
restart: 'restart Restart the server',
};
async status() { return 'Server is running.'; }
async restart() { return 'Server restarted.'; }
}
const registry = new CommandRegistry();
registry.register('server', new ServerCommands());
new TuiShell(registry, {
welcomeMessage: "Welcome to My App!",
}).start();Run it:
node tui.jsAt the prompt:
> server status
> server restart
> help
> exitCommand groups
Each group is a class that extends BaseCommand. Methods become dispatchable actions.
import { BaseCommand } from '@dbwebb/tui';
export class MyCommands extends BaseCommand {
static descriptions = {
greet: 'greet <name> Say hello',
};
async greet(name) {
return `Hello, ${name}!`;
}
}Register it:
registry.register('my', new MyCommands());Then call it:
> my greet world
Hello, world!Rules for command methods:
- Must be
async - Return a string (or
undefinedfor no output) - Names starting with
_are private and will never be dispatched
API
new CommandRegistry()
Holds registered command groups.
registry.register(name, instance)— register a command groupregistry.dispatch(tokens)— resolve and call a command, returns{ ok, message }; exceptions thrown by command methods are caught and returned as{ ok: false, message }
new TuiShell(registry, options?)
Interactive readline REPL.
shell.start()— start the prompt loopshell.ask(query)— from within a dispatched command, ask a follow-up question on the same interface (interactive mode only); see Multi-question dialogs withask()- Built-in commands:
help/h,exit/e,quit/q - Tab-completion for group names and actions
Options:
welcomeMessage(string) — optional message shown on startup in interactive mode (not shown in--filemode)defaultGroup(string) — optional default command group; allows omitting the group name when typing commands
A standard hint line (Type "help" to see available commands or "exit" to quit.) is always printed on startup in interactive mode.
Example with defaultGroup:
const registry = new CommandRegistry();
registry.register('server', new ServerCommands());
new TuiShell(registry, { defaultGroup: 'server' }).start();With defaultGroup set, status dispatches as server status. Typing the full server status still works. Tab completion also suggests the default group's actions alongside registered group names.
File mode
Run commands from a file instead of interactively:
node tui.js --file commands.txtThe file contains one command per line. Blank lines and lines starting with # are ignored.
# commands.txt
server status
server restartThe program executes each command in order and then exits.
Stdin pipe mode
Commands can also be piped via stdin — no --file flag needed:
echo "server status" | node tui.js
cat commands.txt | node tui.jsWhen stdin is not a TTY, TuiShell automatically reads from stdin using the same output format as --file mode: bold echo, plain output, blank line between blocks.
Multi-question dialogs with ask()
A dispatched command that needs to collect several answers in a row (e.g. a form) can call shell.ask(query) instead of pulling in a separate prompt library:
class LedgerCommands extends BaseCommand {
constructor(shell) {
super();
this.shell = shell;
}
async entry() {
const account = await this.shell.ask('Account: ');
const amount = await this.shell.ask('Amount: ');
return `Entry: ${account} ${amount}`;
}
}
const shell = new TuiShell(registry);
registry.register('ledger', new LedgerCommands(shell));
shell.start();ask() reuses the shell's own readline interface, so it doesn't race with the main prompt loop over stdin. It only works in interactive mode, after start() has been called — it rejects in --file or stdin-pipe mode.
BaseCommand
Base class for all command groups.
instance.publicMethods()— list of dispatchable method namesinstance.help()— auto-generated help string fromstatic descriptions
