refactor(tts): CLI argument parsing and input handling

This commit is contained in:
mitchell 2026-06-14 15:39:41 -04:00
parent 749559e19c
commit dd6df6dd6d

52
tts.ts
View file

@ -12,41 +12,34 @@ import { execa } from "execa";
import { Window } from "happy-dom"; import { Window } from "happy-dom";
import wrapAnsi from "wrap-ansi"; import wrapAnsi from "wrap-ansi";
class InputError extends Error {
override name = "InputError";
}
async function main() { async function main() {
const start = Date.now(); const start = Date.now();
const { values: args } = util.parseArgs({ const { values: args, positionals } = util.parseArgs({
args: process.argv.slice(2), args: process.argv.slice(2),
options: { options: {
wait: { type: "boolean" }, wait: { type: "boolean" },
w: { type: "boolean" }, w: { type: "boolean" },
url: { type: "string" }, url: { type: "boolean" },
u: { type: "string" }, u: { type: "boolean" },
edit: { type: "boolean" }, edit: { type: "boolean" },
e: { type: "boolean" }, e: { type: "boolean" },
}, },
strict: true, strict: true,
allowPositionals: false, allowPositionals: true,
}); });
const wait = args.wait ?? args.w; const wait = args.wait ?? args.w;
const url = args.url ?? args.u; const url = args.url ?? args.u;
const edit = args.edit ?? args.e; const edit = args.edit ?? args.e;
let input = ""; let input = positionals.join(" ").trim();
if (url?.trim()) { if (!input) {
const result = await fetch(url.trim());
const window = new Window({ url: url.trim() });
const doc = window.document;
doc.write(await result.text());
await window.happyDOM.waitUntilComplete();
const readable = new Readability(doc).parse();
if (!readable?.textContent) {
throw "Unable to read web page.";
}
input = readable.textContent.trim();
} else {
process.stdin.setEncoding("utf8"); process.stdin.setEncoding("utf8");
process.stdin.on("data", (data) => { process.stdin.on("data", (data) => {
input += data; input += data;
@ -55,7 +48,24 @@ async function main() {
} }
if (!input) { if (!input) {
throw "No input given."; throw new InputError(
"No input was given. Use positional arguments or stdin.",
);
}
if (url) {
const result = await fetch(input);
const window = new Window({ url: input });
const doc = window.document;
doc.write(await result.text());
await window.happyDOM.waitUntilComplete();
const readable = new Readability(doc).parse();
if (!readable?.textContent) {
throw new InputError(
"Unable to read webpage. This usually means it's blocking scrapers.",
);
}
input = readable.textContent.trim();
} }
const home = os.homedir(); const home = os.homedir();
@ -63,7 +73,7 @@ async function main() {
if (edit) { if (edit) {
const file = path.join(cwd, "input.txt"); const file = path.join(cwd, "input.txt");
await fs.writeFile(file, input); await fs.writeFile(file, input, "utf8");
const editor = process.env.EDITOR || "nano"; const editor = process.env.EDITOR || "nano";
await execa({ await execa({
cwd, cwd,
@ -154,11 +164,11 @@ async function main() {
} catch (err) { } catch (err) {
if (err instanceof Object && "code" in err && err.code === "ENOENT") { if (err instanceof Object && "code" in err && err.code === "ENOENT") {
console.error("Sox not found. Unable to combine outputs."); console.error("Sox not found. Unable to combine outputs.");
console.log(chalk.yellow(`Audio available in ${cwd}`));
return;
} else { } else {
throw err; throw err;
} }
console.log(chalk.yellow(`Audio available in ${cwd}`));
return;
} }
const [columns] = process.stdout.getWindowSize(); const [columns] = process.stdout.getWindowSize();