Standard input/output manager for Node.js with command-line parsing, async file reading, interactive terminal, and progress bars
—
Ask questions in terminal with option validation, retry mechanisms, and customizable input streams for building interactive command-line applications.
Displays questions and waits for user responses with optional validation and retry logic.
/**
* Ask an interactive question and wait for user response
* @param question - The question text to display
* @param config - Configuration options for validation and behavior
* @returns Promise resolving to the user's answer as string
*/
function ask(question: string, config?: AskConfig): Promise<string>;
interface AskConfig {
/** Array of valid answer options for validation */
options?: string[];
/** Maximum number of retry attempts on invalid input (default: 3) */
maxRetries?: number;
/** Custom input stream (defaults to process.stdin) */
inputStream?: any;
}Usage Examples:
import { ask } from "stdio";
// Basic question
const name = await ask("What's your name?");
console.log(`Hello, ${name}!`);
// Multiple choice with validation
const choice = await ask("Choose an option", {
options: ['yes', 'no', 'maybe']
});
console.log(`You chose: ${choice}`);
// Custom retry limit
const action = await ask("Confirm action", {
options: ['confirm', 'cancel'],
maxRetries: 5
});
// Free-form input (no validation)
const feedback = await ask("Any additional comments?");
// Complex interactive flow
const language = await ask("Select language", {
options: ['javascript', 'typescript', 'python']
});
const framework = language === 'javascript' || language === 'typescript'
? await ask("Select framework", { options: ['react', 'vue', 'angular'] })
: await ask("Select framework", { options: ['django', 'flask', 'fastapi'] });
console.log(`Selected: ${language} with ${framework}`);Comprehensive configuration for question behavior and validation.
interface AskConfig {
/**
* Valid answer options - if provided, only these answers are accepted
* Invalid answers trigger retry with helpful error message
*/
options?: string[];
/**
* Maximum retry attempts for invalid answers (default: 3)
* After exhausting retries, promise rejects with error
*/
maxRetries?: number;
/**
* Custom input stream for testing or alternative input sources
* Defaults to process.stdin for normal terminal interaction
*/
inputStream?: any;
}Questions are automatically formatted with helpful context:
// Basic question
await ask("Enter your email");
// Displays: "Enter your email: "
// With options
await ask("Continue?", { options: ['yes', 'no'] });
// Displays: "Continue? [yes/no]: "
// Multiple options
await ask("Select environment", {
options: ['development', 'staging', 'production']
});
// Displays: "Select environment [development/staging/production]: "Automatic validation with user-friendly error handling:
import { ask } from "stdio";
// This will retry until valid input
const answer = await ask("Are you sure?", {
options: ['yes', 'no'],
maxRetries: 3
});
// Invalid inputs show helpful messages:
// User types "maybe"
// Output: "Unexpected answer. 2 retries left."
// Prompt: "Are you sure? [yes/no]: "
// After 3 invalid attempts, promise rejects with:
// Error: "Retries spent"Support for alternative input sources for testing and automation:
import { ask } from "stdio";
import { Readable } from 'stream';
// Create mock input stream for testing
const mockInput = new Readable({
read() {
this.push('yes\n');
this.push(null); // End stream
}
});
const response = await ask("Proceed?", {
options: ['yes', 'no'],
inputStream: mockInput
});
// response === 'yes'
// Testing interactive flows
async function testInteractiveFlow() {
const responses = ['john', 'developer', 'yes'];
let responseIndex = 0;
const mockStream = new Readable({
read() {
if (responseIndex < responses.length) {
this.push(responses[responseIndex++] + '\n');
} else {
this.push(null);
}
}
});
const name = await ask("Name?", { inputStream: mockStream });
const role = await ask("Role?", { inputStream: mockStream });
const confirm = await ask("Confirm?", {
options: ['yes', 'no'],
inputStream: mockStream
});
return { name, role, confirm };
}Comprehensive error handling for various failure scenarios:
try {
const answer = await ask("Choose option", {
options: ['a', 'b', 'c'],
maxRetries: 2
});
console.log(`Selected: ${answer}`);
} catch (error) {
if (error.message === 'Retries spent') {
console.error('Too many invalid attempts');
process.exit(1);
} else {
console.error('Unexpected error:', error.message);
}
}
// Stream-related errors
try {
const brokenStream = new BrokenReadableStream();
await ask("Question?", { inputStream: brokenStream });
} catch (error) {
console.error('Input stream error:', error);
}Common patterns for building interactive applications:
Setup Wizard:
async function setupWizard() {
console.log('=== Application Setup ===');
const config = {};
config.name = await ask("Project name?");
config.type = await ask("Project type?", {
options: ['web', 'api', 'cli']
});
if (config.type === 'web') {
config.framework = await ask("Frontend framework?", {
options: ['react', 'vue', 'angular']
});
}
config.database = await ask("Database?", {
options: ['mysql', 'postgresql', 'mongodb', 'none']
});
const proceed = await ask("Create project with these settings?", {
options: ['yes', 'no']
});
if (proceed === 'yes') {
await createProject(config);
} else {
console.log('Setup cancelled');
}
}Confirmation Dialogs:
async function confirmAction(action: string): Promise<boolean> {
const response = await ask(`Are you sure you want to ${action}?`, {
options: ['yes', 'no'],
maxRetries: 1
});
return response === 'yes';
}
// Usage
if (await confirmAction('delete all files')) {
await deleteFiles();
} else {
console.log('Operation cancelled');
}Menu Selection:
async function showMenu(): Promise<string> {
console.log('Available actions:');
console.log('1. Create new item');
console.log('2. List items');
console.log('3. Delete item');
console.log('4. Exit');
const choice = await ask("Select action", {
options: ['1', '2', '3', '4'],
maxRetries: 5
});
const actions = {
'1': 'create',
'2': 'list',
'3': 'delete',
'4': 'exit'
};
return actions[choice];
}/** Default maximum retry attempts */
const DEFAULT_MAX_RETRIES = 3;The ask function uses sensible defaults:
process.stdin for normal terminal interactionprocess.stdout with formatted optionsInstall with Tessl CLI
npx tessl i tessl/npm-stdio