Scrapes job portals across six platform families: NeoGov, AppliTrack, FastTrack, HRMplus (public sector, browser-driven) and Workday, Greenhouse (private sector, JSON API). Writes structured JSON + markdown output filtered by salary threshold. Includes a fit-scoring command that rates each job against a local candidate profile using haiku subagents.
Requires Bun 1.0+.
bun install
bun playwright install chromiumbun run scrapeWrites to research/<YYYY-MM-DD>-scrape/scraped/ (the research/ dir is gitignored):
jobs.json— every job, full shapejobs.md— human-readable all-jobs listingfiltered.md— jobs ≥--min-salary+ jobs with unknown salaryerrors.json— per-portal errorssummary.md— per-portal count table
--out-dir <path> default: research/<YYYY-MM-DD>-scrape/scraped
--min-salary <n> default: 100000 (applied by filtered.md)
--include <regex> case-insensitive title filter (keep matching)
--exclude <regex> case-insensitive title filter (drop matching)
--portals <file> path to JSON portal list (default: built-in)
--platforms <list> comma-separated: neogov,applitrack,fasttrack,hrmplus,workday,greenhouse
--concurrency <n> per-platform concurrent scrapes, default: 3
--headful launch browser with UI (debug)
--timeout <ms> per-navigation default: 30000
-h, --help show this help
# Only admin/executive titles, higher threshold
bun run scrape --include "(administrat|coordinator|executive assistant|director)" --min-salary 120000
# Single platform while debugging
bun run scrape --platforms neogov --concurrency 1 --headful
# Custom region via JSON file
bun run scrape --portals ./my-region.jsonCreate a JSON file like:
[
{ "name": "City of Example", "platform": "neogov", "url": "https://www.governmentjobs.com/careers/example" },
{ "name": "Example SD", "platform": "applitrack", "url": "https://www.applitrack.com/exampledistrict/onlineapp" }
]Each entry needs name, platform (one of: neogov, applitrack, fasttrack, hrmplus, workday, greenhouse), and url. Pass via --portals path.json.
Workday portals use https://{tenant}.{region}.myworkdayjobs.com/{site}. Greenhouse portals use https://boards.greenhouse.io/{board_token}. Both adapters filter client-side to WA/Remote locations (Remote is rejected if a non-WA US state is named in the location text).
# Private sector only
bun run scrape --platforms workday,greenhouse --min-salary 135000src/config.js— argv parsersrc/portals.js— built-in portal registrysrc/lib/types.js— JSDoc typedefssrc/lib/browser.js— Playwright wrapper (launchBrowser,withContext)src/lib/salary.js—parseSalary(text) → SalaryRangewith period detection + annualizationsrc/lib/output.js— output formatters +writeResults(outDir, ...)src/platforms/{neogov,applitrack,fasttrack,hrmplus,workday,greenhouse}.js— per-platform adapters; each exportsscrape(browser, portal, config) → { jobs, errors }src/score.js— fit-scoring CLI (prep/aggregatesubcommands)src/score/{prep,aggregate}.js— batching + merging logicsrc/index.js— orchestrator
Drop resume / skills / targets files into profile/ (gitignored; see profile/README.md for suggested structure).
From within a Claude Code session:
/score-jobs research/2026-XX-XX-scrape/scraped/jobs.json --min-salary 135000
The slash command runs bun src/score.js prep, dispatches haiku subagents in parallel batches of 8 (rides your Claude subscription — no metered API cost), then runs bun src/score.js aggregate. Output is scored.json + scored.md sorted by fit score.
The /score-jobs command ships in .claude/commands/ and runs inside a Claude Code session. Without Claude Code: run prep, send each batch-NNN.md prompt to any LLM, save each reply as response-NNN.json in the out dir, then run aggregate.
Underlying CLI (also usable directly):
bun run score prep --jobs <jobs.json> --profile profile --out .scoring
bun run score aggregate --jobs <jobs.json> --batches .scoringbun test # pure-function unit tests (salary, output, score)Adapters have no unit tests — they run against real Playwright / API instances only.
MIT. See LICENSE.