A local-first job application tracker that lets you add and manage applications using natural language — typed or spoken. You describe what you want ("Add Neilsoft for AI Engineer, full time, onsite") and the system interprets it, shows you an editable preview, and saves only after your explicit confirmation. Nothing is written to the database by the LLM directly.
This repository (jobtracker-MASTER) is the umbrella entry point for the complete project. The four service repositories are linked as Git submodules. Supporting folders (docker/, docs/, evaluation/, data/, job_tracker-extension/) are tracked directly here.
Tracking a serious job search across dozens of applications — each with its own status, stage, notes, links, and next actions — becomes unwieldy in a spreadsheet. Context switches are expensive: what stage is this one at? Did I follow up? Where is the job listing? The standard answer is a heavyweight ATS, most of which are designed for recruiters, not candidates.
This project is the other approach: a lightweight local tracker that stays out of the way, captures what you actually care about, and lets you add and update entries by describing them in plain language rather than filling out forms.
| Capability | Details |
|---|---|
| Application tracking | Add, view, edit, and archive job applications |
| Draft and pending-change workflow | All LLM-generated changes are previews; nothing is persisted without an explicit save |
| Conversational commands | Natural-language typed commands: "Add Neilsoft for AI Engineer, full time, onsite" |
| Multi-role creates | One command can propose multiple roles for the same company |
| Company matching | Canonical company names and aliases are stored; new companies trigger a confirmation modal |
| Context follow-ups | Follow-up commands apply against the active unsaved draft |
| Voice input | LiveKit tap-to-toggle voice; transcript appears in panel, copied to command area on explicit click |
| ASR hotword adaptation | Backend exposes /asr/hotwords for Whisper and the agent to improve transcription accuracy |
| Browser-context capture | Chrome extension captures the active tab URL and title; one click fills the JOB LINK field |
| Local-first execution | Everything runs on your machine; no external API calls required |
| Evaluation tooling | Separate evaluation harness for benchmarking Whisper model accuracy against ground truth |
| Path | Type | Responsibility |
|---|---|---|
jobtracker-BE/ |
Git submodule | FastAPI backend — CRUD API, semantic command interpreter, company table, Alembic migrations, ASR hotwords |
jobtracker-FE/ |
Git submodule | Next.js frontend — tracker table, add/edit forms, draft panel, voice panel, company confirmation modal |
livekit-agent/ |
Git submodule | Python LiveKit RTC participant — subscribes to browser mic audio, calls Whisper, publishes transcript |
whisper-service/ |
Git submodule | CUDA Faster-Whisper microservice — accepts audio uploads, returns transcripts |
job_tracker-extension/ |
Supporting folder | Chrome Manifest V3 extension — captures active tab URL/title, posts to backend |
docker/ |
Supporting folder | Dockerfiles for services (e.g. faster-whisper.Dockerfile) |
evaluation/ |
Supporting folder | Whisper accuracy evaluation harness — ground truth CSV, run scripts, reports |
data/ |
Supporting folder | Raw audio files used for evaluation runs |
docs/ |
Documentation | Architecture notes and implementation documents |
AGENTS.md |
Documentation | Project constraints and field definitions for AI assistants |
graph TD
A[User — Browser UI] -->|natural-language command| B[Next.js Frontend :3000]
B -->|POST /api/chat| C[FastAPI Backend :8000]
C -->|structured extraction prompt| D[Ollama llama3.2:3b :11434]
D -->|extracted fields + tool call| C
C -->|editable preview response| B
B -->|explicit Save click| C
C -->|SQL INSERT / UPDATE| E[PostgreSQL :5432]
graph TD
A[User — Browser UI] -->|Connect + Start Recording| F[LiveKit Server :7880]
F -->|WebRTC audio track| G[livekit-agent]
G -->|POST /transcribe| H[Whisper Service :8100]
H -->|transcript text| G
G -->|final_transcript packet| F
F -->|WebRTC data channel| B[Next.js Frontend :3000]
B -->|Use transcript button| A
Chrome Extension → POST /browser-context → FastAPI Backend
↓
Stored as latest captured URL
↓
Frontend "Use captured URL" button → JOB LINK field
End-to-end sequence:
- User types a command ("Add Neilsoft for AI Engineer") or uses the voice flow to get a transcript.
- Frontend sends the command to
POST /api/chatalong with the active unsaved draft and any selected persisted row. - Backend calls Ollama with a structured extraction prompt. Pydantic validates the output. Deterministic rules resolve company matching and enum normalization.
- Backend returns an editable preview — the LLM never writes to the database.
- If the company is new, the frontend shows a confirmation modal. If it matches an existing company, the save is low-friction.
- User clicks Save Application or Save Update. The change is persisted through the standard CRUD endpoints.
| Layer | Technologies |
|---|---|
| Frontend | Next.js 16, React 19, TypeScript 5.7, Tailwind CSS 4, Radix UI, livekit-client 2 |
| Backend | Python 3.11+, FastAPI 0.115, SQLAlchemy 2, Alembic, Pydantic 2, Uvicorn, psycopg 3 |
| Database | PostgreSQL |
| Local LLM | Ollama (llama3.2:3b or any compatible 7B+ model) |
| Voice transport | LiveKit server (local dev mode) |
| Speech-to-text | Faster-Whisper 1.1.1 on CUDA 12.3, ctranslate2 4.5.0, exposed as a FastAPI microservice |
| Browser integration | Chrome Manifest V3 extension (unpacked) |
| Evaluation tooling | Python scripts, pytest, ground truth CSV, per-run JSON reports |
Required for the core tracker (frontend + backend):
- Git with submodule support
- Node.js 20+ with npm
- Python 3.11+ with venv
- PostgreSQL accessible on port 5432
- Ollama running on port 11434 with
llama3.2:3bpulled
Required for voice input:
- LiveKit server CLI (
livekit-server --dev) - Docker with NVIDIA Container Toolkit and a CUDA-capable GPU
Optional:
- Google Chrome (for the browser extension)
git clone --recurse-submodules git@github-adi:adityadmore2000/jobtracker-MASTER.git
cd jobtracker-MASTERIf you already cloned without --recurse-submodules:
git submodule update --init --recursive# PostgreSQL
docker start resume_tailor
# Ollama
docker start ollama
# ollama pull llama3.2:3b # if not already pulled
# LiveKit (required only for voice)
livekit-server --devcd jobtracker-BE
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtCreate jobtracker-BE/.env:
DATABASE_URL=postgresql+psycopg://<user>:<password>@localhost:5432/job_tracker
TEST_DATABASE_URL=postgresql+psycopg://<user>:<password>@localhost:5432/job_tracker_test
FRONTEND_ORIGIN=http://localhost:3000,http://127.0.0.1:3000
AUTO_MIGRATE=false
OLLAMA_BASE_URL=http://127.0.0.1:11434
OLLAMA_MODEL=llama3.2:3b
OLLAMA_TIMEOUT_SECONDS=20
OLLAMA_KEEP_ALIVE=10m
OLLAMA_MAX_TOOL_TURNS=2
LIVEKIT_URL=ws://127.0.0.1:7880
LIVEKIT_API_KEY=devkey
LIVEKIT_API_SECRET=secretBootstrap databases and run migrations:
python scripts/bootstrap_postgres.py
alembic upgrade headStart:
uvicorn app.main:app --reloadHealth check: curl http://127.0.0.1:8000/semantic-interpreter/health
cd jobtracker-FE
npm installCreate jobtracker-FE/.env.local:
NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8000Start:
npm run devApp available at http://localhost:3000.
docker build -f whisper-service/Dockerfile -t job-tracker-whisper-cuda:latest whisper-service
mkdir -p "$HOME/.cache/huggingface"
docker run --rm --gpus all \
-p 8100:8100 \
--env-file whisper-service/.env \
-v "$HOME/.cache/huggingface:/root/.cache/huggingface" \
job-tracker-whisper-cuda:latestHealth check: curl http://127.0.0.1:8100/health
cd livekit-agent
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
python agent.py- Open
chrome://extensions - Enable Developer mode
- Click Load unpacked and select
job_tracker-extension/
Open http://localhost:3000. Type:
Add Neilsoft for AI Engineer
An editable preview should appear. Click Save Application to persist.
| Variable | Required | Purpose | Safe Example |
|---|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string | postgresql+psycopg://user:pass@localhost:5432/job_tracker |
TEST_DATABASE_URL |
Yes (tests) | Separate test database | postgresql+psycopg://user:pass@localhost:5432/job_tracker_test |
FRONTEND_ORIGIN |
Yes | CORS allowed origins | http://localhost:3000,http://127.0.0.1:3000 |
AUTO_MIGRATE |
No | Run Alembic on startup | false |
OLLAMA_BASE_URL |
Yes | Ollama endpoint | http://127.0.0.1:11434 |
OLLAMA_MODEL |
Yes | Model name | llama3.2:3b |
OLLAMA_TIMEOUT_SECONDS |
No | Per-request timeout | 20 |
OLLAMA_KEEP_ALIVE |
No | Model keep-alive duration | 10m |
OLLAMA_MAX_TOOL_TURNS |
No | Max LLM tool turns | 2 |
LIVEKIT_URL |
No | LiveKit server URL | ws://127.0.0.1:7880 |
LIVEKIT_API_KEY |
No | LiveKit API key | devkey |
LIVEKIT_API_SECRET |
No | LiveKit API secret | secret |
| Variable | Required | Purpose | Safe Example |
|---|---|---|---|
NEXT_PUBLIC_API_BASE_URL |
Yes | Backend base URL | http://127.0.0.1:8000 |
| Variable | Required | Purpose | Safe Example |
|---|---|---|---|
LIVEKIT_URL |
Yes | LiveKit server WebSocket URL | ws://127.0.0.1:7880 |
LIVEKIT_API_KEY |
Yes | LiveKit API key | devkey |
LIVEKIT_API_SECRET |
Yes | LiveKit API secret | secret |
LIVEKIT_ROOM_NAME |
Yes | Room to join | job-tracker-local |
JOBTRACKER_BACKEND_URL |
Yes | Backend URL for hotwords | http://127.0.0.1:8000 |
WHISPER_SERVICE_URL |
Yes | Whisper microservice URL | http://127.0.0.1:8100 |
WHISPER_REQUEST_TIMEOUT_SECONDS |
No | Whisper HTTP timeout | 120 |
| Variable | Required | Purpose | Safe Example |
|---|---|---|---|
WHISPER_MODEL_SIZE |
No | Faster-Whisper model size | small |
WHISPER_DEVICE |
No | Inference device | cuda |
WHISPER_COMPUTE_TYPE |
No | CTranslate2 compute type | float16 |
WHISPER_LANGUAGE |
No | Language hint | en |
MAX_AUDIO_UPLOAD_MB |
No | Max upload size | 20 |
- Open
http://localhost:3000. The tracker table is empty on first run. - Type a command in the Transcript Command area:
Add Stripe for AI Engineer, full time, remote - An editable draft appears. Adjust any field, then click Save Application.
- To update an existing row: click the row to select it, then type:
Update status to interviewing - To capture a job listing URL: click the Chrome extension on the job page, then click Use captured URL in the form.
- For voice: click Connect, then Start Recording. Speak your command. Click Stop Recording, then Use transcript to copy it to the command area.
| Method | Path | Description |
|---|---|---|
GET |
/applications |
List all applications |
POST |
/applications |
Create a new application |
PATCH |
/applications/{id} |
Update an application |
DELETE |
/applications/{id} |
Permanently delete an application |
POST |
/api/chat |
Process a natural-language command; returns an editable preview |
GET |
/asr/hotwords |
Return deduplicated hotword list for Whisper/agent callers |
POST |
/browser-context |
Store a captured browser URL from the Chrome extension |
GET |
/browser-context/latest |
Retrieve the most recently captured URL |
GET |
/semantic-interpreter/health |
Ollama connectivity health check |
| Repository | Command | Notes |
|---|---|---|
jobtracker-BE |
cd jobtracker-BE && source .venv/bin/activate && pytest |
Requires TEST_DATABASE_URL pointing to a running PostgreSQL instance |
jobtracker-FE |
cd jobtracker-FE && npm run test |
Vitest + Testing Library; no server required |
livekit-agent |
cd livekit-agent && source .venv/bin/activate && pytest |
Mocks LiveKit and Whisper; no services required |
whisper-service |
cd whisper-service && PYTHONPATH=. pytest |
Mocks Whisper model; no GPU or Docker required |
| Evaluation tooling | cd evaluation && python evaluate.py |
Requires Whisper service running and audio files in data/raw/ |
Changes must be committed inside the relevant child repository first. Then update the umbrella's submodule pointer.
# 1. Make changes inside the child repository
cd jobtracker-BE
git add app/main.py
git commit -m "fix: improve semantic prompt"
git push
# 2. Update the umbrella pointer
cd ..
git add jobtracker-BE
git commit -m "chore: update jobtracker-BE revision"
git pushThe child-repository commit and the umbrella pointer update are always separate commits. The umbrella never contains child source code directly — only a pointer to a specific commit in each child repo.
- Voice transcripts always require an explicit Use transcript click before they reach the command area — there is no automatic submission.
- No VAD, silence detection, or partial transcripts in the voice pipeline.
- No CSV import/export.
- No reminders, scheduled follow-ups, or notifications.
- No analytics or reporting dashboards.
- The Chrome extension captures URL and title only; it does not scrape page content or infer metadata.
- The Whisper service requires a CUDA-capable GPU for production use; CPU fallback is for debugging only.
- No automatic fine-tuning or model deployment pipeline.
| Path | Contents |
|---|---|
AGENTS.md |
Project constraints, allowed field values, and rules for AI assistants working in this repo |
docs/ |
Architecture notes and implementation documents |
evaluation/README.md |
Whisper evaluation harness — how to run, directory layout, Docker build |
| Repository | URL |
|---|---|
jobtracker-BE |
git@github-adi:adityadmore2000/jobtracker-BE.git |
jobtracker-FE |
git@github-adi:adityadmore2000/jobtracker-FE.git |
livekit-agent |
git@github-adi:adityadmore2000/livekit-agent.git |
whisper-service |
git@github-adi:adityadmore2000/whisper-service.git |
License information has not yet been added.