Job Agent
by PARK0301
README.md
# Job Agent
AI-powered job search and application assistant: multi-source hunting, OpenAI matching, cover letters, and hybrid apply (Playwright ATS → Chrome CDP → optional screen OCR → manual assist).
> **Safety first:** read [DISCLAIMER.md](DISCLAIMER.md). Keep `require_submit_confirmation: true` and prefer `--dry-run` until you trust the flow.
## Architecture
```text
┌─────────────┐ ┌──────────────┐ ┌─────────────────────────────┐
│ Job sources │ → │ OpenAI match │ → │ today.json + daily_report │
│ LinkedIn │ │ gpt-4o-mini │ │ + optional Canvas sidecars │
│ JobsDB │ └──────────────┘ └──────────────┬──────────────┘
│ Adzuna … │ │ approve
└─────────────┘ ▼
┌─────────────────────┐
│ ApplyRouter │
│ Playwright ATS │
│ LinkedIn CDP │
│ Screen OCR (macOS) │
│ Manual assist pack │
└─────────────────────┘
```
## Requirements
- Python 3.11+
- OpenAI API key (matching + cover letters)
- Optional: Adzuna App ID/Key
- Chrome (LinkedIn Easy Apply via CDP)
- macOS (Screen OCR fallback; Accessibility + Screen Recording permissions)
## Setup
```bash
git clone https://github.com/<you>/job-agent.git ~/job-agent
cd ~/job-agent
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
playwright install chromium
cp .env.example .env # OPENAI_API_KEY, optional ADZUNA_*
cp profile/profile.example.json profile/profile.json
cp profile/answers.example.json profile/answers.json
python -m src.cli onboard # or edit profile JSON directly
```
Edit `config.yaml` for search sources, match threshold, Chrome CDP URL, and paths.
Optional Canvas sync: set `canvas_dir` (or env `CURSOR_CANVAS_DIR`) to your Cursor canvases folder.
## Four ways to use Job Agent
### 1. CLI
```bash
python -m src.cli launch # tune (if needed) + hunt
python -m src.cli list # markdown report
./scripts/approve_and_apply.sh <id> # approve + apply + cover letter
python -m src.cli apply <id> --dry-run
./scripts/start_chrome_debug.sh # LinkedIn Easy Apply via CDP
python -m src.cli cdp-status
```
### 2. Web Dashboard
```bash
python -m src.cli dashboard
# or macOS Desktop shortcut:
./scripts/install_desktop_shortcut.sh
```
Open http://127.0.0.1:8787 — run hunts, batch-approve, paste ATS URLs, manage applied history.
### 3. Cursor Agent + MCP
1. Open this folder as the Cursor workspace
2. Create the venv and install deps (MCP uses `.venv/bin/python` — see `.cursor/mcp.json`)
3. Run `./scripts/verify_mcp.sh`
4. Use the **job-hunt** skill (`.cursor/skills/job-hunt/SKILL.md`)
| MCP server | Role |
|------------|------|
| `job-search` | Hunt, match, list jobs |
| `playwright-agent` | Browser automation |
| `screen-agent` | macOS screen OCR fallback |
Example chat prompts:
- "Run today's job hunt and show top 3 matches"
- "Approve job `<id>` with cover letter, dry-run only"
- "Tune my profile — ask about missing salary and notice period"
Optional Canvas UI samples live in `canvases/`. Sync sidecars with `python scripts/sync_canvas.py` after setting `canvas_dir`.
### 4. Cursor Automation
Import `automation/daily-job-hunt.yaml`:
1. Open **Automations** in Cursor
2. Import the YAML (cron: weekdays 08:00)
3. Point `gitConfig.repo` at your clone (`~/job-agent`)
4. Ensure `.env` is available to the agent runtime
The automation runs `./scripts/daily_hunt.sh` only — **no automatic submit**. Review matches in the dashboard.
## Learning loop (review before it changes your profile)
Nothing is written to your profile automatically. After each apply the agent files
*proposals* into a review inbox; you approve or reject them in the dashboard's
**Learning** tab (or via CLI). Password-like fields are never proposed.
| Proposal | Approving it does | Source |
|----------|-------------------|--------|
| `question_answer` | Merges the (editable) answer into `profile/answers.json` → `custom_answers`, so the next apply auto-fills it | `form_answers.json` of a successful apply |
| `fixture` | Copies `page.html` into `tests/fixtures/ats/` + writes a pytest stub | a failed apply that left a snapshot |
| `retry_policy` | Records a channel-specific retry hint | repeated failures of one `failure_type` |
| `hunt_signal` | Feeds a boost/skip signal into scoring | CRM outcome or the "bad match" button |
```bash
python -m src.cli learn list # pending proposals (JSON)
python -m src.cli learn approve <id> # apply it
python -m src.cli learn reject <id> # drop it
```
CRM statuses close the loop on their own: `interview` / `offer` boost similar companies
and titles in future scoring, while `rejected` / `withdrawn` down-weight them.
### Reliability & ops CLI
```bash
python -m src.cli apply-report --days 7 # success rates, failure types, timings
python -m src.cli crm list
python -m src.cli source-eval # which search sources actually convert
python -m src.cli export-applications --output ~/Desktop/applications.csv
python -m src.cli profile list # multi-profile switching
```
Failures are bucketed into `login_gate`, `empty_required`, `resume_upload`, `captcha`,
`timeout`, and `unsupported` so `apply-report` can tell you what to fix next. ATS account
walls (Workday and friends) are reported as `login_gate` with the credential key to add —
not as "unsupported ATS".
## Config highlights
| Key | Purpose |
|-----|---------|
| `match_threshold` | Minimum OpenAI match score |
| `search_sources` | e.g. `linkedin`, `jobsdb`, `adzuna`, `remotive` |
| `linkedin_mode` | `hybrid` / `playwright` / `screen` / `manual` |
| `require_submit_confirmation` | Skip final Submit until confirmed (default `true`) |
| `chrome_cdp_url` | Debug Chrome endpoint (default `http://127.0.0.1:9222`) |
| `canvas_dir` | Optional Cursor Canvas sidecar directory |
| `applications_dir` | Where cover letters / apply artefacts are written |
## Tests
```bash
pytest
```
## License
MIT — see [LICENSE](LICENSE).
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues