Files
claw-apply/lib/apply/index.mjs
Matthew Jackson ae797d73eb Refactor apply handlers: generic with extensions + domain auto-routing
Generic handler now accepts options (transformUrl, formDetector,
submitSelector, resumeSelector, beforeSubmit, verifySelector, etc.).
Each ATS handler passes its overrides instead of reimplementing.

Registry resolves handlers by: apply_type -> URL domain -> generic fallback.
New ATS handlers only need to export SUPPORTED_TYPES and an apply() that
calls genericApply with platform-specific options.

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

96 lines
2.7 KiB
JavaScript

/**
* index.mjs — Apply handler registry
*
* Two lookup mechanisms:
* 1. apply_type → handler (explicit ATS classification)
* 2. apply_url domain → handler (auto-detect from URL, fallback to generic)
*/
import * as easyApply from './easy_apply.mjs';
import * as greenhouse from './greenhouse.mjs';
import * as lever from './lever.mjs';
import * as workday from './workday.mjs';
import * as ashby from './ashby.mjs';
import * as jobvite from './jobvite.mjs';
import * as wellfound from './wellfound.mjs';
import * as generic from './generic.mjs';
const ALL_HANDLERS = [
easyApply,
greenhouse,
lever,
workday,
ashby,
jobvite,
wellfound,
generic,
];
// Build registry: apply_type → handler
const REGISTRY = {};
for (const handler of ALL_HANDLERS) {
for (const type of handler.SUPPORTED_TYPES) {
REGISTRY[type] = handler;
}
}
// Domain → handler mapping for URL-based auto-detection
// When apply_type is unknown_external, match apply_url against these patterns
const DOMAIN_REGISTRY = [
{ pattern: /ashbyhq\.com/i, handler: ashby },
{ pattern: /greenhouse\.io|grnh\.se/i, handler: greenhouse },
{ pattern: /lever\.co|jobs\.lever\.co/i, handler: lever },
{ pattern: /workday\.com|myworkdayjobs\.com|myworkdaysite\.com/i, handler: workday },
{ pattern: /jobvite\.com|applytojob\.com/i, handler: jobvite },
];
/**
* Get handler for a job — checks apply_type first, then URL domain, then generic
*/
function resolveHandler(job) {
// Explicit type match
if (job.apply_type && REGISTRY[job.apply_type]) {
return REGISTRY[job.apply_type];
}
// Domain match from apply_url
if (job.apply_url) {
for (const { pattern, handler } of DOMAIN_REGISTRY) {
if (pattern.test(job.apply_url)) return handler;
}
}
// Fallback to generic if it has a URL, otherwise unsupported
return job.apply_url ? generic : null;
}
/**
* List all supported apply types
*/
export function supportedTypes() {
return Object.keys(REGISTRY);
}
const STATUS_MAP = {
no_button: 'skipped_no_apply',
no_submit: 'skipped_no_apply',
no_modal: 'skipped_no_apply',
};
/**
* Apply to a job using the appropriate handler
* Returns result object with normalized status
*/
export async function applyToJob(page, job, formFiller) {
const handler = resolveHandler(job);
if (!handler) {
return {
status: 'skipped_external_unsupported',
meta: { title: job.title, company: job.company },
externalUrl: job.apply_url || '',
ats_platform: job.apply_type || 'unknown',
};
}
const result = await handler.apply(page, job, formFiller);
return { ...result, status: STATUS_MAP[result.status] || result.status };
}