A personal, human-in-the-loop job application agent for Greenhouse, Lever and Ashby. It finds jobs, filters and scores them against your profile, tailors a truthful one-page resume per job, prepares answers to each application form, and fills the real form in a browser. You review every application; V1 has you click Submit, V2 (opt-in) submits only forms that pass every check. Design notes: BUILD_GUIDE.md.
Status: complete. 1–7 + week 2 of the build plan are implemented and tested (170 offline tests; live checks on real Greenhouse, Lever and Ashby forms, never submitted). What has not run yet is anything needing your API key: profile extraction, scoring, tailoring and LLM answer drafts are tested with fake models only. See "First run" below.
Python 3.12 with a virtual environment in this folder. pip's cache, temp files, Playwright's
Chromium and pytest's temp files all live in .cache/; C: is not used.
python -m venv .venv
$env:PIP_CACHE_DIR="E:\Job_Hunterrr\.cache\pip"; $env:TMP="E:\Job_Hunterrr\.cache\tmp"; $env:TEMP=$env:TMP
.venv\Scripts\python -m pip install -e ".[dev]"
$env:PLAYWRIGHT_BROWSERS_PATH="E:\Job_Hunterrr\.cache\ms-playwright"; .venv\Scripts\python -m playwright install chromium
copy .env.example .env # then put your ANTHROPIC_API_KEY in .env
copy config\prefs.example.yaml config\prefs.yaml # then fill in us_citizen / needs_sponsorship
copy config\answers.example.yaml config\answers.yaml # then fill in your answers(All of this is already done in this folder.) Activate with .venv\Scripts\Activate.ps1 to use
the short autoapply command; otherwise .venv\Scripts\python -m autoapply ....
- API key: put
ANTHROPIC_API_KEY=...in.env. - Profile:
autoapply profile extract path\to\resume.pdf, read the warnings, then editdata/profile.jsonby hand (add projects/bullets your resume cut for space; more raw material = better tailoring).autoapply render --baseshows it in the resume template, for free. - Preferences: in
config/prefs.yamlsetus_citizenandneeds_sponsorship(required; nothing runs until you do), and adjust titles / cities. - Answer bank:
autoapply banklists the blank entries inconfig/answers.yaml(work authorization x3, citizenship, age, pronouns, relocation, in-office, salary, start date...). Each blank becomes a "NEEDS YOU" item on every form until filled. - Dry run one job end to end:
autoapply filter,autoapply score --limit 10,autoapply tailor --top 1,autoapply prepare --top 1,autoapply review, thenautoapply apply --dry-run <id>and look atdata/runs/<id>/filled.png.
autoapply daily # discover -> filter -> score -> tailor -> prepare (never applies; ~$1 cap)
autoapply review # approve / edit / reject in the browser (http://localhost:8501)
autoapply apply # fills each approved form; you check it and click Submit
autoapply outcome 1234 interview --note "phone screen Tue" # when you hear back
autoapply stats # funnel + response rate by ATS and score bucketscripts/daily.ps1 runs autoapply daily and appends to data/logs/daily.log. To schedule it
at 8:00 each morning (run this yourself once; it creates a Windows scheduled task):
schtasks /Create /SC DAILY /ST 08:00 /TN "AutoApply daily" /TR "powershell -NoProfile -ExecutionPolicy Bypass -File E:\Job_Hunterrr\scripts\daily.ps1"Remove it with schtasks /Delete /TN "AutoApply daily" /F.
| Command | What it does |
|---|---|
profile extract <pdf> [--force] / profile show |
resume -> data/profile.json (backup kept on overwrite) |
seeds import [--simplify newgrad|intern|both|none] |
boards from config/companies.yaml + SimplifyJobs lists |
seeds validate [--ats all|greenhouse|lever|ashby] / seeds list |
dead boards get deactivated |
discover [--ats ...] [-v] |
fetch all active boards (~80 s for 24k jobs) |
filter [--refilter] |
free rules from prefs.yaml; prints what each rule dropped |
score [--limit N] [--batch] [--dry-run] [-y] / collect |
Haiku fit score; batch = half price |
jobs [words] [--state S] [--where CITY] [--sort score] / show <id> [--desc] |
browse |
tailor [ids] [--top N] [--dry-run] [-y] |
tailored, validated one-page PDF per job |
render <id> / render --base |
re-render after editing data/resumes/<id>/tailored.json |
bank [--test "question"] |
blank answer-bank entries / which entry a question hits |
prepare [ids] [--top N] [--no-llm] |
fetch each form, propose answers -> review queue |
review |
Streamlit review queue |
apply [ids] [--dry-run] [--headless] [--auto-submit] [--wait MIN] |
fill approved forms |
outcome <id> interview|rejected|offer|ghosted|withdrawn |
record results |
daily / stats |
scheduled pipeline / numbers |
- Resume: Sonnet only selects, orders and rephrases your bullets; every bullet cites its
source id.
tailor/validate.pyreverts any new number, any tech term not in that bullet (config/tech_terms.txt), invented skills, dropped jobs and overlong rewrites. Hand edits get the same checks. - Answers come from (in order) your profile, answers you saved,
config/answers.yaml, computation (years of experience from your dates), or an LLM draft checked the same way. Never by the LLM: work authorization / visa / citizenship, legal and background questions, agreements and attestations (including AI-usage attestations, which several companies require), salary, demographics, relocation / in-office preferences, personal details not in your profile (address, zip...), and anti-bot questions ("What is seven plus four minus three?"). - Forms: uploads go first and every text field is re-checked afterwards, because Lever-style resume parsing can overwrite fields asynchronously. Every field is verified after filling; the report lists anything that failed, required follow-ups that appeared, CAPTCHAs, and emailed security codes. Ashby's page-level "Autofill from resume" is never used.
- Submitting: V1 waits for you. V2 (
auto_submit: truein prefs and--auto-submit) only clicks Submit on approved jobs whose report is perfectly clean (resume confirmed, nothing failed, nothing required left empty, no CAPTCHA); otherwise it hands over to you. A random 30–120 s gap between automatic submissions, andmax_applications_per_dayis a hard stop. - State: a job that might have been submitted (crash, window closed after Submit, no
confirmation) becomes
NEEDS_HUMANand is never retried automatically. Same company + same role is never applied to twice. - Never: CAPTCHA solving, account creation, LinkedIn automation, or Workday/iCIMS/Taleo.
Pipeline: DISCOVERED -> CANDIDATE -> (SCORING) -> SCORED | LOW_FIT -> TAILORED -> NEEDS_REVIEW -> APPROVED | REJECTED -> FILLING -> (SUBMITTING) -> SUBMITTED | NEEDS_HUMAN; every change is in
the events table, every application's exact answers in applications.answers_json, and each
attempt's screenshots + fill report in data/runs/<id>/.
Scoring ~$0.0006 (Haiku 5.5; half with --batch), tailoring ~$0.05–0.07 (Sonnet 5.5, one retry
in three), form drafts ~$0.02–0.05. daily stays under daily_llm_budget_usd (default $1).
- Greenhouse sometimes emails a security code on submit, and CAPTCHAs can appear: you type/solve
those in the window (the browser profile in
browser_profile/keeps you signed in to nothing, but remembers cookies, so challenges get rarer). - EU Greenhouse boards (
job-boards.eu.greenhouse.io) and Lever's optional diversity surveys are skipped. Lever/Ashby company lists come from the SimplifyJobs lists pluscompanies.yaml. - Confirmation-page detection is pattern based; if a site's thank-you page isn't recognized,
press
min the terminal to record the submission. - Ashby runs session replay (Datadog) on its forms, so typing is observable to the company before you submit; that's normal for modern sites.
config/ prefs.yaml answers.yaml answers.saved.yaml companies.yaml tech_terms.txt
data/ profile.json autoapply.db resumes/<id>/ runs/<id>/ logs/ (git-ignored)
scripts/daily.ps1
src/autoapply/
cli.py config.py db.py models.py llm.py http.py prefs.py filter.py score.py daily.py stats.py
profile/extract.py
discover/ greenhouse.py lever.py ashby.py seeds.py run.py
tailor/ tailor.py validate.py render.py pipeline.py templates/resume.html
answers/ forms.py bank.py engine.py prepare.py
review/ logic.py app.py
fill/ common.py greenhouse.py lever.py ashby.py session.py
tests/ offline tests + fixtures/ (mock Greenhouse, Lever, Ashby forms with the live sites' traps)
python -m pytest runs everything offline (~2 minutes; real headless Chromium against the mocks).