* initial commit * use colours! * match by scoring * scroll job card into view * introduce @ based 'locks' to restrict search to specific statuses * clear lock states on close * split up component * inline pill * resuse job row content * fix intro anim * larger size, instruction * refactor existing search feature * lock colour border * if active, clear active on escape * remove query param * documenration update * scoring logic * check exists before scroll * status dot and checkbox occupy the same space!
Job Ops Orchestrator
A unified orchestrator for the job application pipeline. Discovers jobs, scores them for suitability, generates tailored resumes, and provides a UI to manage applications.
Architecture
orchestrator/
├── src/
│ ├── server/ # Express backend
│ │ ├── api/ # REST API routes
│ │ ├── db/ # SQLite + Drizzle ORM
│ │ ├── pipeline/ # Orchestration logic
│ │ ├── repositories/ # Data access layer
│ │ └── services/ # Integrations (crawler, AI, PDF)
│ ├── client/ # React frontend
│ │ ├── api/ # API client
│ │ ├── components/ # UI components
│ │ └── styles/ # CSS design system
│ └── shared/ # Shared types
├── data/ # SQLite DB + generated PDFs (gitignored)
└── public/ # Static assets
Setup
-
Install dependencies:
cd orchestrator npm install -
Set up environment:
cp .env.example .env # The app is self-configuring. You can add keys via the UI Onboarding.After the server starts, use the onboarding modal to connect OpenRouter, link your v4.rxresu.me account, and select a template resume.
OpenRouter is the default LLM provider, but LM Studio, Ollama, OpenAI, and Gemini are also supported.
Deprecated:
OPENROUTER_API_KEY/openrouterApiKey. UseLLM_API_KEY/llmApiKeyinstead (legacy values are auto-migrated/copied for compatibility). -
Initialize database:
npm run db:migrate -
Start development server:
npm run devThis starts:
- Backend API at
http://localhost:3001 - Frontend at
http://localhost:5173
- Backend API at
API Endpoints
Jobs
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/jobs |
List all jobs (filter with ?status=ready,discovered) |
| GET | /api/jobs/:id |
Get single job |
| PATCH | /api/jobs/:id |
Update job |
| POST | /api/jobs/:id/process |
Generate resume for job |
| POST | /api/jobs/:id/apply |
Mark as applied + sync to Notion |
| POST | /api/jobs/:id/skip |
Mark as skipped |
Pipeline
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/pipeline/status |
Get pipeline status |
| GET | /api/pipeline/runs |
Get recent pipeline runs |
| POST | /api/pipeline/run |
Trigger pipeline manually |
| POST | /api/webhook/trigger |
Webhook for n8n (use WEBHOOK_SECRET) |
Daily Flow
-
17:00 - n8n triggers pipeline:
- Calls
POST /api/webhook/trigger - Pipeline crawls Gradcracker
- Scores jobs with AI
- Generates tailored resumes for top 10
- Calls
-
You review in the UI:
- See jobs at
http://localhost:5173 - "Ready" tab shows jobs with generated PDFs
- Use command bar search (
Cmd/Ctrl+K) to quickly find and open jobs - Click "View Job" to open application
- Download PDF and apply manually
- Click "Mark Applied" → syncs to Notion
- See jobs at
n8n Setup
Create a workflow with:
- Schedule Trigger - Every day at 17:00
- HTTP Request:
- Method: POST
- URL:
http://localhost:3001/api/webhook/trigger - Headers:
Authorization: Bearer YOUR_WEBHOOK_SECRET
Development
# Run just the server
npm run dev:server
# Run just the client
npm run dev:client
# Run the pipeline manually
npm run pipeline:run
# Build for production
npm run build
npm start
Tech Stack
- Backend: Express, TypeScript, Drizzle ORM, SQLite
- Frontend: React, Vite, CSS (custom design system)
- AI: Configurable LLM provider (OpenRouter default; also supports OpenAI/Gemini/LM Studio/Ollama)
- PDF Generation: RxResume v4 API export (configured via Settings)
- Job Crawling: Wraps existing TypeScript Crawlee crawler