browser-terminal-cli
v1.0.2
Published
A customizable terminal/CLI interface for browser-side JavaScript
Maintainers
Readme
Browser Terminal CLI
A powerful, customizable, and lightweight terminal/CLI interface for browser-side JavaScript applications. Create beautiful command-line interfaces in your web apps with ease.
✨ Features
- 🎨 9 Built-in Themes - Dracula, Monokai, Matrix, Ubuntu, Solarized, and more
- ⌨️ Command History - Navigate with arrow keys, persistent storage ready
- 📝 Tab Completion - Auto-complete command names
- 🔌 Easy Command Registration - Simple API for adding custom commands
- 🎯 Full TypeScript Support - Complete type definitions included
- 📦 Zero Dependencies - Lightweight (~15KB minified)
- ⚡ Keyboard Shortcuts - Ctrl+C, Ctrl+L, Ctrl+U support
- 📊 Rich Output - Tables, colors, styled text, HTML support
- 🎭 Customizable - Prompts, themes, fonts, cursor styles
- 🔄 Async Commands - Full Promise/async-await support
- 📱 Responsive - Works on desktop and mobile browsers
📦 Installation
NPM
npm install browser-terminal-cliYarn
yarn add browser-terminal-cliPNPM
pnpm add browser-terminal-cliCDN
<!-- UMD Build -->
<script src="https://unpkg.com/browser-terminal-cli/dist/index.umd.js"></script>
<!-- ESM Build -->
<script type="module">
import { Terminal } from 'https://unpkg.com/browser-terminal-cli/dist/index.esm.js';
</script>🚀 Quick Start
ES Modules
import { Terminal } from 'browser-terminal-cli';
const terminal = new Terminal({
container: '#terminal',
prompt: '❯ ',
welcomeMessage: 'Welcome! Type "help" for available commands.',
theme: 'dracula',
});
// Add a simple command
terminal.addCommand('hello', () => {
return 'Hello, World!';
}, 'Say hello');
// Add a command with arguments
terminal.addCommand('greet', ({ args }) => {
const name = args[0] || 'stranger';
return `Hello, ${name}!`;
}, 'Greet someone by name');HTML Setup
<!DOCTYPE html>
<html>
<head>
<style>
#terminal {
width: 800px;
height: 400px;
border-radius: 8px;
overflow: hidden;
}
</style>
</head>
<body>
<div id="terminal"></div>
<script type="module">
import { Terminal } from 'browser-terminal-cli';
const terminal = new Terminal({
container: '#terminal',
theme: 'monokai',
});
</script>
</body>
</html>UMD (Script Tag)
<script src="https://unpkg.com/browser-terminal-cli/dist/index.umd.js"></script>
<script>
const terminal = new BrowserTerminal.Terminal({
container: '#terminal',
});
</script>📖 API Reference
Constructor Options
const terminal = new Terminal(options: TerminalOptions);| Option | Type | Default | Description |
|--------|------|---------|-------------|
| container | HTMLElement \| string | required | Container element or CSS selector |
| prompt | string | '$ ' | Command prompt string |
| welcomeMessage | string \| string[] | '' | Welcome message displayed on init |
| theme | string \| TerminalTheme | 'default' | Theme name or custom theme object |
| fontSize | number | 14 | Font size in pixels |
| fontFamily | string | 'Cascadia Code', monospace | Font family |
| cursorStyle | 'block' \| 'underline' \| 'bar' | 'block' | Cursor style |
| cursorBlink | boolean | true | Enable cursor blinking |
| history | boolean | true | Enable command history |
| historySize | number | 100 | Maximum history entries |
| autoFocus | boolean | true | Auto-focus terminal on init |
| scrollback | number | 1000 | Maximum output lines to keep |
| tabSize | number | 4 | Tab character width |
Output Methods
write(text, options?)
Write text without a newline.
terminal.write('Hello ');
terminal.write('World!');
// Output: Hello World!writeln(text, options?)
Write text with a newline.
terminal.writeln('Line 1');
terminal.writeln('Line 2');writeError(text)
Write error message (red color).
terminal.writeError('Something went wrong!');writeSuccess(text)
Write success message (green color).
terminal.writeSuccess('Operation completed!');writeWarning(text)
Write warning message (yellow color).
terminal.writeWarning('This is deprecated');writeInfo(text)
Write info message (blue color).
terminal.writeInfo('Tip: Use help for more commands');writeTable(data, columns?)
Write formatted table.
terminal.writeTable([
{ name: 'John', age: 30, city: 'NYC' },
{ name: 'Jane', age: 25, city: 'LA' },
{ name: 'Bob', age: 35, city: 'Chicago' },
]);
// Output:
// name | age | city
// -----+-----+--------
// John | 30 | NYC
// Jane | 25 | LA
// Bob | 35 | Chicagoclear()
Clear all terminal output.
terminal.clear();Output Options
interface OutputOptions {
color?: string; // Text color (CSS color)
backgroundColor?: string; // Background color
bold?: boolean; // Bold text
italic?: boolean; // Italic text
underline?: boolean; // Underlined text
className?: string; // Custom CSS class
html?: boolean; // Parse as HTML
}Examples:
// Colored text
terminal.writeln('Error!', { color: '#ff5555' });
terminal.writeln('Success!', { color: 'rgb(80, 250, 123)' });
// Styled text
terminal.writeln('Important', { bold: true });
terminal.writeln('Emphasis', { italic: true });
terminal.writeln('Link', { underline: true, color: '#8be9fd' });
// Combined styles
terminal.writeln('Critical Error', {
color: '#ff5555',
bold: true,
backgroundColor: '#1a1a1a'
});
// HTML content
terminal.writeln('<strong>Bold</strong> and <em>italic</em>', { html: true });
terminal.writeln('<span style="color: red">Red text</span>', { html: true });
// Custom class
terminal.writeln('Custom styled', { className: 'my-custom-class' });Command Registration
addCommand(name, handler, description?)
Simple command registration.
terminal.addCommand('time', () => {
return new Date().toLocaleTimeString();
}, 'Display current time');
terminal.addCommand('add', ({ args }) => {
const sum = args.reduce((a, b) => a + parseFloat(b), 0);
return `Sum: ${sum}`;
}, 'Add numbers together');registerCommand(definition)
Advanced command registration.
interface CommandDefinition {
name: string; // Command name
handler: CommandHandler; // Handler function
description?: string; // Help description
usage?: string; // Usage example
aliases?: string[]; // Alternative names
}terminal.registerCommand({
name: 'fetch',
description: 'Fetch data from a URL',
usage: 'fetch <url> [--json]',
aliases: ['get', 'request'],
handler: async ({ args, flags, terminal }) => {
const url = args[0];
if (!url) {
terminal.writeError('Usage: fetch <url>');
return;
}
try {
terminal.writeln(`Fetching ${url}...`);
const response = await fetch(url);
const data = flags.json
? await response.json()
: await response.text();
return typeof data === 'object'
? JSON.stringify(data, null, 2)
: data;
} catch (error) {
terminal.writeError(`Failed: ${error.message}`);
}
},
});registerCommands(definitions[])
Register multiple commands at once.
terminal.registerCommands([
{
name: 'start',
handler: () => 'Starting...',
description: 'Start the application',
},
{
name: 'stop',
handler: () => 'Stopping...',
description: 'Stop the application',
},
]);removeCommand(name)
Remove a registered command.
terminal.removeCommand('hello');hasCommand(name)
Check if command exists.
if (terminal.hasCommand('deploy')) {
terminal.exec('deploy');
}getCommands()
Get all registered commands.
const commands = terminal.getCommands();
commands.forEach(cmd => {
console.log(cmd.name, cmd.description);
});Command Handler Context
interface CommandContext {
terminal: TerminalInterface; // Terminal instance
args: string[]; // Positional arguments
flags: Record<string, string | boolean>; // Parsed flags
rawInput: string; // Original input string
}Example:
terminal.addCommand('example', (context) => {
const { terminal, args, flags, rawInput } = context;
console.log('Raw input:', rawInput);
console.log('Arguments:', args);
console.log('Flags:', flags);
// Use terminal methods
terminal.writeln('Processing...');
if (flags.verbose) {
terminal.writeln('Verbose mode enabled');
}
return `Processed ${args.length} arguments`;
});
// Usage: example arg1 arg2 --verbose --output=file.txt
// args: ['arg1', 'arg2']
// flags: { verbose: true, output: 'file.txt' }Control Methods
focus()
Focus the terminal input.
terminal.focus();blur()
Remove focus from terminal.
terminal.blur();disable()
Disable terminal input.
terminal.disable();enable()
Enable terminal input.
terminal.enable();setPrompt(prompt)
Change the command prompt.
terminal.setPrompt('>>> ');
terminal.setPrompt('user@host:~$ ');
terminal.setPrompt('🚀 ');getPrompt()
Get current prompt string.
const prompt = terminal.getPrompt(); // '$ 'setValue(value)
Set the input field value.
terminal.setValue('echo hello');getValue()
Get current input value.
const input = terminal.getValue();exec(command)
Execute a command programmatically.
await terminal.exec('help');
await terminal.exec('clear');
await terminal.exec('echo Hello World');destroy()
Clean up and remove terminal.
terminal.destroy();Theme Methods
setTheme(theme)
Change terminal theme.
// Use built-in theme
terminal.setTheme('dracula');
terminal.setTheme('matrix');
// Use custom theme
terminal.setTheme({
background: '#1a1a2e',
foreground: '#eaeaea',
cursor: '#00ff88',
selection: 'rgba(0, 255, 136, 0.3)',
black: '#000000',
red: '#ff5555',
green: '#00ff88',
yellow: '#ffff55',
blue: '#5555ff',
magenta: '#ff55ff',
cyan: '#55ffff',
white: '#ffffff',
});getTheme()
Get current theme object.
const theme = terminal.getTheme();
console.log(theme.background); // '#282a36'Available Themes
| Theme | Description |
|-------|-------------|
| default | Default dark theme |
| dark | GitHub dark inspired |
| light | Light theme |
| matrix | Matrix green on black |
| ubuntu | Ubuntu terminal colors |
| monokai | Monokai color scheme |
| dracula | Dracula theme |
| solarized-dark | Solarized dark |
| solarized-light | Solarized light |
History Methods
getHistory()
Get command history array.
const history = terminal.getHistory();
// ['ls', 'cd documents', 'cat file.txt']clearHistory()
Clear command history.
terminal.clearHistory();setHistory(history)
Set command history (for persistence).
// Restore from localStorage
const saved = localStorage.getItem('terminal-history');
if (saved) {
terminal.setHistory(JSON.parse(saved));
}
// Save on command
terminal.on('command', () => {
localStorage.setItem('terminal-history',
JSON.stringify(terminal.getHistory())
);
});Events
on(event, callback)
Subscribe to terminal events.
terminal.on('command', (command, args) => {
console.log('Executed:', command, args);
analytics.track('command', { command, args });
});
terminal.on('input', (value) => {
console.log('Input changed:', value);
});
terminal.on('clear', () => {
console.log('Terminal cleared');
});
terminal.on('ready', () => {
console.log('Terminal initialized');
});
terminal.on('key', (event) => {
if (event.key === 'Escape') {
terminal.blur();
}
});off(event, callback)
Unsubscribe from events.
const handler = (cmd) => console.log(cmd);
terminal.on('command', handler);
terminal.off('command', handler);Event Types
| Event | Callback Signature | Description |
|-------|-------------------|-------------|
| command | (command: string, args: string[]) => void | Command executed |
| input | (value: string) => void | Input value changed |
| clear | () => void | Terminal cleared |
| ready | () => void | Terminal initialized |
| key | (event: KeyboardEvent) => void | Key pressed |
Built-in Commands
| Command | Description |
|---------|-------------|
| help | Show all available commands |
| help <command> | Show help for specific command |
| clear | Clear terminal screen |
| history | Show command history |
| echo <text> | Print text to terminal |
Keyboard Shortcuts
| Shortcut | Action |
|----------|--------|
| ↑ Arrow Up | Previous command in history |
| ↓ Arrow Down | Next command in history |
| Tab | Auto-complete command name |
| Ctrl + C | Cancel current input |
| Ctrl + L | Clear terminal |
| Ctrl + U | Clear current line |
| Enter | Execute command |
🎨 Theming
Custom Theme Object
interface TerminalTheme {
background: string; // Background color
foreground: string; // Default text color
cursor: string; // Cursor color
cursorAccent?: string; // Cursor text color
selection?: string; // Selection background
// ANSI Colors
black?: string;
red?: string;
green?: string;
yellow?: string;
blue?: string;
magenta?: string;
cyan?: string;
white?: string;
// Bright ANSI Colors
brightBlack?: string;
brightRed?: string;
brightGreen?: string;
brightYellow?: string;
brightBlue?: string;
brightMagenta?: string;
brightCyan?: string;
brightWhite?: string;
}Theme Examples
// Cyberpunk theme
terminal.setTheme({
background: '#0a0a0f',
foreground: '#0ff',
cursor: '#f0f',
green: '#0f0',
red: '#f00',
yellow: '#ff0',
blue: '#00f',
magenta: '#f0f',
cyan: '#0ff',
});
// Nord theme
terminal.setTheme({
background: '#2e3440',
foreground: '#d8dee9',
cursor: '#d8dee9',
black: '#3b4252',
red: '#bf616a',
green: '#a3be8c',
yellow: '#ebcb8b',
blue: '#81a1c1',
magenta: '#b48ead',
cyan: '#88c0d0',
white: '#e5e9f0',
});💡 Examples
File System Simulation
const fs = {
'/': { type: 'dir', children: ['home', 'usr', 'var'] },
'/home': { type: 'dir', children: ['user'] },
'/home/user': { type: 'dir', children: ['documents', 'readme.txt'] },
'/home/user/readme.txt': { type: 'file', content: 'Hello World!' },
};
let cwd = '/home/user';
terminal.addCommand('pwd', () => cwd);
terminal.addCommand('ls', ({ args }) => {
const path = args[0] ? resolvePath(args[0]) : cwd;
const node = fs[path];
if (!node) return `ls: ${path}: No such file or directory`;
if (node.type === 'file') return path.split('/').pop();
return node.children.join(' ');
});
terminal.addCommand('cd', ({ args }) => {
const path = args[0] ? resolvePath(args[0]) : '/home/user';
if (!fs[path] || fs[path].type !== 'dir') {
return `cd: ${args[0]}: Not a directory`;
}
cwd = path;
terminal.setPrompt(`${cwd} $ `);
});
terminal.addCommand('cat', ({ args }) => {
const path = resolvePath(args[0]);
const node = fs[path];
if (!node) return `cat: ${args[0]}: No such file`;
if (node.type !== 'file') return `cat: ${args[0]}: Is a directory`;
return node.content;
});
function resolvePath(path) {
if (path.startsWith('/')) return path;
if (path === '..') return cwd.split('/').slice(0, -1).join('/') || '/';
return `${cwd}/${path}`.replace(/\/+/g, '/');
}Interactive Game
let gameState = {
score: 0,
level: 1,
target: randomTarget(),
};
function randomTarget() {
return Math.floor(Math.random() * 100) + 1;
}
terminal.registerCommand({
name: 'play',
description: 'Start the number guessing game',
handler: ({ terminal }) => {
gameState = { score: 0, level: 1, target: randomTarget() };
terminal.writeln('🎮 Number Guessing Game Started!');
terminal.writeln('Guess a number between 1-100');
terminal.writeln('Use: guess <number>');
},
});
terminal.registerCommand({
name: 'guess',
description: 'Guess the number',
usage: 'guess <number>',
handler: ({ args, terminal }) => {
const num = parseInt(args[0]);
if (isNaN(num)) {
return 'Please enter a valid number';
}
if (num === gameState.target) {
gameState.score += gameState.level * 10;
gameState.level++;
gameState.target = randomTarget();
terminal.writeSuccess(`🎉 Correct! Score: ${gameState.score}`);
terminal.writeln(`Level ${gameState.level} - New number generated`);
} else if (num < gameState.target) {
terminal.writeWarning('📈 Go higher!');
} else {
terminal.writeWarning('📉 Go lower!');
}
},
});
terminal.addCommand('score', () => {
return `Score: ${gameState.score} | Level: ${gameState.level}`;
});API Client
const API_BASE = 'https://jsonplaceholder.typicode.com';
terminal.registerCommand({
name: 'api',
description: 'Make API requests',
usage: 'api <endpoint> [--method=GET] [--body={}]',
handler: async ({ args, flags, terminal }) => {
const endpoint = args[0];
if (!endpoint) {
terminal.writeError('Usage: api <endpoint>');
return;
}
const method = flags.method || 'GET';
const url = `${API_BASE}${endpoint}`;
terminal.writeln(`${method} ${url}`, { color: '#8be9fd' });
try {
const options = { method };
if (flags.body) {
options.body = flags.body;
options.headers = { 'Content-Type': 'application/json' };
}
const response = await fetch(url, options);
const data = await response.json();
terminal.writeSuccess(`Status: ${response.status}`);
terminal.writeln(JSON.stringify(data, null, 2));
} catch (error) {
terminal.writeError(`Error: ${error.message}`);
}
},
});
// Usage:
// api /posts/1
// api /posts --method=POST --body={"title":"Hello"}Task Manager
const tasks = [];
let taskId = 1;
terminal.addCommand('add', ({ args }) => {
const text = args.join(' ');
if (!text) return 'Usage: add <task description>';
tasks.push({ id: taskId++, text, done: false });
return `✅ Added task #${taskId - 1}`;
});
terminal.addCommand('list', ({ terminal }) => {
if (tasks.length === 0) {
return '📭 No tasks';
}
tasks.forEach(task => {
const status = task.done ? '✅' : '⬜';
const style = task.done ? { color: '#6b7280' } : {};
terminal.writeln(`${status} #${task.id}: ${task.text}`, style);
});
});
terminal.addCommand('done', ({ args }) => {
const id = parseInt(args[0]);
const task = tasks.find(t => t.id === id);
if (!task) return `Task #${id} not found`;
task.done = true;
return `✅ Completed: ${task.text}`;
});
terminal.addCommand('remove', ({ args }) => {
const id = parseInt(args[0]);
const index = tasks.findIndex(t => t.id === id);
if (index === -1) return `Task #${id} not found`;
const [task] = tasks.splice(index, 1);
return `🗑️ Removed: ${task.text}`;
});Progress Animation
terminal.registerCommand({
name: 'download',
description: 'Simulate file download with progress',
handler: async ({ args, terminal }) => {
const filename = args[0] || 'file.zip';
const size = parseInt(args[1]) || 100;
terminal.writeln(`Downloading ${filename}...`);
for (let i = 0; i <= 100; i += 5) {
const filled = Math.floor(i / 5);
const empty = 20 - filled;
const bar = '█'.repeat(filled) + '░'.repeat(empty);
const mb = ((i / 100) * size).toFixed(1);
// Create/update progress line
process.stdout.write(`\r[${bar}] ${i}% (${mb}/${size} MB)`);
await new Promise(r => setTimeout(r, 100));
}
terminal.writeln('');
terminal.writeSuccess(`✅ Downloaded ${filename}`);
},
});🌐 Browser Support
| Browser | Version | |---------|---------| | Chrome | 60+ | | Firefox | 55+ | | Safari | 12+ | | Edge | 79+ | | Opera | 47+ |
📄 TypeScript
Full TypeScript support with exported types:
import {
Terminal,
TerminalOptions,
TerminalTheme,
CommandDefinition,
CommandHandler,
CommandContext,
OutputOptions,
ThemeName,
} from 'browser-terminal-cli';
const options: TerminalOptions = {
container: '#terminal',
theme: 'dracula',
};
const terminal = new Terminal(options);
const myCommand: CommandDefinition = {
name: 'test',
description: 'A test command',
handler: (ctx: CommandContext): string => {
return `Args: ${ctx.args.join(', ')}`;
},
};
terminal.registerCommand(myCommand);🔧 Advanced Usage
Persisting History
const HISTORY_KEY = 'terminal-history';
// Load history on init
const terminal = new Terminal({
container: '#terminal',
});
const savedHistory = localStorage.getItem(HISTORY_KEY);
if (savedHistory) {
terminal.setHistory(JSON.parse(savedHistory));
}
// Save history on each command
terminal.on('command', () => {
localStorage.setItem(HISTORY_KEY, JSON.stringify(terminal.getHistory()));
});Custom Autocomplete
terminal.on('key', (e) => {
if (e.key === 'Tab') {
e.preventDefault();
const input = terminal.getValue();
const suggestions = getAutocompleteSuggestions(input);
if (suggestions.length === 1) {
terminal.setValue(suggestions[0] + ' ');
} else if (suggestions.length > 1) {
terminal.writeln('');
terminal.writeln(suggestions.join(' '));
}
}
});
function getAutocompleteSuggestions(input) {
const files = ['readme.md', 'package.json', 'index.js'];
return files.filter(f => f.startsWith(input.split(' ').pop()));
}Multiple Terminals
const term1 = new Terminal({
container: '#terminal-1',
prompt: 'server1$ ',
theme: 'dracula',
});
const term2 = new Terminal({
container: '#terminal-2',
prompt: 'server2$ ',
theme: 'monokai',
});
// Sync commands between terminals
term1.on('command', (cmd) => {
term2.writeln(`[server1]: ${cmd}`, { color: '#888' });
});📝 License
MIT © Adhi
🤝 Contributing
Contributions are welcome! Please read our Contributing Guide for details.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
🙏 Acknowledgments
- Inspired by Xterm.js
- Themes inspired by popular color schemes
- Built with TypeScript and Rollup
