Something went wrong. Try again.
A fully featured CLI used as an alternative to AppView applications (Bsky.app/mu.social)
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273import * as Readline from 'node:readline/promises';import { NeedsInputError } from './errors.ts';
// Asking the person at the terminal. Every question needs a terminal on stdin// and stderr; without one (an agent, a pipe) the command fails with exit 6 and// says which option answers the question instead.
export function interactive(): boolean { return Boolean(process.stdin.isTTY && process.stderr.isTTY);}
function requireTerminal(instead: string): void { if (!interactive()) throw new NeedsInputError(`there is no terminal to ask on; ${instead}`);}
export async function ask(question: string, instead: string): Promise<string> { requireTerminal(instead); const rl = Readline.createInterface({ input: process.stdin, output: process.stderr }); try { return (await rl.question(`? ${question} `)).trim(); } finally { rl.close(); }}
export async function confirm(question: string, instead = 'pass --yes'): Promise<boolean> { const answer = (await ask(`${question} [y/N]`, instead)).toLowerCase(); return answer === 'y' || answer === 'yes';}
/** A line read without echoing it. */export async function secret(question: string, instead: string): Promise<string> { requireTerminal(instead); const input = process.stdin; process.stderr.write(`? ${question} `); input.setRawMode(true); input.resume(); input.setEncoding('utf-8'); return await new Promise((resolve, reject) => { let value = ''; const done = (error?: Error) => { input.setRawMode(false); input.pause(); input.off('data', onData); process.stderr.write('\n'); if (error) reject(error); else resolve(value); }; const onData = (chunk: string) => { for (const char of chunk) { if (char === '\r' || char === '\n' || char === '\u0004') return done(); if (char === '\u0003') return done(new NeedsInputError('cancelled')); if (char === '\u007f' || char === '\b') value = value.slice(0, -1); else value += char; } }; input.on('data', onData); });}
/** One line from the terminal, for a pasted address; resolves never when there is no terminal. */export function pastedLine(): Promise<string> { if (!interactive()) return new Promise(() => {}); return new Promise((resolve) => { const rl = Readline.createInterface({ input: process.stdin, terminal: false }); rl.on('line', (line) => { if (!/^https?:\/\//.test(line.trim())) return; rl.close(); resolve(line); }); });}