Repository Public MIT
1import { createInterface } from "node:readline";2import { homedir } from "node:os";34/** Everything the CLI touches from the outside world, injectable so tests can run it in-process. */5export interface IO {6 stdout: (s: string) => void;7 stderr: (s: string) => void;8 /** Read all of stdin (used for --token-stdin and piped input). */9 stdin: () => Promise<string>;10 isTTY: boolean;11 prompt: (question: string, opts?: { hidden?: boolean }) => Promise<string>;12 env: Record<string, string | undefined>;13 cwd: string;14 home: string;15 platform: NodeJS.Platform;16 fetch?: typeof fetch;17}1819function ttyPrompt(question: string, hidden: boolean): Promise<string> {20 return new Promise((resolve) => {21 const rl = createInterface({ input: process.stdin, output: process.stderr, terminal: true });22 if (hidden) {23 // Print the question once, then swallow every echo of typed characters.24 (rl as unknown as { _writeToOutput: (s: string) => void })._writeToOutput = (s: string) => {25 if (s.includes(question)) process.stderr.write(question);26 };27 }28 rl.question(question, (answer) => {29 rl.close();30 if (hidden) process.stderr.write("\n");31 resolve(answer);32 });33 });34}3536async function readAllStdin(): Promise<string> {37 const chunks: Buffer[] = [];38 for await (const c of process.stdin) chunks.push(Buffer.from(c as Buffer));39 return Buffer.concat(chunks).toString("utf8");40}4142export function defaultIO(): IO {43 return {44 stdout: (s) => void process.stdout.write(s),45 stderr: (s) => void process.stderr.write(s),46 stdin: readAllStdin,47 isTTY: !!process.stdin.isTTY && !!process.stderr.isTTY,48 prompt: (q, o) => ttyPrompt(q, !!o?.hidden),49 env: process.env,50 cwd: process.cwd(),51 home: homedir(),52 platform: process.platform,53 };54}55