CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-vorpal

Node's first framework for building immersive CLI apps.

Pending
Overview
Eval results
Files

extensions.mddocs/

Extension System

Plugin architecture for importing command libraries and extending Vorpal functionality.

Capabilities

Extension Loading

Imports a library of Vorpal API commands.

/**
 * Imports a library of Vorpal API commands
 * @param commands - Extension module (function, string path, array, or object)
 * @param options - Optional configuration for the extension
 * @returns Vorpal instance for chaining
 */
function use(commands: ExtensionInput, options?: ExtensionOptions): Vorpal;

type ExtensionInput = 
  | ((vorpal: Vorpal) => void)      // Function that extends vorpal
  | string                          // Module path to require
  | ExtensionInput[]               // Array of extensions
  | { [key: string]: any };        // Extension object

interface ExtensionOptions {
  [key: string]: any;  // Extension-specific options
}

Usage Examples:

const vorpal = require('vorpal')();

// Load extension by function
function myExtension(vorpal) {
  vorpal
    .command('hello', 'Says hello')
    .action(function(args, callback) {
      this.log('Hello from extension!');
      callback();
    });
  
  vorpal
    .command('goodbye', 'Says goodbye')
    .action(function(args, callback) {
      this.log('Goodbye from extension!');
      callback();
    });
}

vorpal.use(myExtension);

// Load extension by module path
vorpal.use('./my-extension');  // Requires local file
vorpal.use('vorpal-grep');     // Requires npm module

// Load multiple extensions
vorpal.use([
  './commands/file-commands',
  './commands/network-commands',
  myExtension
]);

// Load extension with options
vorpal.use(myExtension, {
  prefix: 'ext:',
  verbose: true
});

Creating Extensions

Function-Based Extensions

The most common way to create extensions:

// my-extension.js
function myExtension(vorpal, options) {
  const prefix = options?.prefix || '';
  
  vorpal
    .command(`${prefix}status`, 'Shows application status')
    .action(function(args, callback) {
      this.log('Status: Running');
      this.log('Uptime:', process.uptime());
      callback();
    });
  
  vorpal
    .command(`${prefix}restart`, 'Restarts the application')
    .option('-f, --force', 'Force restart without confirmation')
    .action(function(args, callback) {
      if (args.options.force) {
        this.log('Force restarting...');
        callback();
      } else {
        this.prompt({
          type: 'confirm',
          name: 'confirm',
          message: 'Are you sure you want to restart?'
        }, (result) => {
          if (result.confirm) {
            this.log('Restarting...');
          } else {
            this.log('Restart cancelled');
          }
          callback();
        });
      }
    });
}

module.exports = myExtension;

// Usage
const vorpal = require('vorpal')();
vorpal.use(require('./my-extension'), { prefix: 'app:' });

Object-Based Extensions

Extensions can also be objects with specific structure:

// extension-object.js
const extensionObject = {
  // Optional initialization function
  init: function(vorpal, options) {
    console.log('Extension initialized with options:', options);
  },
  
  // Commands definition
  commands: [
    {
      name: 'list-files [directory]',
      description: 'Lists files in directory',
      action: function(args, callback) {
        const fs = require('fs');
        const dir = args.directory || process.cwd();
        
        fs.readdir(dir, (err, files) => {
          if (err) {
            this.log('Error:', err.message);
          } else {
            this.log('Files in', dir + ':');
            files.forEach(file => this.log('-', file));
          }
          callback();
        });
      }
    },
    {
      name: 'current-time',
      description: 'Shows current time',
      action: function(args, callback) {
        this.log('Current time:', new Date().toLocaleString());
        callback();
      }
    }
  ]
};

module.exports = extensionObject;

Class-Based Extensions

More complex extensions using classes:

// database-extension.js
class DatabaseExtension {
  constructor(options = {}) {
    this.connectionString = options.connectionString;
    this.timeout = options.timeout || 5000;
  }
  
  install(vorpal) {
    const self = this;
    
    vorpal
      .command('db:connect', 'Connect to database')
      .action(function(args, callback) {
        self.connect(this, callback);
      });
    
    vorpal
      .command('db:query <sql>', 'Execute SQL query')
      .action(function(args, callback) {
        self.query(this, args.sql, callback);
      });
    
    vorpal
      .command('db:status', 'Show database status')
      .action(function(args, callback) {
        self.status(this, callback);
      });
  }
  
  connect(context, callback) {
    context.log('Connecting to database...');
    // Database connection logic
    setTimeout(() => {
      context.log('Connected successfully');
      callback();
    }, 1000);
  }
  
  query(context, sql, callback) {
    context.log(`Executing: ${sql}`);
    // Query execution logic
    setTimeout(() => {
      context.log('Query completed');
      callback();
    }, 500);
  }
  
  status(context, callback) {
    context.log('Database Status: Connected');
    context.log('Connection:', this.connectionString);
    callback();
  }
}

// Export function that creates and installs the extension
module.exports = function(options) {
  return function(vorpal) {
    const extension = new DatabaseExtension(options);
    extension.install(vorpal);
  };
};

// Usage
const vorpal = require('vorpal')();
const dbExtension = require('./database-extension');

vorpal.use(dbExtension({
  connectionString: 'mongodb://localhost:27017/mydb',
  timeout: 10000
}));

Built-in Extensions

Loading Common Extensions

Many community extensions are available:

const vorpal = require('vorpal')();

// File system commands
vorpal.use('vorpal-less');     // less command for viewing files
vorpal.use('vorpal-grep');     // grep command for searching

// Network utilities
vorpal.use('vorpal-tour');     // Interactive tours/tutorials

// Development tools
vorpal.use('vorpal-watch');    // File watching commands

Creating Modular Extensions

Split complex functionality across multiple extension files:

// commands/index.js - Main extension loader
const fileCommands = require('./file-commands');
const networkCommands = require('./network-commands');
const systemCommands = require('./system-commands');

module.exports = function(options = {}) {
  return function(vorpal) {
    if (options.includeFiles !== false) {
      vorpal.use(fileCommands);
    }
    
    if (options.includeNetwork !== false) {
      vorpal.use(networkCommands);
    }
    
    if (options.includeSystem !== false) {
      vorpal.use(systemCommands);
    }
  };
};

// Usage
const vorpal = require('vorpal')();
const myCommands = require('./commands');

vorpal.use(myCommands({
  includeNetwork: false  // Skip network commands
}));

Extension with Shared State

Extensions can maintain shared state:

// stateful-extension.js
function createStatefulExtension() {
  // Shared state across commands
  const state = {
    users: [],
    currentUser: null,
    settings: {}
  };
  
  return function(vorpal) {
    vorpal
      .command('user:add <name>', 'Add a user')
      .action(function(args, callback) {
        state.users.push({
          name: args.name,
          id: Date.now(),
          created: new Date()
        });
        this.log(`User ${args.name} added`);
        callback();
      });
    
    vorpal
      .command('user:list', 'List all users')
      .action(function(args, callback) {
        if (state.users.length === 0) {
          this.log('No users found');
        } else {
          this.log('Users:');
          state.users.forEach(user => {
            this.log(`- ${user.name} (ID: ${user.id})`);
          });
        }
        callback();
      });
    
    vorpal
      .command('user:select <name>', 'Select current user')
      .action(function(args, callback) {
        const user = state.users.find(u => u.name === args.name);
        if (user) {
          state.currentUser = user;
          this.log(`Selected user: ${user.name}`);
        } else {
          this.log(`User ${args.name} not found`);
        }
        callback();
      });
    
    vorpal
      .command('user:current', 'Show current user')
      .action(function(args, callback) {
        if (state.currentUser) {
          this.log('Current user:', state.currentUser.name);
        } else {
          this.log('No user selected');
        }
        callback();
      });
  };
}

module.exports = createStatefulExtension;

// Usage
const vorpal = require('vorpal')();
const statefulExtension = require('./stateful-extension');

vorpal.use(statefulExtension());

Extension Best Practices

Namespace Commands

Prefix commands to avoid conflicts:

function myExtension(vorpal, options) {
  const prefix = options?.prefix || 'ext:';
  
  vorpal
    .command(`${prefix}command1`, 'Description')
    .action(function(args, callback) {
      // Command logic
      callback();
    });
}

Provide Configuration Options

Make extensions configurable:

function configurableExtension(vorpal, options = {}) {
  const config = {
    timeout: options.timeout || 5000,
    verbose: options.verbose || false,
    prefix: options.prefix || '',
    ...options
  };
  
  // Use config throughout the extension
}

Error Handling

Handle errors gracefully in extensions:

function robustExtension(vorpal) {
  vorpal
    .command('risky-command', 'Might fail')
    .action(function(args, callback) {
      try {
        // Risky operation
        this.log('Success!');
        callback();
      } catch (error) {
        this.log('Error:', error.message);
        callback(error);
      }
    });
}

Complete Extension Example

// file-manager-extension.js
const fs = require('fs');
const path = require('path');

function fileManagerExtension(vorpal, options = {}) {
  const config = {
    showHidden: options.showHidden || false,
    defaultPath: options.defaultPath || process.cwd(),
    ...options
  };
  
  // Shared state
  let currentPath = config.defaultPath;
  
  vorpal
    .command('fm:pwd', 'Show current directory')
    .action(function(args, callback) {
      this.log('Current directory:', currentPath);
      callback();
    });
  
  vorpal
    .command('fm:cd [directory]', 'Change directory')
    .action(function(args, callback) {
      const newPath = args.directory 
        ? path.resolve(currentPath, args.directory)
        : config.defaultPath;
      
      fs.access(newPath, fs.constants.F_OK, (err) => {
        if (err) {
          this.log('Directory not found:', newPath);
        } else {
          currentPath = newPath;
          this.log('Changed to:', currentPath);
        }
        callback();
      });
    });
  
  vorpal
    .command('fm:ls [pattern]', 'List files')
    .option('-l, --long', 'Long format')
    .option('-a, --all', 'Show hidden files')
    .action(function(args, callback) {
      const showHidden = args.options.all || config.showHidden;
      const longFormat = args.options.long;
      
      fs.readdir(currentPath, (err, files) => {
        if (err) {
          this.log('Error reading directory:', err.message);
          callback();
          return;
        }
        
        let filteredFiles = files;
        
        if (!showHidden) {
          filteredFiles = files.filter(file => !file.startsWith('.'));
        }
        
        if (args.pattern) {
          const regex = new RegExp(args.pattern, 'i');
          filteredFiles = filteredFiles.filter(file => regex.test(file));
        }
        
        if (longFormat) {
          filteredFiles.forEach(file => {
            const fullPath = path.join(currentPath, file);
            fs.stat(fullPath, (err, stats) => {
              if (!err) {
                const size = stats.size;
                const modified = stats.mtime.toLocaleDateString();
                const type = stats.isDirectory() ? 'DIR' : 'FILE';
                this.log(`${type.padEnd(4)} ${size.toString().padStart(8)} ${modified} ${file}`);
              }
            });
          });
        } else {
          this.log(filteredFiles.join('  '));
        }
        
        callback();
      });
    });
}

module.exports = fileManagerExtension;

// Usage in main application
const vorpal = require('vorpal')();
const fileManager = require('./file-manager-extension');

vorpal.use(fileManager, {
  showHidden: true,
  defaultPath: '/home/user'
});

vorpal
  .delimiter('app$')
  .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