Feature guide
JobPilot is six pipelines orchestrated together, plus four side-workflows. This page walks through what each one does, when to use it, and how to tweak it.
Multi-portal scanners
Two scanners discover jobs from completely different sources, both writing to the same deduplicated store.
Scanner A — ATS (scan-ats)
- Hits the public JSON endpoints of Greenhouse, Ashby, Lever, Workable
- No authentication, no scraping, no rate limits
- ~10 seconds for 30+ companies (1,500+ jobs)
- Add new companies by appending slugs to
config/portals.yml
Scanner B — India (scan-india) ★
- Scrapes Naukri + Instahyre using your real Chrome profile
- Cookies are copied to a temp directory at run time — your actual Chrome is untouched
- Instahyre requires you to be logged in (Naukri doesn't)
- Extracts posted date — filters out stale postings
Important: Chrome must be fully quit (not just window-closed) before running. Use osascript -e 'quit app "Google Chrome"'.
./jobpilot scan runs both in parallel.
Filter + Score
Two stages of scoring, blended into a final 1.0–5.0 number.
Filter (drops obvious mismatches)
- Title contains intern/trainee/principal/staff/director
- Stack contains .NET/C#/SAP (configurable)
- Service company (Accenture/Wipro/TCS etc.)
- YoE in URL exceeds your
max_yoe
Typical: 1,578 raw → 282 after filter.
Heuristic scoring
Rule-based formula on:
- Company tier (tier-1 / known product / service / recruiter)
- YoE fit (URL-extracted range vs your years)
- Title keyword match (your stack vs JD title)
- Posted recency
- Location preference
Semantic scoring (sentence-transformers)
- Loads
all-MiniLM-L6-v2(80MB, one-time download) - Embeds your resume markdown + each job's title+JD
- Cosine similarity → mapped to 1.0–5.0
- ~5ms per job on CPU
Final = heuristic × heuristic_weight + semantic × semantic_weight. Default 50/50, tunable in profile.yml.
Review CSV — the human gate
The output is a CSV at outputs/<date>/review-batch.csv with these columns:
| Column | What |
|---|---|
| Score | Combined 1.0–5.0 |
| Heuristic / Semantic | Individual sub-scores (debug) |
| Suggested_Action | cold_email + apply / portal_apply / consider / skip |
| Source | greenhouse / ashby / lever / naukri / instahyre |
| Company, Title, Location, Posted, YoE | Job metadata |
| Notes | Why this score |
| Contact_Name / Contact_Email | You fill (or Apollo auto-fills) |
| URL | Job posting URL |
| Status | NEW → OPENED_FOR_APPLY → REPLY_RECEIVED → INTERVIEW_INVITE → REJECTED |
| Approval | PENDING → APPROVED / SKIP |
Open in Excel/Sheets, change the Approval column, save. Every downstream command reads this file and acts only on APPROVED rows.
Portal Apply
Level 1 — Open URLs (./jobpilot apply)
Opens each APPROVED URL in your default browser, 1.5s apart. You click "Apply" on each.
Level 2 — ATS autofill (./jobpilot auto-apply)
Launches a visible Chromium and fills the standard fields (name, email, phone, resume) on Greenhouse, Ashby, and Lever forms. Pauses at submit so you can review custom questions and click Submit yourself.
For other portals (Naukri, Workday, custom company pages) it just opens the URL.
Cold Email Agent
A separate workflow from the main pipeline. Two modes:
Bulk from CSV (./jobpilot draft-emails)
Reads APPROVED rows with Contact_Email filled in. For each row, generates a tailored draft and saves it to outputs/<date>/drafts/. Nothing is sent.
One-off (./jobpilot draft-one)
For when you spot a job on LinkedIn or someone DMs you about an opening that isn't in the CSV:
./jobpilot draft-one \ --company "Razorpay" \ --name "Priya Sharma" \ --email priya@razorpay.com \ --role "SDE-2 Full Stack"
Sample-first send (./jobpilot send-emails)
Shows each draft, asks y/n/q:
- y → sends via Gmail, attaches resume, moves draft to
drafts/sent/ - n → skip
- q → quit
Project-Aware Pitches ★ NEW
Drop your projects in projects/<name>.md with YAML frontmatter:
--- name: JobPilot demo_url: https://yourdomain.com repo_url: https://github.com/you/jobpilot tech: Python, Playwright, sentence-transformers tags: [automation, ai, full-stack] --- End-to-end AI job-search agent. Scans 30+ portals, scores roles with ML embeddings, tracks every application.
The cold-email agent picks the best-matching project for each JD (by tag/tech overlap) and writes a pitch into the email body. You can override per-email via --project <name>.
Apollo Contact Finder (./jobpilot find-contacts)
For each APPROVED row missing a Contact_Email, hits Apollo API and looks up Engineering Managers, Tech Leads, Recruiters at that company. Auto-fills name + email. Free tier: 50 lookups/month.
Works without Apollo too — you fill Contact_Email manually in the CSV.
Inbox Auto-Tracker (./jobpilot inbox)
Scans your Gmail (last 14 days) for emails matching:
- "thank you for applying"
- "application has been received"
- "phone screen", "interview", "next steps"
Matches to companies in your review CSV → updates the Status column with one of: APPLICATION_CONFIRMED, REPLY_RECEIVED, MOVING_FORWARD, INTERVIEW_INVITE, REJECTED.
Live Status Dashboard (./jobpilot status)
Prints a snapshot of all pipelines for today:
🟢 RUNNING Scanner A — ATS
Started: 2026-06-28T10:54:47
Jobs: 128
Progress: ashby: deepgram
✓ COMPLETED Scanner B — India
Started: 2026-06-28T10:54:47
Ended: 2026-06-28T10:57:21
Jobs: 289
Pass --date 2026-06-27 to inspect a previous day.
Date-organized outputs
Everything writes into outputs/YYYY-MM-DD/:
outputs/ ├── 2026-06-27/ ├── 2026-06-28/ ← today │ ├── jobs-raw.jsonl │ ├── review-batch.csv │ ├── pipeline-status.json │ ├── inbox-matches.csv │ ├── drafts/ │ ├── reports/ │ └── logs/ └── 2026-06-29/ ← tomorrow auto-creates
Old runs are preserved by default. Delete folders you don't need.