248 lines
6.8 KiB
Markdown
248 lines
6.8 KiB
Markdown
# claw-apply — Skill Spec v0.1
|
|
|
|
Automated job search and application skill for OpenClaw.
|
|
Searches LinkedIn and Wellfound for matching roles, applies automatically using Playwright + Kernel stealth browsers.
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
### Two agents
|
|
|
|
**JobSearcher** (`job_searcher.mjs`)
|
|
- Runs on a schedule (default: hourly)
|
|
- Searches configured platforms with configured queries
|
|
- Filters out excluded roles/companies
|
|
- Dedupes against existing queue
|
|
- Writes new jobs to `jobs_queue.json` with status `new`
|
|
- Sends Telegram summary: "Found X new jobs"
|
|
|
|
**JobApplier** (`job_applier.mjs`)
|
|
- Runs on a schedule (default: every 6 hours)
|
|
- Reads `jobs_queue.json` for status `new` + `needs_answer`
|
|
- Attempts to apply to each job
|
|
- On success → status: `applied`
|
|
- On unknown question → messages user via Telegram, status: `needs_answer`
|
|
- On skip/fail → status: `skipped` or `failed`
|
|
- Sends Telegram summary when done
|
|
|
|
---
|
|
|
|
## Config Files (user sets up once)
|
|
|
|
### `profile.json`
|
|
```json
|
|
{
|
|
"name": { "first": "Jane", "last": "Smith" },
|
|
"email": "jane@example.com",
|
|
"phone": "555-123-4567",
|
|
"location": {
|
|
"city": "San Francisco",
|
|
"state": "California",
|
|
"zip": "94102",
|
|
"country": "United States"
|
|
},
|
|
"linkedin_url": "https://linkedin.com/in/janesmith",
|
|
"resume_path": "/home/user/resume.pdf",
|
|
"years_experience": 7,
|
|
"work_authorization": {
|
|
"authorized": true,
|
|
"requires_sponsorship": false
|
|
},
|
|
"willing_to_relocate": false,
|
|
"desired_salary": 150000,
|
|
"cover_letter": "Your cover letter text here..."
|
|
}
|
|
```
|
|
|
|
### `search_config.json`
|
|
```json
|
|
{
|
|
"searches": [
|
|
{
|
|
"name": "Founding GTM",
|
|
"track": "gtm",
|
|
"keywords": [
|
|
"founding account executive",
|
|
"first sales hire",
|
|
"first GTM hire",
|
|
"founding AE",
|
|
"head of sales startup remote"
|
|
],
|
|
"platforms": ["linkedin", "wellfound"],
|
|
"filters": {
|
|
"remote": true,
|
|
"posted_within_days": 2
|
|
},
|
|
"exclude_keywords": ["BDR", "SDR", "staffing", "insurance", "retail", "consumer", "recruiter"],
|
|
"salary_min": 130000
|
|
},
|
|
{
|
|
"name": "Enterprise AE",
|
|
"track": "ae",
|
|
"keywords": [
|
|
"enterprise account executive SaaS remote",
|
|
"senior account executive technical SaaS remote"
|
|
],
|
|
"platforms": ["linkedin"],
|
|
"filters": {
|
|
"remote": true,
|
|
"posted_within_days": 2,
|
|
"easy_apply_only": true
|
|
},
|
|
"exclude_keywords": ["BDR", "SDR", "SMB", "staffing"],
|
|
"salary_min": 150000
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### `answers.json`
|
|
Flat array of pattern → answer mappings. Pattern is substring match (case-insensitive). First match wins.
|
|
```json
|
|
[
|
|
{ "pattern": "quota attainment", "answer": "1.12", "note": "FY24 $1.2M quota, hit $1.12M" },
|
|
{ "pattern": "sponsor", "answer": "No" },
|
|
{ "pattern": "authorized", "answer": "Yes" },
|
|
{ "pattern": "relocat", "answer": "No" },
|
|
{ "pattern": "years.*sales", "answer": "7" },
|
|
{ "pattern": "years.*enterprise", "answer": "5" },
|
|
{ "pattern": "years.*crm", "answer": "7" },
|
|
{ "pattern": "1.*10.*scale", "answer": "9" },
|
|
{ "pattern": "salary", "answer": "150000" },
|
|
{ "pattern": "start date", "answer": "Immediately" }
|
|
]
|
|
```
|
|
|
|
### `settings.json`
|
|
```json
|
|
{
|
|
"mode": "A",
|
|
"review_window_minutes": 30,
|
|
"schedules": {
|
|
"search": "0 * * * *",
|
|
"apply": "0 */6 * * *"
|
|
},
|
|
"max_applications_per_run": 50,
|
|
"notifications": {
|
|
"telegram_user_id": "YOUR_TELEGRAM_ID"
|
|
},
|
|
"kernel": {
|
|
"proxy_id": "YOUR_KERNEL_PROXY_ID",
|
|
"profiles": {
|
|
"linkedin": "LinkedIn-YourName",
|
|
"wellfound": "WellFound-YourName"
|
|
}
|
|
},
|
|
"browser": {
|
|
"provider": "kernel",
|
|
"fallback": "local"
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Data Files (auto-managed)
|
|
|
|
### `jobs_queue.json`
|
|
```json
|
|
[
|
|
{
|
|
"id": "li_4381658809",
|
|
"platform": "linkedin",
|
|
"track": "ae",
|
|
"title": "Senior Account Executive",
|
|
"company": "Acme Corp",
|
|
"url": "https://linkedin.com/jobs/view/4381658809/",
|
|
"found_at": "2026-03-05T22:00:00Z",
|
|
"status": "new",
|
|
"status_updated_at": "2026-03-05T22:00:00Z",
|
|
"pending_question": null,
|
|
"applied_at": null,
|
|
"notes": null
|
|
}
|
|
]
|
|
```
|
|
|
|
**Statuses:** `new` → `applied` / `skipped` / `failed` / `needs_answer`
|
|
|
|
### `applications_log.json`
|
|
Append-only history of every application attempt with outcome.
|
|
|
|
---
|
|
|
|
## Unknown Question Flow
|
|
|
|
1. Applier hits a required field it can't answer
|
|
2. Marks job as `needs_answer`, stores the question text in `pending_question`
|
|
3. Sends Telegram: *"Applying to Senior AE @ Acme Corp and hit this question: 'What was your last quota attainment in $M?' — what should I answer?"*
|
|
4. Moves on to next job
|
|
5. User replies → answer saved to `answers.json`
|
|
6. Next applier run retries all `needs_answer` jobs
|
|
|
|
---
|
|
|
|
## Mode A vs Mode B
|
|
|
|
**Mode A (fully automatic):**
|
|
Search → Queue → Apply. No intervention required.
|
|
|
|
**Mode B (soft gate):**
|
|
Search → Queue → Telegram summary sent to user → 30 min window to reply with any job IDs to skip → Apply runs.
|
|
|
|
Configured via `settings.json` → `mode: "A"` or `"B"`
|
|
|
|
---
|
|
|
|
## File Structure
|
|
|
|
```
|
|
claw-apply/
|
|
├── SKILL.md ← OpenClaw skill entry point
|
|
├── SPEC.md ← this file
|
|
├── job_searcher.mjs ← search agent
|
|
├── job_applier.mjs ← apply agent
|
|
├── lib/
|
|
│ ├── browser.mjs ← Kernel/Playwright browser factory
|
|
│ ├── form_filler.mjs ← form filling logic
|
|
│ ├── linkedin.mjs ← LinkedIn search + apply
|
|
│ ├── wellfound.mjs ← Wellfound search + apply
|
|
│ └── notify.mjs ← Telegram notifications
|
|
├── config/
|
|
│ ├── profile.json ← user fills this
|
|
│ ├── search_config.json← user fills this
|
|
│ ├── answers.json ← auto-grows over time
|
|
│ └── settings.json ← user fills this
|
|
└── data/
|
|
├── jobs_queue.json ← auto-managed
|
|
└── applications_log.json ← auto-managed
|
|
```
|
|
|
|
---
|
|
|
|
## Setup (user steps)
|
|
|
|
1. Install: `openclaw skill install claw-apply`
|
|
2. Configure Kernel Managed Auth for LinkedIn + Wellfound (or provide local Chrome)
|
|
3. Create a residential proxy in Kernel: `kernel proxies create --type residential --country US`
|
|
4. Fill in `config/profile.json`, `config/search_config.json`, `config/settings.json`
|
|
5. Run: `openclaw skill run claw-apply setup` — registers crons, verifies login, sends test notification
|
|
6. Done. Runs automatically.
|
|
|
|
---
|
|
|
|
## v1 Scope
|
|
|
|
- [x] LinkedIn Easy Apply
|
|
- [x] Wellfound apply
|
|
- [x] Kernel stealth browser + residential proxy
|
|
- [x] Mode A + Mode B
|
|
- [x] Unknown question → Telegram → answers.json flow
|
|
- [x] Deduplication
|
|
- [x] Hourly search / 6hr apply cron
|
|
- [ ] Indeed (v2)
|
|
- [ ] External ATS / Greenhouse / Lever (v2)
|
|
- [ ] Job scoring/ranking (v2)
|
|
- [ ] Cover letter generation per-job via LLM (v2)
|