CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vorpal

Node's first framework for building immersive CLI apps.

Pending
Overview
Eval results
Files

events.mddocs/

Event Handling

Event system with hooks, pipes, and signal handling for advanced CLI behaviors.

Capabilities

Signal Handling

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();

Output Piping

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=***');
  });

Stdout Hooking

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();
  });

Custom Help Handler

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');
    }
  });

Application Exit

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();
        }
      });
    }
  });

Event System

Vorpal extends EventEmitter and emits various events during operation.

Vorpal Events

// 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 Events

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');
});

Advanced Event Patterns

Event-Driven Command Logging

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();
  });

Plugin Event System

// 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
  }
});

Real-time Status Updates

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();
  });

Complete Event-Driven Example

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

docs

commands.md

configuration.md

events.md

execution.md

extensions.md

index.md

storage.md

ui.md

tile.json