Refactor runtime around isolated bot instances

This commit is contained in:
zenord
2026-08-16 23:45:09 +08:00
parent 914579a94c
commit f9fb4ef776
51 changed files with 2355 additions and 1459 deletions
+20 -1
View File
@@ -1,8 +1,10 @@
import readline from "node:readline/promises";
import { stdin as input, stdout as output } from "node:process";
import { Writable } from "node:stream";
export interface PromptSession {
ask(question: string, defaultValue?: string): Promise<string>;
askSecret(question: string, hasExisting?: boolean): Promise<string>;
askBoolean(question: string, defaultValue?: boolean): Promise<boolean>;
askList(question: string, defaultValues?: string[]): Promise<string[]>;
choose<T>(question: string, choices: Choice<T>[], defaultIndex?: number): Promise<T>;
@@ -16,9 +18,14 @@ export interface Choice<T> {
}
export function createPromptSession(): PromptSession {
const rl = readline.createInterface({ input, output });
let rl = readline.createInterface({ input, output });
return {
ask: (question, defaultValue) => ask(rl, question, defaultValue),
askSecret: async (question, hasExisting) => {
rl.close();
try { return await askSecret(question, hasExisting); }
finally { rl = readline.createInterface({ input, output }); }
},
askBoolean: (question, defaultValue) => askBoolean(rl, question, defaultValue),
askList: (question, defaultValues) => askList(rl, question, defaultValues),
choose: (question, choices, defaultIndex) => choose(rl, question, choices, defaultIndex),
@@ -26,6 +33,18 @@ export function createPromptSession(): PromptSession {
};
}
async function askSecret(question: string, hasExisting = false): Promise<string> {
if (!input.isTTY || !output.isTTY) throw new Error("Secret input requires an interactive terminal");
const muted = new Writable({ write(_chunk, _encoding, callback) { callback(); } });
const secretRl = readline.createInterface({ input, output: muted, terminal: true });
output.write(`${question}${hasExisting ? " (leave blank to keep existing)" : ""}: `);
try {
const answer = (await secretRl.question("")).trim();
output.write("\n");
return answer;
} finally { secretRl.close(); }
}
export async function ask(rl: readline.Interface, question: string, defaultValue?: string): Promise<string> {
const suffix = defaultValue !== undefined && defaultValue !== "" ? ` [${defaultValue}]` : "";
const answer = (await rl.question(`${question}${suffix}: `)).trim();