Node's first framework for building immersive CLI apps.
—
Event system with hooks, pipes, and signal handling for advanced CLI behaviors.
Registers a handler for SIGINT (Ctrl+C) signals.
/**
* Registers a SIGINT handler function
* @param fn - Function to call when SIGINT is received
* @returns Vorpal instance for chaining
*/
function sigint(fn: () => void): Vorpal;Usage Example:
const vorpal = require('vorpal')();
// Handle Ctrl+C gracefully
vorpal
.sigint(function() {
console.log('\nReceived SIGINT. Cleaning up...');
// Cleanup operations
// Save data, close connections, etc.
process.exit(0);
})
.delimiter('myapp$')
.show();Intercepts all stdout logging through a custom function.
/**
* Pipes all stdout through a custom function
* @param fn - Function to process output before display
* @returns Vorpal instance for chaining
*/
function pipe(fn: (output: string) => string): Vorpal;Usage Examples:
const vorpal = require('vorpal')();
// Log all output to file
const fs = require('fs');
const logStream = fs.createWriteStream('app.log', { flags: 'a' });
vorpal
.pipe(function(output) {
// Log to file with timestamp
logStream.write(`[${new Date().toISOString()}] ${output}\n`);
// Return output to display normally
return output;
});
// Transform output (e.g., add color, formatting)
vorpal
.pipe(function(output) {
// Add timestamp prefix to all output
return `[${new Date().toLocaleTimeString()}] ${output}`;
});
// Filter sensitive information
vorpal
.pipe(function(output) {
// Remove sensitive data from logs
return output.replace(/password=\w+/gi, 'password=***');
});Hooks into stdout and passes it through a function.
/**
* Hooks stdout and passes it through a function
* @param fn - Optional function to process stdout
* @returns Vorpal instance for chaining
*/
function hook(fn?: (output: string) => void): Vorpal;Usage Example:
const vorpal = require('vorpal')();
// Monitor all stdout output
vorpal
.hook(function(output) {
// Send output to monitoring service
console.error(`[MONITOR] ${output}`); // Use stderr to avoid recursion
// Count output lines
if (!global.outputStats) {
global.outputStats = { lines: 0, chars: 0 };
}
global.outputStats.lines++;
global.outputStats.chars += output.length;
});
// Example command that generates output
vorpal
.command('test-output', 'Generate test output')
.action(function(args, callback) {
this.log('Line 1');
this.log('Line 2');
this.log('Line 3');
callback();
});Registers a custom help handler for the application.
/**
* Registers a custom help handler
* @param fn - Function to handle help requests
* @returns undefined
*/
function help(fn: (cmd?: string) => void): undefined;Usage Example:
const vorpal = require('vorpal')();
// Custom help system
vorpal
.help(function(cmd) {
if (cmd) {
// Help for specific command
console.log(`Custom help for command: ${cmd}`);
const command = this.find(cmd);
if (command) {
console.log(`Description: ${command.description()}`);
console.log(`Usage: ${command.usage()}`);
} else {
console.log(`Command '${cmd}' not found`);
}
} else {
// General help
console.log('=== My CLI Application Help ===');
console.log('Available commands:');
this.commands.forEach(command => {
if (!command._hidden) {
console.log(` ${command._name} - ${command.description()}`);
}
});
console.log('\nUse "help <command>" for specific command help');
}
});Exits the application with optional cleanup.
/**
* Exits the application
* @param options - Exit options (code, cleanup, etc.)
* @returns undefined
*/
function exit(options: ExitOptions): undefined;
interface ExitOptions {
code?: number; // Exit code (default: 0)
cleanup?: boolean; // Whether to run cleanup (default: true)
force?: boolean; // Force exit without confirmation (default: false)
}Usage Example:
const vorpal = require('vorpal')();
vorpal
.command('shutdown', 'Shutdown the application')
.option('-f, --force', 'Force shutdown without confirmation')
.action(function(args, callback) {
const force = args.options.force;
if (force) {
this.log('Force shutdown initiated...');
this.parent.exit({ code: 0, force: true });
} else {
this.prompt({
type: 'confirm',
name: 'confirm',
message: 'Are you sure you want to shutdown?',
default: false
}, (result) => {
if (result.confirm) {
this.log('Shutting down...');
this.parent.exit({ code: 0, cleanup: true });
} else {
this.log('Shutdown cancelled');
callback();
}
});
}
});Vorpal extends EventEmitter and emits various events during operation.
// Event types emitted by Vorpal
interface VorpalEvents {
'keypress': (key: KeyPress) => void;
'command_registered': (command: Command) => void;
'client_prompt_submit': (data: PromptData) => void;
'client_command_error': (error: Error, command: string) => void;
'client_command_executed': (command: string, args: any) => void;
'client_command_cancelled': (command: string) => void;
'mode_exit': (mode: string) => void;
'vorpal_exit': (code: number) => void;
}
interface KeyPress {
sequence: string;
name: string;
ctrl: boolean;
meta: boolean;
shift: boolean;
}
interface PromptData {
command: string;
args: any;
}Event Handling Examples:
const vorpal = require('vorpal')();
// Listen for command executions
vorpal.on('client_command_executed', function(command, args) {
console.log(`Command executed: ${command}`);
console.log('Arguments:', args);
});
// Listen for errors
vorpal.on('client_command_error', function(error, command) {
console.error(`Error in command ${command}:`, error.message);
// Log to file, send to monitoring service, etc.
logError(command, error);
});
// Listen for command registrations
vorpal.on('command_registered', function(command) {
console.log(`New command registered: ${command._name}`);
});
// Listen for key presses
vorpal.on('keypress', function(key) {
if (key.name === 'tab') {
console.log('Tab key pressed for autocomplete');
}
});
// Listen for application exit
vorpal.on('vorpal_exit', function(code) {
console.log(`Application exiting with code: ${code}`);
// Cleanup operations
closeConnections();
saveState();
});
// Listen for mode changes
vorpal.on('mode_exit', function(mode) {
console.log(`Exited mode: ${mode}`);
});Session instances also emit events:
const vorpal = require('vorpal')();
// Listen to session events
vorpal.session.on('vorpal_command_cancel', function() {
console.log('Command was cancelled');
});const vorpal = require('vorpal')();
const fs = require('fs');
// Setup comprehensive logging
const logFile = 'command-log.json';
const commandLog = [];
vorpal.on('client_command_executed', function(command, args) {
const logEntry = {
timestamp: new Date().toISOString(),
command: command,
args: args,
success: true
};
commandLog.push(logEntry);
fs.writeFileSync(logFile, JSON.stringify(commandLog, null, 2));
});
vorpal.on('client_command_error', function(error, command) {
const logEntry = {
timestamp: new Date().toISOString(),
command: command,
error: error.message,
success: false
};
commandLog.push(logEntry);
fs.writeFileSync(logFile, JSON.stringify(commandLog, null, 2));
});
// Command to view logs
vorpal
.command('logs [count]', 'Show recent command logs')
.action(function(args, callback) {
const count = parseInt(args.count) || 10;
const recentLogs = commandLog.slice(-count);
this.log(`Last ${count} commands:`);
recentLogs.forEach((entry, index) => {
const status = entry.success ? '✓' : '✗';
this.log(`${index + 1}. ${status} ${entry.timestamp} - ${entry.command}`);
if (!entry.success) {
this.log(` Error: ${entry.error}`);
}
});
callback();
});// Create event-driven plugin system
function createEventPlugin(vorpal) {
const pluginEvents = {};
// Register plugin event listeners
vorpal.pluginOn = function(event, listener) {
if (!pluginEvents[event]) {
pluginEvents[event] = [];
}
pluginEvents[event].push(listener);
};
// Emit plugin events
vorpal.pluginEmit = function(event, ...args) {
if (pluginEvents[event]) {
pluginEvents[event].forEach(listener => {
try {
listener(...args);
} catch (error) {
console.error(`Plugin event error:`, error);
}
});
}
};
// Hook into command execution
vorpal.on('client_command_executed', function(command, args) {
vorpal.pluginEmit('command:executed', command, args);
});
vorpal.on('client_command_error', function(error, command) {
vorpal.pluginEmit('command:error', error, command);
});
}
// Usage
const vorpal = require('vorpal')();
createEventPlugin(vorpal);
// Plugin that listens to events
vorpal.pluginOn('command:executed', function(command, args) {
if (command.startsWith('db:')) {
console.log('Database command executed:', command);
}
});
vorpal.pluginOn('command:error', function(error, command) {
if (command.startsWith('api:')) {
console.log('API command failed:', error.message);
// Maybe retry or show helpful message
}
});const vorpal = require('vorpal')();
// Status monitoring with events
let systemStatus = {
uptime: 0,
commandsExecuted: 0,
errors: 0,
lastCommand: null
};
// Update status on events
vorpal.on('client_command_executed', function(command, args) {
systemStatus.commandsExecuted++;
systemStatus.lastCommand = {
command: command,
timestamp: new Date(),
args: args
};
});
vorpal.on('client_command_error', function(error, command) {
systemStatus.errors++;
});
// Periodic status updates
setInterval(() => {
systemStatus.uptime += 1;
}, 1000);
vorpal
.command('status', 'Show system status')
.action(function(args, callback) {
this.log('System Status:');
this.log(` Uptime: ${systemStatus.uptime} seconds`);
this.log(` Commands executed: ${systemStatus.commandsExecuted}`);
this.log(` Errors: ${systemStatus.errors}`);
if (systemStatus.lastCommand) {
this.log(` Last command: ${systemStatus.lastCommand.command} at ${systemStatus.lastCommand.timestamp.toLocaleTimeString()}`);
}
callback();
});const vorpal = require('vorpal')();
const fs = require('fs');
const path = require('path');
// Setup comprehensive event handling
class CLIEventSystem {
constructor(vorpal) {
this.vorpal = vorpal;
this.logPath = path.join(process.cwd(), 'cli-events.log');
this.stats = {
sessionsStarted: 0,
commandsExecuted: 0,
errors: 0,
startTime: new Date()
};
this.setupEventHandlers();
}
setupEventHandlers() {
// Log all events to file
this.vorpal.on('client_command_executed', (command, args) => {
this.logEvent('command_executed', { command, args });
this.stats.commandsExecuted++;
});
this.vorpal.on('client_command_error', (error, command) => {
this.logEvent('command_error', { command, error: error.message });
this.stats.errors++;
});
this.vorpal.on('command_registered', (command) => {
this.logEvent('command_registered', { name: command._name });
});
this.vorpal.on('keypress', (key) => {
if (key.ctrl && key.name === 'c') {
this.logEvent('sigint_received', {});
}
});
this.vorpal.on('vorpal_exit', (code) => {
this.logEvent('application_exit', { code, uptime: this.getUptime() });
});
// Custom graceful shutdown
this.vorpal.sigint(() => {
console.log('\n\nGraceful shutdown initiated...');
this.logEvent('graceful_shutdown', { stats: this.stats });
console.log('Event log saved to:', this.logPath);
process.exit(0);
});
}
logEvent(type, data) {
const logEntry = {
timestamp: new Date().toISOString(),
type: type,
data: data
};
fs.appendFileSync(this.logPath, JSON.stringify(logEntry) + '\n');
}
getUptime() {
return Math.floor((new Date() - this.stats.startTime) / 1000);
}
getStats() {
return {
...this.stats,
uptime: this.getUptime()
};
}
}
// Initialize event system
const eventSystem = new CLIEventSystem(vorpal);
// Commands that work with the event system
vorpal
.command('events:stats', 'Show event statistics')
.action(function(args, callback) {
const stats = eventSystem.getStats();
this.log('CLI Event Statistics:');
this.log(` Uptime: ${stats.uptime} seconds`);
this.log(` Commands executed: ${stats.commandsExecuted}`);
this.log(` Errors: ${stats.errors}`);
this.log(` Session started: ${stats.startTime.toLocaleString()}`);
callback();
});
vorpal
.command('events:log [lines]', 'Show recent event log entries')
.action(function(args, callback) {
const lines = parseInt(args.lines) || 10;
try {
const logContent = fs.readFileSync(eventSystem.logPath, 'utf8');
const logLines = logContent.trim().split('\n').slice(-lines);
this.log(`Last ${lines} events:`);
logLines.forEach((line, index) => {
try {
const entry = JSON.parse(line);
this.log(`${index + 1}. [${entry.timestamp}] ${entry.type}: ${JSON.stringify(entry.data)}`);
} catch (e) {
this.log(`${index + 1}. Invalid log entry: ${line}`);
}
});
} catch (error) {
this.log('Error reading event log:', error.message);
}
callback();
});
// Custom output piping for enhanced logging
vorpal.pipe(function(output) {
// Add session info to all output
const sessionInfo = `[Session ${eventSystem.stats.sessionsStarted}]`;
return `${sessionInfo} ${output}`;
});
// Start the CLI
eventSystem.stats.sessionsStarted++;
eventSystem.logEvent('session_started', { pid: process.pid });
vorpal
.delimiter('events$')
.show();Install with Tessl CLI
npx tessl i tessl/npm-vorpal