/** * Auto-apply worker (Playwright). * * Usage: * npm i -D playwright && npx playwright install chromium # one-time * npx tsx worker/apply.ts [--submit] * * Safety model: * - WITHOUT --submit it fills the form, screenshots every step, and STOPS * before the final submit (dry run). You review screenshots in worker/out/. * - WITH --submit it still pauses on CAPTCHAs/Cloudflare (headed mode) and * waits for you to solve them; it only clicks submit on adapter-known forms. */ import { PrismaClient } from "@prisma/client"; import { adapters } from "./adapters"; const prisma = new PrismaClient(); async function main() { const [jobId, ...flags] = process.argv.slice(2); const submit = flags.includes("--submit"); if (!jobId) { console.error("usage: npx tsx worker/apply.ts [--submit]"); process.exit(1); } const job = await prisma.job.findUnique({ where: { id: jobId } }); if (!job) throw new Error("job not found"); const profile = await prisma.profile.findUnique({ where: { id: "main" } }); if (!profile) throw new Error("no profile โ€” fill in /profile first"); const url = job.applyUrl ?? job.url; if (!url) throw new Error("job has no applyUrl/url"); const adapter = adapters.find((a) => a.matches(url)); if (!adapter) { console.error(`No adapter matches ${url}.\nKnown adapters: ${adapters.map((a) => a.name).join(", ")}\nUse the in-app Apply Wizard (semi-auto) instead.`); process.exit(2); } console.log(`Adapter: ${adapter.name} ยท submit=${submit}`); const log = await adapter.run({ job, profile, submit }); await prisma.application.create({ data: { jobId, method: "auto", submittedAt: submit && log.submitted ? new Date() : null, log: log.lines.join("\n"), }, }); if (submit && log.submitted) { await prisma.job.update({ where: { id: jobId }, data: { status: "applied" } }); } console.log(log.submitted ? "SUBMITTED โœ…" : "DRY RUN complete โ€” review worker/out/ screenshots, re-run with --submit to finish."); } main() .catch((e) => { console.error(e); process.exit(1); }) .finally(() => prisma.$disconnect());