UNPKG

@xec-sh/core

Version:

Universal shell execution engine

183 lines 6.19 kB
import { createInterface } from 'node:readline'; export class InteractiveSession { constructor(engine, options = {}) { this.engine = engine; this.options = options; this.rl = createInterface({ input: this.options.input || process.stdin, output: this.options.output || process.stdout, terminal: this.options.terminal ?? true }); } async question(prompt, options = {}) { const { defaultValue, choices, validate, mask, multiline } = options; let displayPrompt = prompt; if (choices && choices.length > 0) { displayPrompt += '\n' + choices.map((c, i) => ` ${i + 1}. ${c}`).join('\n'); displayPrompt += '\nChoice: '; } else if (defaultValue) { displayPrompt += ` (${defaultValue}): `; } else { displayPrompt += ': '; } return new Promise((resolve, reject) => { const askQuestion = () => { this.rl.question(displayPrompt, async (answer) => { answer = answer.trim() || defaultValue || ''; if (choices && choices.length > 0) { const choiceIndex = parseInt(answer) - 1; if (choiceIndex >= 0 && choiceIndex < choices.length) { const selectedChoice = choices[choiceIndex]; if (selectedChoice !== undefined) { answer = selectedChoice; } } else if (!choices.includes(answer)) { console.log('Invalid choice. Please try again.'); return askQuestion(); } } if (validate) { const validationResult = validate(answer); if (validationResult !== true) { console.log(typeof validationResult === 'string' ? validationResult : 'Invalid input'); return askQuestion(); } } resolve(answer); }); }; askQuestion(); }); } async confirm(prompt, defaultValue = false) { const answer = await this.question(`${prompt} (${defaultValue ? 'Y/n' : 'y/N'})`, { defaultValue: defaultValue ? 'y' : 'n' }); return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'; } async select(prompt, choices) { return this.question(prompt, { choices }); } async multiselect(prompt, choices) { const displayPrompt = prompt + '\n' + choices.map((c, i) => ` ${i + 1}. ${c}`).join('\n') + '\nEnter numbers separated by commas: '; const answer = await this.question(displayPrompt); const indices = answer.split(',').map(s => parseInt(s.trim()) - 1); return indices .filter(i => i >= 0 && i < choices.length) .map(i => choices[i]) .filter((choice) => choice !== undefined); } async password(prompt) { return new Promise((resolve) => { const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true }); rl.stdoutMuted = true; rl.question(prompt + ': ', (password) => { rl.close(); console.log(); resolve(password); }); rl._writeToOutput = function (char) { if (!rl.stdoutMuted) { rl.output.write(char); } }; }); } close() { this.rl.close(); } } export async function question(engine, prompt, options) { const session = new InteractiveSession(engine); try { return await session.question(prompt, options); } finally { session.close(); } } export async function confirm(engine, prompt, defaultValue) { const session = new InteractiveSession(engine); try { return await session.confirm(prompt, defaultValue); } finally { session.close(); } } export async function select(engine, prompt, choices) { const session = new InteractiveSession(engine); try { return await session.select(prompt, choices); } finally { session.close(); } } export async function password(engine, prompt) { const session = new InteractiveSession(engine); try { return await session.password(prompt); } finally { session.close(); } } export class Spinner { constructor(text) { this.frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']; this.currentFrame = 0; this.interval = null; this.text = ''; if (text) this.text = text; } start(text) { if (text) this.text = text; this.interval = setInterval(() => { process.stdout.write(`\r${this.frames[this.currentFrame]} ${this.text}`); this.currentFrame = (this.currentFrame + 1) % this.frames.length; }, 80); } update(text) { this.text = text; } succeed(text) { this.stop(); console.log(`\r✓ ${text || this.text}`); } fail(text) { this.stop(); console.log(`\r✗ ${text || this.text}`); } stop() { if (this.interval) { clearInterval(this.interval); this.interval = null; process.stdout.write('\r' + ' '.repeat(this.text.length + 4) + '\r'); } } } export async function withSpinner(text, fn) { const spinner = new Spinner(text); spinner.start(); try { const result = await fn(); spinner.succeed(); return result; } catch (error) { spinner.fail(); throw error; } } export { createInteractiveSession } from './interactive-process.js'; //# sourceMappingURL=interactive.js.map