Five tools in one clean, self-hosted dashboard: search remote jobs across multiple boards, track your pipeline, get instant AI salary & company intel, auto-draft your application kit, score your rΓ©sumΓ© against a job, rehearse the interview, and coach your salary negotiation.
| Β | Β |
|---|---|
![]() |
![]() |
| Home β tools + live pipeline stats | Job Search β master-detail, ranked by match |
![]() |
|
| Application Tracker β Saved β Offer pipeline | Resume Analyzer β ATS score & keyword gaps |
![]() |
![]() |
| Interview Prep β questions + STAR answers | Salary Coach β market range & counter-script |
| Tool | What it does | Needs AI? |
|---|---|---|
| π Job Search | Search 5+ free boards at once, ranked by a keyword match score. AI company/salary intel + auto-drafted email, cover letter & CV prompt. | for intel/drafts |
| π Application Tracker | Save jobs and move them through Saved β Applied β Interview β Offer β Rejected, with notes. Persists to local JSON. | no |
| π Resume Analyzer | Paste your CV against a job β ATS match score, matched vs missing keywords, concrete rewrite suggestions, and a stronger tailored summary. | yes |
| π€ Interview Prep | Generate the questions youβll likely face β what each really assesses, plus STAR-format model answers tailored to you. | yes |
| π° Salary Coach | A realistic market range, an assessment of your offer, a ready-to-send counter-offer script, and negotiation tips. | yes |
Remote | Any toggle.mailto:.applications.json.flowchart LR
U([You]) -->|title Β· skills Β· Remote/Any| S[Search form]
S --> J["jobs.search_jobs()"]
J --> R[Remotive]
J --> RO[RemoteOK]
J --> AB[Arbeitnow]
J --> JC[Jobicy]
J --> TM[The Muse]
J -. optional key .-> AD[Adzuna]
J -. optional key .-> JS[JSearch]
R & RO & AB & JC & TM --> N[Normalize Β· de-dupe Β· match score]
N --> C[[Job cards]]
C -->|Select| D[Detail panel]
D -->|Analyze| E["ai.enrich()"]
D -->|Generate| G["ai.generate()"]
E & G --> AI
AI --> OMNI[OmniRoute / Groq / OpenAI / Ollama]
The job boards find the jobs; the AI enriches them (salary, company, fit, drafts). The LLM never scrapes β it only reasons over what the APIs return.
Tech stack: Python Β· Streamlit Β· requests Β· openai SDK Β· python-dotenv. No database β results live in session state, your profile saves to a local profile.json.
# 1. Clone
git clone https://github.com/hassanannajjar/Job-Pilot.git
cd Job-Pilot
# 2. Create a virtual env + install
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
# 3. (Optional) configure AI + keys
cp .env.example .env # edit AI_BASE_URL / AI_API_KEY / AI_MODEL
# 4. Run
streamlit run Home.py
Open the URL Streamlit prints (default http://localhost:8501). Search & the tracker work with zero config β you only need an AI provider for the intel, rΓ©sumΓ©, interview, salary & drafting features (set it live in the sidebar).
Run the offline self-checks and the line-cap gate anytime (same as CI):
python scripts/check_lines.py # β "β every source file is β€ 150 lines"
python -m scripts.selftest_all # β "β all self-checks passed"
Pick a preset in the sidebar, paste a key, and go. Any OpenAI-compatible endpoint works.
| Preset | Base URL | Example model | Key needed? |
|---|---|---|---|
| OmniRoute | http://localhost:20128/v1 |
auto/best-free |
Local key (see below) |
| Groq (free) | https://api.groq.com/openai/v1 |
llama-3.3-70b-versatile |
Free β console.groq.com |
| OpenRouter | https://openrouter.ai/api/v1 |
openai/gpt-4o-mini |
openrouter.ai/keys |
| OpenAI | https://api.openai.com/v1 |
gpt-4o-mini |
Paid β platform.openai.com |
| Ollama (local) | http://localhost:11434/v1 |
llama3.1 |
None (runs on your machine) |
π‘ New to this? Groq is the fastest free path (sign up, paste key, done). Ollama runs fully offline.
OmniRoute is a local, open-source AI gateway β one OpenAI-compatible endpoint (http://localhost:20128/v1) that routes across 290+ providers with free-tier fallback. Job-Pilot connects to it like any other provider.
π Full step-by-step guide: docs/omniroute.md.

The OmniRoute (local) preset is selected by default, so the sidebar already points at http://localhost:20128/v1 with model auto/best-free β you just paste your key and load models.
Setup:
http://localhost:20128.sequenceDiagram
participant JP as Job-Pilot
participant OR as OmniRoute (:20128)
participant P as Provider (DeepSeek / Kimi / β¦)
JP->>OR: POST /v1/chat/completions (model = auto/best-free)
OR->>P: routes to a healthy provider
P-->>OR: completion
OR-->>JP: OpenAI-shaped response
OmniRoute exposes namespaced model routes β there is no bare auto. Sending auto (or auto/best-chat) can fall through to a weak provider (e.g. felo-chat) that returns garbage like .com.
| Use these β | Avoid these β |
|---|---|
auto/best-free, auto/smart (route to DeepSeek) |
bare auto, auto/best-chat (β felo-chat junk) |
Use π Load models to see everything your OmniRoute exposes, and π§ͺ Test connection to confirm a model actually replies before you rely on it. If output ever comes back as a fragment, Job-Pilot detects it and tells you to switch models. Note: some providers may return errors independent of Job-Pilot (e.g. Kimi 402 if its quota/billing is unset, occasional 502s) β just pick another working route.
| Source | Coverage | Salary data | Key required |
|---|---|---|---|
| Remotive | Remote (tech + non-tech) | sometimes | β none |
| RemoteOK | Remote (tech) | sometimes | β none |
| Arbeitnow | EU + remote | rarely | β none |
| Jobicy | Remote | often | β none |
| The Muse | Global (many on-site) | rarely | β none |
| Adzuna | Broad incl. non-remote | β real | ADZUNA_APP_ID + ADZUNA_APP_KEY |
| JSearch | Aggregates LinkedIn/Indeed/Glassdoor/ZipRecruiter | β real | RAPIDAPI_KEY |
The five no-key sources are always on. Adzuna and JSearch activate automatically only when their keys are present in .env.
.env)| Variable | Purpose | Default |
|---|---|---|
AI_BASE_URL |
AI endpoint | http://localhost:20128/v1 |
AI_API_KEY |
Key for the endpoint | β |
AI_MODEL |
Model id | auto (override to a valid one, e.g. auto/best-free) |
ADZUNA_APP_ID / ADZUNA_APP_KEY |
Enable Adzuna | β |
ADZUNA_COUNTRY |
Adzuna country code | us |
RAPIDAPI_KEY |
Enable JSearch | β |
All AI settings are also editable live in the sidebar β .env just sets the defaults.
Remote or Any, hit π Search.Job-Pilot/
βββ Home.py # dashboard entry β `streamlit run Home.py`
βββ pages/ # one file per tool (Streamlit-native multipage nav)
β βββ 1_π_Job_Search.py
β βββ 2_π_Application_Tracker.py
β βββ 3_π_Resume_Analyzer.py
β βββ 4_π€_Interview_Prep.py
β βββ 5_π°_Salary_Coach.py
βββ lib/
β βββ ai/ # client Β· prompts Β· generate Β· analyze (+ barrel)
β βββ jobs/ # normalize Β· sources Β· search (+ barrel)
β βββ ui.py Β· ui_css.py # shared components + the single CSS kit
β βββ storage.py # profile.json + applications.json
βββ scripts/ # check_lines.py (β€150 cap) Β· selftest_all.py
βββ docs/ # per-tool documentation
βββ .github/ # CI workflow + screenshots
βββ CLAUDE.md Β· CONTRIBUTING.md # engineering rules (CI-enforced)
βββ requirements.txt Β· .env.example Β· .streamlit/config.toml
Every file stays β€ 150 lines, one responsibility each, with strict page β lib barrel β service/engine layering. See CONTRIBUTING.md.
applications.json + a pipeline page)mailto:)Issues and PRs welcome. This is a small codebase with a deliberately strict, tiny rule set
(β€150 lines/file, one responsibility, zero heavy deps, a self-check per module) β read
CONTRIBUTING.md first. CI enforces the line cap and self-checks on every PR.
Adding a tool? Drop a file in pages/, reuse lib.ai.chat() + the lib.ui kit, and add a page under docs/.
MIT Β© 2026 Hassan Al-Najjar
A personal productivity tool. Respect each job boardβs Terms of Service and rate limits, and review every AI-generated draft before sending it.