Automated job discovery, enrichment, and fit scoring — with a web dashboard for triage.
The best job opportunities tend to surface when you're already employed and not actively looking — and that's exactly when you're least likely to catch them. I built this tool to keep a continuous eye on the market in the background, so that when a great match appears you get notified instead of missing it entirely.
The project is still mostly manual in its current form, but the long-term plan is to ship a fully containerized version that can run on Kubernetes or any other server — set it up once, let it run. There are plenty of features and rough edges still to tackle, but the core pipeline works. Have fun with it!
Warning
Early-stage / hobby project. Expect rough edges, breaking changes, and no support SLA. Use at your own risk.
- How it works
- Getting Started
- Running the Application
- Optimization Tips
- Adding Companies
- LLM Provider Options
- Configuration Reference
- Contributing
Three pipeline stages run against your list of tracked companies:
- Scout — scans career pages and ATS APIs for new postings matching your title filters
- Enrich — fetches each listing and extracts structured metadata (location, remote policy, salary, tech stack) via LLM
- Evaluate — scores each job 0–10 against your candidate profile and routes it to match / review / rejected
Everything is stored locally and surfaced in a web dashboard for triage.
Requirements: Python 3.9+, uv, any LLM API key — Google Gemini, Anthropic, or anything supported by LiteLLM (Groq, Bedrock, Azure, …) — or Ollama for fully local use. Plus either direnv or source .envrc in each terminal session.
- Clone the repo and open it in Claude Code
- Run
/app:setup
The setup wizard walks you through installing dependencies, configuring your LLM key, generating a profile from your CV, and adding companies to track. It detects what is already configured and offers to skip or redo individual steps.
- Clone the repo and open it in Gemini CLI
- Ask: "Set up the application tracker for me"
Gemini will invoke the same setup wizard via its built-in skill.
# 1. Clone and install
git clone https://github.com/jwndlng/ai-app-radar
cd ai-app-radar
uv sync
# 2. Install Playwright (for scraping career pages, ~150 MB)
uv run playwright install chromium
# 3. Configure your LLM key
cp .envrc.example .envrc
# Edit .envrc — uncomment and fill in your key (see LLM Provider Options below)
direnv allow # or: source .envrc
# 4. Set up your profile
cp configs/profile.example.yaml configs/profile.yaml
# Edit configs/profile.yaml — add your target roles, skills, location, compensation
# 5. Start the dashboard
make run
# → http://localhost:8000make run # Start the web dashboard → http://localhost:8000The dashboard is the recommended way to use the app — Scout, Enrich, and Evaluate are all triggerable with a button, results update in real time, and running tasks can be cancelled at any time via the × button on the task card. The layout is responsive and works on mobile.
CLI usage (advanced — no UI required)
make scout # Discover new roles from all tracked companies
make enrich # Extract structured metadata for discovered jobs
make evaluate # Score jobs against your profile
make sync # Run all three stages in sequence
make scout-one COMPANY="Stripe" # Scout a single company
make enrich-all # Enrich all queued jobs (no limit)
make fix-errors # Retry jobs that errored in the last run
make help # Show all available targetsShow optimization tips
[!TIP] Limit
agent_reviewcompanies Eachagent_reviewentry triggers a full Playwright browser scrape plus an LLM parse on every pipeline run. Costs and run times scale linearly — keep it to 5 or feweragent_reviewcompanies. Use API-based providers (greenhouse_api,ashby_api,lever_api, etc.) wherever possible; they are fast, free, and don't consume LLM budget.
[!TIP] Keep scout filters specific Broad
title_includepatterns (e.g."engineer") pull in large volumes of irrelevant jobs. Every job that passes the scout filter gets sent to enrich and evaluate — both of which make LLM calls. Use precise title substrings (e.g."Staff Software Engineer","Senior Backend Engineer") to keep the job set small and the results relevant.
[!TIP] Use a free-tier model for enrich and evaluate The enrich and evaluate stages run on every job in the queue. A fast free-tier model such as
gemini/gemini-2.5-flash(~1,500 req/day free) is more than sufficient for structured extraction and fit scoring. Reserve stronger or paid models foragent_reviewscraping or interactive use.
[!TIP] Tune evaluate thresholds after the first real run The default thresholds (
auto_reject: 4.0,auto_match: 8.0) are conservative by design. Run the pipeline once at defaults and review the results before adjusting — loweringauto_rejectfloods the review queue; raisingauto_matchhides borderline matches. Calibrate against real output, not guesses.
With Claude Code or Gemini CLI (recommended — auto-detects ATS, verifies the endpoint, and writes a validated entry):
/app:onboard-company "Company Name"
/app:onboard-company "Company Name" https://jobs.example.com
Manually — add an entry to configs/companies.json. The repo ships with a curated community list. Supported ATS providers:
scan_method |
ATS | Config needed |
|---|---|---|
greenhouse_api |
Greenhouse | api_base URL |
ashby_api |
Ashby | slug |
lever_api |
Lever | slug |
workable_api |
Workable | slug |
workday_api |
Workday | careers URL only |
agent_review |
Any (web scrape + LLM) | careers URL only (see Optimization Tips) |
All pipeline stages use the model set in ADK_MODEL. Every call goes through LiteLLM, so any LiteLLM-supported provider works — just change the env vars in .envrc.
Free options:
| Option | ADK_MODEL |
Key | Notes |
|---|---|---|---|
| Google Gemini free tier | gemini/gemini-2.5-flash |
GEMINI_API_KEY |
~1,500 req/day free; best default |
| Groq free tier | groq/llama-3.3-70b-versatile |
GROQ_API_KEY |
Rate-limited, no billing required |
| Ollama (local) | ollama/gemma3:27b |
none | Fully offline; needs ~16 GB RAM |
See .envrc.example for all options including Anthropic and LiteLLM proxy.
Defines who you are and what you're looking for. The pipeline scores every job against this file. Copy configs/profile.example.yaml as a starting point.
Key sections:
| Section | Purpose |
|---|---|
targets.primary_roles |
Job titles to target — used in scout title filters |
skill_tiers |
super_power / strong / low — used to assess tech stack fit |
mission_domains |
Domain interest weights (e.g. cloud_infrastructure: 10) |
location_preferences.accepted |
Accepted locations — used for the location hard-gate |
compensation |
minimum and target_range — used in fit scoring |
scout_filters |
Keyword allow/block lists for title filtering |
Pipeline thresholds and notification settings:
evaluate:
auto_reject: 4.0 # score below this → archived
auto_match: 8.0 # score above this → match
location_reject_threshold: 2.0 # location score below this → hard rejected
notifications:
telegram:
bot_token: null # or set TELEGRAM_BOT_TOKEN env var
chat_id: null # or set TELEGRAM_CHAT_ID env var
notify_match: true # alert per matched job
notify_review: true # alert per review job
notify_scout_summary: true # summary after scout completes
notify_enrich_summary: true # summary after enrich completes
notify_evaluate_summary: true # summary after evaluate completesTelegram setup: create a bot via @BotFather, get your chat_id from @userinfobot, then add both to .envrc or settings.yaml. Credentials in settings.yaml take precedence over environment variables. Send /start to your bot at least once before the first run.
Read-only configs (Kubernetes / NAS): the Settings page saves to configs/settings.yaml. If configs/ is mounted read-only (e.g. from a ConfigMap) or isn't writable by the container user, set RADAR_SETTINGS_PATH to a writable file, ideally on the persistent artifacts volume:
RADAR_SETTINGS_PATH=/app/artifacts/settings.yamlUntil that file exists, settings are read from the bundled configs/settings.yaml, and the first save copies its values (including notifications) into the new file. From then on, the new file is the only source. Relative paths are resolved against the project root. The Settings page shows a warning when the active settings file can't be written.
.envrc.example # Environment variable template (copy to .envrc)
configs/
profile.yaml # Your candidate profile (gitignored)
profile.example.yaml # Example profile for testing
companies.json # Community-maintained company list
settings.yaml # Pipeline thresholds and model config
src/ # Pipeline source code
static/index.html # Web dashboard (Alpine.js)
artifacts/ # Runtime state — job registry and logs (gitignored)
Issues and PRs are welcome. This is a hobby project — response time may be slow. Good candidates: new ATS providers, bug fixes, documentation improvements.
MIT — see LICENSE.