A Claude Code-powered job search system that runs automated daily searches across multiple job APIs, emails you results twice a day, and provides a full web dashboard for tracking applications and generating tailored resumes and cover letters.
Built and maintained using Claude Code. Released under the MIT License.
Kanban Board — drag jobs through columns as you work them

Job Radar — tier-coded cards (green = Apply Now · blue = Worth a Look · gray = Skip)

Job Detail — reviewing checklist, application URL field, and document generation

Automated Job Radar
- Queries Adzuna, Brave Search, Tavily, LinkedIn, Remotive, WeWorkRemotely, Himalayas, RemoteOK, Jobicy, JSearch, Greenhouse, Lever, and Ashby ATS twice a day (9am and 4pm via cron)
- LinkedIn scraping uses full filter params (full-time, newest first, remote+hybrid), fetches up to 2 pages per query with randomized delays, and retrieves full job descriptions for every job that passes pre-filtering — so Claude rates on real content, not just title
- Deduplicates results across runs using multi-key dedup (company+title and URL) so you only see new postings
- Filters out wrong titles, non-US locations, onsite/hybrid roles outside your area, staffing agencies, closed listings, broken URLs, salary below floor, and aggregate/category pages
- Broad title targeting: PO, PM, BA, systems/functional/solutions/integration analyst, scrum master, agile delivery, platform/service/feature owner, pre-sales engineer, customer success (technical/enterprise), and more
- Rates each job with Claude Haiku —
Apply Now / Worth a Look / Weak Match / Skip— with a one-sentence reason - Within each tier, companies local to your configured metro area sort to the top — even for remote roles, proximity to the company is an advantage
- Saves a dated markdown report to
output/job-radar/with each tier in its own section - Emails the full report in the email body and attaches the
.mdfile
Web Dashboard (scripts/dashboard.py)
- Local Flask app — neon dark UI, run it on your always-on machine, open at
http://localhost:5000 - Kanban board — drag jobs through: New → Reviewing → Drafting → Ready → Applied → Phone Screen → Interview → Offer → Accepted / Rejected / Passed
- Status is drag-only — no dropdowns to accidentally click
- Reviewing cards show a checklist badge (☑) as a reminder to work through the checklist before moving on
- Radar view — browse reports in-browser, save jobs to the board with one click
- Job cards have a color-coded right-border stripe: green = Apply Now, blue = Worth a Look, yellow = Weak Match, gray = Skip
- Posting button is electric blue to distinguish it from the green Save button
- Comment box on every card to note why a job isn't a fit or flag search tweaks
- Dismiss jobs you've reviewed — stays hidden on future visits, toggle to show dismissed
- Mark entire reports as reviewed — ✓ appears in the report dropdown
- Portal Scanner — on-demand scanning of 230+ company career pages across Greenhouse, Lever, and Ashby ATS platforms
- Filter by source (Greenhouse / Lever / Ashby) and location (Remote / Local / Other US)
- Filters out non-US jobs and on-site roles outside your metro area
- Save results directly to the kanban board
- Fit Analysis — AI-powered evaluation of how well your experience matches a job posting
- Match score (1-10, color-coded), experience matches mapped to specific JD requirements
- Gap analysis with mitigation strategies for missing requirements
- Surfaces 3-5 relevant STAR interview stories from your work history
- Auto-moves job from New → Reviewing when analysis runs
- Interview Story Bank — persistent view of all STAR stories from your job docs
- Cross-references fit analyses to show which jobs each story was surfaced for
- Ready reference for behavioral interview prep
- Job detail — full description, timestamped notes, application URL field
- Reviewing checklist: 7-step checklist (read JD, verify remote, check salary, research company, find hiring manager, paste apply URL, drag to Drafting) appears when card is in Reviewing
- Paste and save a direct application URL — shows as a separate Apply Now button at the top
- Document generation — generates tailored resume + cover letter via Claude (uses your Pro subscription via
claude -p, no API cost)- ATS keyword optimization — extracts 15-20 key terms from the JD and reformulates existing experience to match the posting's vocabulary without inventing experience
- Two-panel editor — edit resume and cover letter side by side, regenerate with custom instructions, version history, mark final
- DOCX export — download resume or cover letter as a Word file
- Source Health — tracks per-source job counts, errors, and latency for every radar run
- Trend arrows show whether each source returned more or fewer jobs than the previous run
- Filter stats show how many jobs each filter caught — helps tune search queries
- 14-day run history at a glance
- Filter audit trail — every filtered job is logged with the reason it was dropped
- Collapsible "Filtered" section on each radar report page shows what was removed and why
- Grouped by filter (wrong title, non-US, staffing, etc.) so you can spot patterns
- Company memory — tracks dismiss/reject/boost signals per company across runs
- Dismissing radar jobs or rejecting board jobs automatically records a signal for that company
- Boost button on radar cards for companies you want to prioritize
- Signal badges show on radar cards (e.g. "3x dismissed", "rejected before", "boosted")
- Application completeness gate — prevents moving jobs to Ready without requirements met
- Requires a final document (resume/cover letter marked final) and an apply URL
- Server-side enforcement with clear error messages
- Stale job alerts — highlights jobs stuck in a column too long
- Per-status thresholds (New=3d, Reviewing=5d, Drafting=7d, Ready=3d, Applied=14d, Interviewing=7d)
- Orange "stale" badge and border on board cards
- SQLite database — all jobs, notes, documents, comments, dismissed state, and application URLs persist locally
Telegram Bot (scripts/telegram_bot.py, optional)
/radar— trigger a job search from your phone/latest— pull up the most recent report/status— last run summary- Chat with any supported AI model using your full career profile as context
- Supports Claude, GPT-4o, Gemini, Kimi K2, DeepSeek, and any OpenRouter or Nvidia NIM model
job-search-profile/
├── CLAUDE.md # Claude Code instructions
├── .env # create locally — your API keys (gitignored)
├── config.py # create locally — copy from config.example.py (gitignored)
├── config.example.py # template — copy this to config.py to get started
├── docs/ # create locally — your personal profile docs (gitignored)
│ ├── personal-info.md # contact, headline, summary
│ ├── technical-skills.md # master skills list
│ ├── education.md # degrees and formatting
│ ├── resume-generation-rules.md # rules Claude follows for resume/cover letter work
│ ├── YYYY-YYYY-company-title.md # one file per role
│ └── templates/ # starter templates for each doc type
├── input/
│ ├── job-postings/ # drop JDs here before generating a resume
│ ├── old-resumes/ # source material for backfilling docs
│ └── raw-notes/ # informal role notes to clean up
├── output/
│ ├── documents/ # DOCX resume/cover letter exports
│ └── job-radar/ # daily search reports + dedup state
├── dashboard/
│ ├── templates/ # Jinja2 HTML templates
│ │ ├── base.html
│ │ ├── board.html
│ │ ├── radar.html
│ │ ├── portals.html # portal scanner results
│ │ ├── health.html # source health + filter stats
│ │ ├── stories.html # interview story bank
│ │ ├── job_detail.html
│ │ ├── draft.html
│ │ └── _card.html
│ └── static/
│ ├── style.css # dark mode UI
│ └── app.js
├── tests/
│ ├── conftest.py # shared fixtures, mock config
│ ├── test_job_radar.py # filter, dedup, dataclass tests
│ ├── test_source_parsers.py # fixture-based API response parsing
│ ├── test_dashboard.py # Flask route smoke tests
│ ├── test_report_parser.py # radar report markdown parsing
│ ├── test_startup.py # config validation tests
│ ├── test_log.py # logging helper tests
│ ├── test_telegram_bot.py # bot token redaction tests
│ └── fixtures/ # JSON/HTML/XML API response fixtures
└── scripts/
├── job_radar.py # orchestrator — main(), seen file, dedup
├── models.py # Job dataclass
├── filters.py # 12 filter functions + regex constants
├── rating.py # Claude AI job rating + salary extraction
├── report.py # report building, debug log, email
├── normalize.py # shared cleanup: HTML strip, salary format
├── sources/ # one module per source or source group
│ ├── adzuna.py # Adzuna REST API
│ ├── brave.py # Brave web search
│ ├── tavily.py # Tavily web search + company extraction
│ ├── linkedin.py # LinkedIn HTML scraping + enrichment
│ ├── remote_boards.py # Remotive, WWR, Himalayas, RemoteOK, Jobicy
│ ├── jsearch.py # JSearch / RapidAPI
│ └── ats.py # Greenhouse, Lever, Ashby direct APIs
├── dashboard.py # Flask dashboard — kanban, radar, doc gen
├── db.py # SQLite access layer (named query functions)
├── portal_scanner.py # on-demand ATS career page scanner
├── telegram_bot.py # Telegram bot with multi-model AI chat
├── startup.py # env/config validation at boot
└── log.py # log() helper with [source] prefix
Job radar runs automatically. Check your email at 9am and 4pm.
Reviewing the radar:
- Open the dashboard at
http://localhost:5000and go to Radar - Review each job — save promising ones to the board with + Save, dismiss the rest with ✕
- Add comments to any job to note why it's not a fit (helps tune the search over time)
- Click Mark Reviewed when done with the report
Scanning career portals:
- Go to Portals and click Scan All Portals — checks 230+ companies across Greenhouse, Lever, and Ashby
- Filter results by source and location (Remote / Local / Other US)
- Save interesting jobs to the board with + Save
Working a saved job:
- Go to Board — saved jobs land in the New column
- Click View and run Analyze Fit — get a match score, experience mapping, gap analysis, and relevant interview stories
- Drag the card to Reviewing — a checklist (☑) badge appears as a reminder
- Work through the Reviewing checklist:
- Read the full job description
- Verify remote / location works
- Check salary range (listed or Glassdoor)
- Research the company (size, funding, culture)
- Find the hiring manager on LinkedIn
- Paste the application URL into the field and save
- Drag the card to Drafting, then click View → Generate Resume + Cover Letter
- Edit in the two-panel editor, add regeneration instructions if needed
- Download DOCX, Mark Final — drag card to Ready
- Move through Applied → Phone Screen → Interview → Offer as you progress
Preparing for interviews:
- Check the Stories tab for your STAR story bank — all signature stories from your work history in one place
- Run a fit analysis on the job to see which stories are most relevant
To generate a resume without the dashboard:
- Save the job description to
input/job-postings/company-title.txt - Open Claude Code in the repo and say:
generate resume for input/job-postings/company-title.txt
Prerequisites:
- Claude Code with a Pro or Max subscription
- Python 3.10+
- Free API keys: Adzuna · Tavily
- (Optional) Brave Search API — paid, but adds broader web search coverage
- (Optional) Gmail account with an App Password — for email delivery of reports. Skip if you'll use the dashboard only.
- Anthropic API key — used for Claude Haiku job rating in
job_radar.py - (Optional) Telegram bot token for mobile access
Run the setup script (handles deps, .env, config.py, docs, and cron for you):
python setup.pyOr set things up manually:
Install dependencies:
pip install flask python-docx anthropic openai google-genai requests beautifulsoup4 python-dotenv markdownCreate your .env file:
ANTHROPIC_API_KEY=...
ADZUNA_APP_ID=...
ADZUNA_APP_KEY=...
BRAVE_API_KEY=... # optional (paid API)
TAVILY_API_KEY=...
JSEARCH_API_KEY=... # optional
GMAIL_FROM=you@gmail.com # optional — skip if using dashboard only
GMAIL_TO=you@gmail.com # optional
GMAIL_APP_PW=... # optional
TELEGRAM_BOT_TOKEN=... # optional
TELEGRAM_USER_ID=... # optional
Configure for yourself:
Copy config.example.py to config.py (gitignored) and fill it out:
cp config.example.py config.pyKey settings in config.py:
CANDIDATE_NAME,CANDIDATE_BACKGROUND— your name and a short profile summary used for job ratingHOME_CITY,HOME_STATE,HOME_METRO_TERMS— your location for local job filteringJOB_DOCS— list of yourdocs/job files for the dashboard- Search query lists —
ADZUNA_QUERIES,LI_REMOTE_QUERIES,LI_LOCAL_QUERIES, etc. — customize for your target roles BLOCKED_COMPANIES— add companies as you find ones that keep slipping through filtersDOMAIN_COMPANY_MAP— add domains for better company name extraction from Workday/ATS URLs
Run the dashboard:
cd job-search-profile
python scripts/dashboard.py
# Open http://localhost:5000Schedule the radar (cron):
0 9,16 * * 1-5 cd /path/to/job-search-profile && python scripts/job_radar.py
Profile docs:
Most setup time is writing your docs/ files. Template files showing the expected structure for each doc type are in docs/templates/. Once those are solid, Claude can generate a tailored resume in under 2 minutes.
- Company memory: dismiss/reject/boost signals per company with radar badges
- Application completeness gate: blocks Ready status without final doc + apply URL
- Stale job alerts: orange badges on board cards past per-status time thresholds
- 139 tests passing
- Source health tracking: per-source job counts, errors, latency, and trend arrows on a new Health dashboard tab
- Filter audit trail: every filtered job logged with reason, shown in a collapsible section on each radar report
- Per-run filter stats (how many jobs each filter caught) on Health page
- 14-day run history table
- 128 tests passing
- Refactored
job_radar.pyfrom 1842 lines into focused modules:models.py,filters.py,rating.py,report.py,normalize.py, and 7 source modules undersources/ - Extracted ~70 inline SQL queries from
dashboard.pyintodb.pywith named functions - Deduplicated salary formatting, keyword matching, and HTML cleanup into shared
normalize.py - 125 tests passing
- Added
scripts/log.py—log()helper with[source]prefix and--verboseflag - Added
scripts/startup.py— env/config validation with clear error messages at boot - Added 13 source parser fixtures + 26 tests (125 total)
- Fixed Adzuna env var mismatch, XSS via innerHTML, URL validation, Flask debug mode
- Added Telegram token redaction, lazy provider init, pinned requirements.txt
- Added test suite (81 tests)
- Fit Analysis, Interview Story Bank, Portal Scanner, ATS keyword optimization
Built in collaboration with Casey Chalfant, who contributed to the initial architecture and feature design.