Files
claw-apply/lib/apply/wellfound.mjs
Matthew Jackson bb0c96dd3d Enable Wellfound apply: fix missing apply_type, add submit verification
- Search now sets apply_type: 'wellfound' on discovered jobs (was missing,
  so applier silently skipped all Wellfound jobs)
- Add Wellfound to DEFAULT_ENABLED_APPLY_TYPES (LinkedIn first, then Wellfound)
- Sort platform processing order: linkedin → wellfound → external
- Apply handler: add closed listing detection, submit verification,
  error handling on meta extraction

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 12:48:03 -08:00

64 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* wellfound.mjs — Wellfound apply handler
*/
import {
NAVIGATION_TIMEOUT, PAGE_LOAD_WAIT, FORM_FILL_WAIT, SUBMIT_WAIT
} from '../constants.mjs';
export const SUPPORTED_TYPES = ['wellfound', 'wellfound_apply'];
export async function apply(page, job, formFiller) {
await page.goto(job.url, { waitUntil: 'domcontentloaded', timeout: NAVIGATION_TIMEOUT });
await page.waitForTimeout(PAGE_LOAD_WAIT);
const meta = await page.evaluate(() => ({
title: document.querySelector('h1')?.textContent?.trim(),
company: document.querySelector('[class*="company"] h2, [class*="startup"] h2, h2')?.textContent?.trim(),
})).catch(() => ({}));
// Check if listing is closed/unavailable
const closed = await page.evaluate(() => {
const text = (document.body.innerText || '').toLowerCase();
return text.includes('no longer accepting') || text.includes('position has been filled') ||
text.includes('this job is no longer') || text.includes('job not found');
}).catch(() => false);
if (closed) {
console.log(` Job closed — no longer available`);
return { status: 'skipped_no_apply', meta };
}
const applyBtn = page.locator('a:has-text("Apply"), button:has-text("Apply Now"), a:has-text("Apply Now")').first();
if (await applyBtn.count() === 0) return { status: 'no_button', meta };
await applyBtn.click();
await page.waitForTimeout(FORM_FILL_WAIT);
const unknowns = await formFiller.fill(page, formFiller.profile.resume_path);
if (unknowns[0]?.honeypot) return { status: 'skipped_honeypot', meta };
if (unknowns.length > 0) return { status: 'needs_answer', pending_question: unknowns[0], meta };
const submitBtn = await page.$('button[type="submit"]:not([disabled]), input[type="submit"]');
if (!submitBtn) return { status: 'no_submit', meta };
await submitBtn.click();
await page.waitForTimeout(SUBMIT_WAIT);
// Verify submission — check for success indicators or form gone
const postSubmit = await page.evaluate(() => {
const text = (document.body.innerText || '').toLowerCase();
return {
hasSuccess: text.includes('application submitted') || text.includes('successfully applied') ||
text.includes('thank you') || text.includes('application received'),
hasForm: !!document.querySelector('form button[type="submit"]:not([disabled])'),
};
}).catch(() => ({ hasSuccess: false, hasForm: false }));
if (postSubmit.hasSuccess || !postSubmit.hasForm) {
return { status: 'submitted', meta };
}
console.log(` ⚠️ Submit clicked but form still present — may not have submitted`);
return { status: 'incomplete', meta };
}