/** * POC — drive chatgpt.com inside a totally temporary Firefox profile * (puppeteer-core, WebDriver BiDi, headed). The profile is created in * /tmp at launch and deleted on exit; login is done manually in the * window when the script asks for it. * * Usage: * bun scripts/poc-chatgpt.ts # send default test message * bun scripts/poc-chatgpt.ts --message "hello" # custom message (\n supported) * bun scripts/poc-chatgpt.ts --message $'l1\nl2' # multi-line test * bun scripts/poc-chatgpt.ts --probe # just open + report page state * bun scripts/poc-chatgpt.ts --timeout-s 600 # longer response timeout */ import { ChatGPTBrowserEngine } from "../src/drivers/browser.ts"; const DEFAULT_MESSAGE = "Hello! Answer with one short sentence so we know the pipeline works."; function flag(name: string): boolean { return process.argv.includes(name); } function option(name: string): string | undefined { const i = process.argv.indexOf(name); return i !== -1 && i + 1 < process.argv.length ? process.argv[i + 1] : undefined; } const exe = process.env.PUPPETEER_EXECUTABLE ?? "firefox"; const executablePath = exe.includes("/") ? exe : Bun.which(exe); if (!executablePath) { console.error("firefox not found — set PUPPETEER_EXECUTABLE to the Firefox binary path"); process.exit(1); } const responseTimeoutS = Number(option("--timeout-s") ?? 300); if (!Number.isFinite(responseTimeoutS) || responseTimeoutS <= 0) { console.error("--timeout-s must be a positive number of seconds"); process.exit(1); } const engine = new ChatGPTBrowserEngine({ executablePath, headless: flag("--headless"), loginTimeoutMs: 600_000, responseTimeoutMs: responseTimeoutS * 1000, }); let interrupted = 0; process.on("SIGINT", () => { interrupted++; if (interrupted > 1) process.exit(130); console.log("\n[interrupt] closing browser and deleting the temporary profile…"); void engine.close().finally(() => process.exit(130)); }); const t0 = Date.now(); const step = (msg: string) => console.log(`[${((Date.now() - t0) / 1000).toFixed(1)}s] ${msg}`); try { if (flag("--probe")) { step(`launching Firefox (${executablePath}) with a fresh throwaway profile`); const info = await engine.probe(); console.log(JSON.stringify(info, null, 2)); step(info.composer ? `composer ready: ${info.composer}` : "composer NOT found — login wall or challenge page"); } else { const message = option("--message") ?? DEFAULT_MESSAGE; step(`launching Firefox (${executablePath}${flag("--headless") ? ", headless" : ""}) — temporary profile, deleted on exit`); let streamed = ""; const final = await engine.send(message, { onLoginHint: () => step("login required: log in inside the Firefox window (waiting up to 10 min)"), onToken: (delta) => { streamed += delta; process.stdout.write(delta); }, }); console.log(); if (!streamed.trim()) console.log(final); step(`done — response above (${final.length} chars)`); } process.exitCode = 0; } catch (e) { console.error(`\n[error] ${(e as Error).message}`); process.exitCode = 1; } finally { await engine.close(); step("browser closed, temporary profile deleted"); }