Skip to content

noFAYZ/JobPilot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

JobPilot

A local job-hunting agent. Point it at company career pages and it scrapes every open role, filters them against your resume, fetches the full description for the survivors, scores each one 0–100, and drafts a cover letter for the ones worth applying to.

It runs on your machine, against your own API keys, and stores everything in one SQLite file. Nothing leaves your laptop except the pages it fetches and the prompts it sends to whichever model you point it at.

The board: 128 jobs from ten career pages, scored against a resume and ranked best first

resume.md ──▶ compile ──▶ discover ──▶ filter ──▶ read ──▶ score ──▶ board
                          412 found    50 kept   22 read  18 scored

The whole design is one idea: cheap filters run before expensive ones. A job title costs a fraction of a model call to judge. A description costs a page fetch and a full scoring call. So titles are filtered first — most of them by rules that cost nothing at all — and only the survivors are ever fetched or scored.

Why it is built this way — the trade-offs, the failure modes, the things deliberately left out — is explained inline. The code is commented for a reader, not for a linter.


Requirements

  • Node 23.6+ — it runs TypeScript directly and uses the built-in node:sqlite
  • An LLM connection, any of:
    • Claude CLI (uses your existing sign-in, no key needed)
    • Google AI Studio — the free tier is enough to run this
    • Anthropic API, OpenRouter, Ollama, LM Studio, or anything OpenAI-compatible
  • A Firecrawl key is optional. Discovery is offline by default; Firecrawl only runs as a last resort on boards where every free strategy found nothing, and can be disabled entirely.

Start

npm install
npm run build      # builds the UI into ui/dist
npm run server     # http://localhost:3000

Setup opens on first run and asks for the four things a run cannot start without: your resume, the roles you want, the pages to watch, and a model to read with. Two minutes. You can reopen it any time from Settings → Data → Run setup again.

Optional: browser rendering

Some career pages are JavaScript-only — no links in the HTML until the app has run. Discovery handles most of them without a browser (platform APIs, sitemaps, embedded JSON), but for the rest:

npx playwright install chromium

Then enable it in Pipeline → Discover → Browser rendering. It is not downloaded automatically, because most boards never need it.

Configuration

Everything is configured in the app and stored in jobpilot.db. .env is only for what is needed before the app is up — copy .env.example:

Variable Purpose
PORT Default 3000
HOST Default 127.0.0.1. Read the security note before changing this
FIRECRAWL_API_KEY Optional fallback scraper
LLM_API_KEY Optional; the UI stores a key per connection instead
VITE_LOGO_TOKEN Optional logo.dev key for company logos. Without it you get letter avatars and nothing is sent to a third party

Security

There is no authentication. The server binds to 127.0.0.1 for exactly that reason: anyone who can reach the port can read your job data, change settings, and start runs that spend your API credits.

HOST=0.0.0.0 is supported and prints a warning on boot. Don't use it without an authenticating proxy in front.

Job descriptions are attacker-controlled text and go into three prompts. They are delimited and marked as data, and the Claude CLI is spawned with no tools, so there is no filesystem for a malicious posting to reach. If you ever add tools to that call, that mitigation is gone.

How discovery works

A plugin engine, not a scraper. Every strategy runs in parallel and returns job references with a confidence; results are merged, keeping the richest version of each job.

Strategy Confidence
Platform adapter — Workday, Greenhouse, Lever, Ashby, Breezy, BambooHR, Freshteam, Zoho Recruit, Hirestream, WebHR 100
Network observer — JSON the page fetched while rendering (browser) 100
Learned API — an endpoint a previous crawl recorded 96
JSON-LD schema.org/JobPosting 95
Embedded JSON — __NEXT_DATA__, Nuxt, Remix 88
Sitemap 75
Infinite scroll (browser) / pagination 70 / 65
Link scan — scores every anchor on path, text, and patterns learned from this host 60
Firecrawl — second pass, only if everything above found nothing 80

Each crawl writes a fingerprint to .cache/discovery/<host>.json: which strategies worked, the job URL pattern, any API endpoints. The next crawl reuses it. Measured on a real site: first crawl 9.4s, second 1.2s.

Discovery is a standalone package importing nothing from the rest of the app, with its own CLI:

node src/discovery/cli.ts https://company.com/careers --no-browser

Adding an ATS adapter

The highest-value contribution: one adapter covers every company on that platform, and most are about 20 lines — a list endpoint, a URL shape, and the fields to map. See src/sources/careers.ts; scrapeGreenhouse is a good template.

Still missing: SmartRecruiters, Personio, Recruitee, JazzHR, Workable, iCIMS.

Layout

src/
  server.ts       HTTP API, SSE progress, static UI
  pipeline.ts     the five stages, in order
  candidate/      compiled resume, title rules, the measured half of a score
  model/          LLM client, connections, rate limits, prompts, output schemas
  sources/        career boards, extraction, the Firecrawl bridge
  discovery/      the standalone discovery engine
  data/           SQLite schema, migrations, every query
ui/               React, Vite, Tailwind
test/             node:test, no framework
npm test          # 111 tests, no network
npm run dev       # Vite dev server with an API proxy
npm start         # one pipeline run, headless, straight to the terminal

Cost

A run on a free model tier costs nothing. In practice:

  • Discovery is free unless a board defeats every offline strategy
  • Titles are filtered by rules first; only ambiguous ones reach a model
  • Descriptions are fetched free wherever a platform API, JSON-LD or a readable page exists
  • Scoring is one call per job, against a ~400-token compiled profile rather than your resume
  • Boards read in the last 12 hours are skipped entirely

Every dial is exposed on the Pipeline page, and the run overlay shows what was actually spent.

Licence

MIT — see LICENSE.

About

JobPilot | A local job-hunting agent. Point it at company career pages and it scrapes every open role, filters them against your resume, fetches the full description for the survivors, scores each one 0–100, and drafts a cover letter for the ones worth applying to.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages