Files
claw-apply/job_searcher.mjs
Matthew Jackson e71f940687 fix: add config validation and retry logic for failed jobs
- Add loadConfig() helper with clear errors for missing/malformed JSON
- Replace raw JSON.parse(readFileSync(...)) in both entry points
- Track retry_count on jobs; re-queue as 'new' up to max_retries (default 2)
- Add max_retries and DEFAULT_MAX_RETRIES constant

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 16:03:33 -08:00

110 lines
3.9 KiB
JavaScript

#!/usr/bin/env node
/**
* job_searcher.mjs — claw-apply Job Searcher
* Searches LinkedIn + Wellfound and populates the jobs queue
* Run via cron or manually: node job_searcher.mjs
*/
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';
const __dir = dirname(fileURLToPath(import.meta.url));
import { addJobs, loadQueue, loadConfig } from './lib/queue.mjs';
import { createBrowser } from './lib/browser.mjs';
import { verifyLogin as liLogin, searchLinkedIn } from './lib/linkedin.mjs';
import { verifyLogin as wfLogin, searchWellfound } from './lib/wellfound.mjs';
import { sendTelegram, formatSearchSummary } from './lib/notify.mjs';
import { DEFAULT_FIRST_RUN_DAYS } from './lib/constants.mjs';
async function main() {
console.log('🔍 claw-apply: Job Searcher starting\n');
// Load config
const settings = loadConfig(resolve(__dir, 'config/settings.json'));
const searchConfig = loadConfig(resolve(__dir, 'config/search_config.json'));
// First run detection: if queue is empty, use first_run_days lookback
const isFirstRun = loadQueue().length === 0;
const lookbackDays = isFirstRun ? (searchConfig.first_run_days || DEFAULT_FIRST_RUN_DAYS) : null;
if (isFirstRun) console.log(`📅 First run — looking back ${lookbackDays} days\n`);
let totalAdded = 0;
let totalSeen = 0;
const platformsRun = [];
// Group searches by platform
const liSearches = searchConfig.searches.filter(s => s.platforms?.includes('linkedin'));
const wfSearches = searchConfig.searches.filter(s => s.platforms?.includes('wellfound'));
// --- LinkedIn ---
if (liSearches.length > 0) {
console.log('🔗 LinkedIn search...');
let liBrowser;
try {
liBrowser = await createBrowser(settings, 'linkedin');
const loggedIn = await liLogin(liBrowser.page);
if (!loggedIn) throw new Error('LinkedIn not logged in');
console.log(' ✅ Logged in');
for (const search of liSearches) {
const effectiveSearch = lookbackDays
? { ...search, filters: { ...search.filters, posted_within_days: lookbackDays } }
: search;
const jobs = await searchLinkedIn(liBrowser.page, effectiveSearch);
const added = addJobs(jobs);
totalAdded += added;
totalSeen += jobs.length;
console.log(` [${search.name}] ${jobs.length} found, ${added} new`);
}
platformsRun.push('LinkedIn');
} catch (e) {
console.error(` ❌ LinkedIn error: ${e.message}`);
} finally {
await liBrowser?.browser?.close().catch(() => {});
}
}
// --- Wellfound ---
if (wfSearches.length > 0) {
console.log('\n🌐 Wellfound search...');
let wfBrowser;
try {
wfBrowser = await createBrowser(settings, 'wellfound');
const loggedIn = await wfLogin(wfBrowser.page);
if (!loggedIn) console.warn(' ⚠️ Wellfound login unconfirmed, proceeding');
else console.log(' ✅ Logged in');
for (const search of wfSearches) {
const effectiveSearch = lookbackDays
? { ...search, filters: { ...search.filters, posted_within_days: lookbackDays } }
: search;
const jobs = await searchWellfound(wfBrowser.page, effectiveSearch);
const added = addJobs(jobs);
totalAdded += added;
totalSeen += jobs.length;
console.log(` [${search.name}] ${jobs.length} found, ${added} new`);
}
platformsRun.push('Wellfound');
} catch (e) {
console.error(` ❌ Wellfound error: ${e.message}`);
} finally {
await wfBrowser?.browser?.close().catch(() => {});
}
}
// Summary
const summary = formatSearchSummary(totalAdded, totalSeen - totalAdded, platformsRun);
console.log(`\n${summary.replace(/\*/g, '')}`);
if (totalAdded > 0) await sendTelegram(settings, summary);
console.log('\n✅ Search complete');
return { added: totalAdded, seen: totalSeen };
}
main().catch(e => {
console.error('Fatal:', e.message);
process.exit(1);
});