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)

Scanner B — India (scan-india) ★

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)

Typical: 1,578 raw → 282 after filter.

Heuristic scoring

Rule-based formula on:

Semantic scoring (sentence-transformers)

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:

ColumnWhat
ScoreCombined 1.0–5.0
Heuristic / SemanticIndividual sub-scores (debug)
Suggested_Actioncold_email + apply / portal_apply / consider / skip
Sourcegreenhouse / ashby / lever / naukri / instahyre
Company, Title, Location, Posted, YoEJob metadata
NotesWhy this score
Contact_Name / Contact_EmailYou fill (or Apollo auto-fills)
URLJob posting URL
StatusNEW → OPENED_FOR_APPLY → REPLY_RECEIVED → INTERVIEW_INVITE → REJECTED
ApprovalPENDING → 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:

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:

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.