Use @inquirer/prompts for interactive login input

Replaces the hand-rolled readline/raw-mode password prompt with
@inquirer/prompts (input + password). The manual approach broke in
bun-compiled binaries because bun doesn't properly re-open stdin
after closing a readline instance. inquirer handles TTY, raw mode,
and password masking correctly across both node and bun runtimes.
This commit is contained in:
2026-06-10 10:54:17 -07:00
parent 3d6742945b
commit 3d76bd092f
3 changed files with 199 additions and 53 deletions

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env node
import { createInterface } from "node:readline/promises";
import { stdin, stdout, stderr } from "node:process";
import { input, password as passwordPrompt } from "@inquirer/prompts";
import { stdout, stderr } from "node:process";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { Command } from "commander";
@@ -45,46 +45,10 @@ const requireSession = (): Client => {
return Client.fromJSON(snapshot);
};
const prompt = async (message: string): Promise<string> => {
const rl = createInterface({ input: stdin, output: stderr });
const answer = await rl.question(message);
rl.close();
return answer;
};
const prompt = async (message: string): Promise<string> => input({ message });
const promptPassword = async (message: string): Promise<string> => {
if (!stdin.isTTY) {
return prompt(message);
}
stderr.write(message);
stdin.setRawMode(true);
stdin.resume();
const chars: string[] = [];
return new Promise((resolve) => {
const onData = (buf: Buffer) => {
for (const byte of buf) {
if (byte === 3) {
stdin.setRawMode(false);
process.exit(130);
}
if (byte === 13 || byte === 10) {
stdin.setRawMode(false);
stdin.removeListener("data", onData);
stdin.pause();
stderr.write("\n");
resolve(chars.join(""));
return;
}
if (byte === 127 || byte === 8) {
chars.pop();
} else {
chars.push(String.fromCharCode(byte));
}
}
};
stdin.on("data", onData);
});
};
const promptSecret = async (message: string): Promise<string> =>
passwordPrompt({ message, mask: true });
const program = new Command();
@@ -98,18 +62,9 @@ program
.description("Log in to an Ente account and save the session")
.action(async () => {
await init();
const email =
process.env.QUAK_EMAIL ??
(stdin.isTTY ? await prompt("Email: ") : null);
const email = process.env.QUAK_EMAIL ?? (await prompt("Email"));
const password =
process.env.QUAK_PASSWORD ??
(stdin.isTTY ? await promptPassword("Password: ") : null);
if (!email || !password) {
stderr.write(
"Set QUAK_EMAIL and QUAK_PASSWORD env vars for non-interactive use.\n",
);
process.exit(1);
}
process.env.QUAK_PASSWORD ?? (await promptSecret("Password"));
stderr.write("Authenticating...\n");
try {