or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-api.mdcustom-prompts.mdform-survey-prompts.mdindex.mdselection-prompts.mdspecialized-prompts.mdtext-prompts.md
tile.json

custom-prompts.mddocs/

Custom Prompt Development

Enquirer provides a flexible architecture for creating custom prompt types using base classes and extending the built-in functionality to meet specific requirements.

Capabilities

Base Prompt Classes

Foundation classes that provide core functionality for different prompt categories.

/**
 * Base prompt class - foundation for all prompts
 */
class Prompt extends EventEmitter {
  constructor(options);
  run();
  render();
  submit();
  cancel();
}

/**
 * Base class for array-based prompts (select, multiselect, etc.)
 */
class ArrayPrompt extends Prompt {
  constructor(options);
  dispatch(char, key);
  append(char);
  delete();
  space();
  number(char);
  a(); // Select all
  i(); // Invert selection
}

/**
 * Base class for string input prompts
 */
class StringPrompt extends Prompt {
  constructor(options);
  append(char);
  delete();
  deleteForward();
  cutForward();
  cutLeft();
}

/**
 * Base class for boolean prompts
 */ 
class BooleanPrompt extends Prompt {
  constructor(options);
  dispatch(char, key);
}

/**
 * Base class for numeric prompts
 */
class NumberPrompt extends Prompt {
  constructor(options);
  append(char);
  delete();
  multiply(n);
  divide(n);
}

/**
 * Base class for authentication prompts
 */
class AuthPrompt extends Prompt {
  constructor(options);
  submit();
}

Creating Custom Prompts

Extend base classes to create custom prompt functionality.

Basic Custom Prompt Example:

const { StringPrompt } = require('enquirer');

class EmailPrompt extends StringPrompt {
  constructor(options) {
    super(options);
    this.type = 'email';
  }

  async submit() {
    // Email validation
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(this.value)) {
      this.state.error = 'Please enter a valid email address';
      return this.render();
    }
    
    return super.submit();
  }

  format(value) {
    return value.toLowerCase().trim();
  }
}

// Register and use
const { Enquirer } = require('enquirer');
const enquirer = new Enquirer();
enquirer.register('email', EmailPrompt);

const response = await enquirer.prompt({
  type: 'email',
  name: 'userEmail',
  message: 'Enter your email address'
});

Advanced Custom Array Prompt:

const { ArrayPrompt } = require('enquirer');

class TagsPrompt extends ArrayPrompt {
  constructor(options) {
    super(options);
    this.type = 'tags';
    this.tags = [];
  }

  async keypress(char, key) {
    // Handle comma to add tag
    if (char === ',') {
      return this.addTag();
    }
    
    // Handle backspace to remove tag
    if (key.name === 'backspace' && this.input === '') {
      return this.removeLastTag();
    }
    
    return super.keypress(char, key);
  }

  addTag() {
    const tag = this.input.trim();
    if (tag && !this.tags.includes(tag)) {
      this.tags.push(tag);
      this.input = '';
      this.cursor = 0;
    }
    this.render();
  }

  removeLastTag() {
    if (this.tags.length > 0) {
      this.tags.pop();
      this.render();
    }
  }

  submit() {
    // Add current input as final tag
    if (this.input.trim()) {
      this.addTag();
    }
    
    this.value = this.tags;
    return super.submit();
  }

  render() {
    const tags = this.tags.map(tag => `[${tag}]`).join(' ');
    const prompt = `${this.message} ${tags} ${this.input}`;
    this.clear();
    this.write(prompt);
  }
}

Registering Custom Prompts

Register custom prompts for use in your application.

/**
 * Register custom prompt with Enquirer instance
 * @param type - Prompt type name
 * @param promptClass - Custom prompt class
 */
enquirer.register(type, promptClass);

/**
 * Register multiple custom prompts
 * @param prompts - Object mapping type names to prompt classes
 */
enquirer.register(prompts);

Usage Examples:

const { Enquirer } = require('enquirer');

// Single registration
enquirer.register('email', EmailPrompt);
enquirer.register('tags', TagsPrompt);

// Multiple registration
enquirer.register({
  'email': EmailPrompt,
  'tags': TagsPrompt,
  'color': ColorPickerPrompt,
  'date': DatePrompt
});

// Use registered prompts
const responses = await enquirer.prompt([
  {
    type: 'email',
    name: 'email',
    message: 'Email address?'
  },
  {
    type: 'tags',
    name: 'skills',
    message: 'Enter skills (comma-separated):'
  }
]);

Custom Prompt Lifecycle

Understanding the prompt lifecycle helps in creating robust custom prompts.

class CustomPrompt extends Prompt {
  constructor(options) {
    super(options);
    // Initialize prompt state
  }

  initialize() {
    // Called before prompt starts
    // Setup initial state, validate options
  }

  render() {
    // Called to display prompt to user
    // Clear screen, write prompt, position cursor
  }

  keypress(char, key) {
    // Handle user input
    // Process keystrokes, update state
  }

  submit() {
    // Handle submission
    // Validate input, format result, emit events
  }

  cancel() {
    // Handle cancellation (Ctrl+C)
    // Cleanup, emit cancel event
  }
}

State Management

Custom prompts maintain state throughout their lifecycle.

class StatefulPrompt extends Prompt {
  constructor(options) {
    super(options);
    
    // Initialize state
    this.state = {
      status: 'pending',
      error: null,
      index: 0,
      value: '',
      submitted: false,
      cancelled: false
    };
  }

  updateState(updates) {
    Object.assign(this.state, updates);
    this.render();
  }

  submit() {
    if (this.validate()) {
      this.state.status = 'submitted';
      return super.submit();
    }
    
    this.state.error = 'Invalid input';
    this.render();
  }
}

Event Handling

Custom prompts can emit and listen for events.

class EventfulPrompt extends Prompt {
  constructor(options) {
    super(options);
    
    // Listen for internal events
    this.on('keypress', this.handleKeypress.bind(this));
    this.on('state', this.handleStateChange.bind(this));
  }

  handleKeypress(char, key) {
    // Emit custom events
    this.emit('input', { char, key, value: this.value });
  }

  handleStateChange(state) {
    // React to state changes
    if (state.error) {
      this.emit('error', state.error);
    }
  }

  submit() {
    // Emit submission event
    this.emit('submit', this.value);
    return super.submit();
  }
}

// Usage with event listeners
const prompt = new EventfulPrompt(options);

prompt.on('input', (data) => {
  console.log('User input:', data);
});

prompt.on('error', (error) => {
  console.log('Validation error:', error);
});

prompt.on('submit', (value) => {
  console.log('Submitted value:', value);
});

Utility Methods

Helper methods for common custom prompt tasks.

/**
 * Utility methods available in custom prompts
 */
class CustomPrompt extends Prompt {
  // Screen management
  clear();           // Clear the terminal
  write(str);        // Write string to stdout
  moveCursor(x, y);  // Move cursor position

  // Input processing
  append(char);      // Add character to input
  delete();          // Delete character (backspace)
  deleteForward();   // Delete character forward

  // Formatting
  format(value);     // Format the final value
  result(value);     // Transform result before returning

  // Validation
  validate(value);   // Validate user input
  
  // State helpers
  isValid();         // Check if current state is valid
  hasError();        // Check if there's an error
}

Plugin Architecture

Create plugins that extend Enquirer functionality.

function customPromptPlugin(enquirer) {
  // Add custom prompts
  enquirer.register({
    'email': EmailPrompt,
    'phone': PhonePrompt,
    'url': UrlPrompt
  });

  // Add utility methods
  enquirer.askEmail = async (message, options = {}) => {
    return enquirer.prompt({
      type: 'email',
      name: 'email',
      message,
      ...options
    });
  };

  // Add default options
  enquirer.options = {
    ...enquirer.options,
    prefix: '🤖',
    footer: 'Use Ctrl+C to cancel'
  };
}

// Use plugin
const enquirer = new Enquirer();
enquirer.use(customPromptPlugin);

// Use plugin-added functionality
const email = await enquirer.askEmail('What is your email?');

Testing Custom Prompts

Test custom prompts with simulated input.

const { EventEmitter } = require('events');

class MockStream extends EventEmitter {
  constructor() {
    super();
    this.output = '';
  }

  write(str) {
    this.output += str;
  }

  clearLine() {}
  cursorTo() {}
}

async function testCustomPrompt() {
  const stdin = new MockStream();
  const stdout = new MockStream();

  const prompt = new CustomPrompt({
    message: 'Test prompt',
    stdin,
    stdout
  });

  // Simulate user input
  setTimeout(() => {
    stdin.emit('keypress', 'h', { name: 'h' });
    stdin.emit('keypress', 'i', { name: 'i' });
    stdin.emit('keypress', '\r', { name: 'return' });
  }, 10);

  const result = await prompt.run();
  console.log('Result:', result);
  console.log('Output:', stdout.output);
}