Update all docs: README, SKILL.md, SPEC.md for current architecture
- Add Telegram answer learning flow (poller + applier safety net) - Add AI filtering, job scoring, cross-track dedup - Add browser crash recovery, fuzzy select matching, shadow DOM details - Update file structure with all new modules - Update job statuses (no_modal, stuck, filtered, duplicate) - Update scheduling info (OpenClaw crons, not crontab/PM2) - Update roadmap Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
181
README.md
181
README.md
@@ -7,10 +7,12 @@ Built for [OpenClaw](https://openclaw.dev) but runs standalone with Node.js.
|
||||
## What it does
|
||||
|
||||
- **Searches** LinkedIn and Wellfound on a schedule with your configured keywords and filters
|
||||
- **Filters** jobs using Claude AI batch scoring — only applies to roles that match your profile
|
||||
- **Applies** to matching jobs automatically via LinkedIn Easy Apply and Wellfound's native flow
|
||||
- **Learns** — when it hits a question it can't answer, it messages you on Telegram, saves your reply, and never asks again
|
||||
- **Deduplicates** across runs so you never apply to the same job twice
|
||||
- **Learns** — when it hits a question it can't answer, it asks Claude for a suggestion, messages you on Telegram, and saves your reply for all future jobs
|
||||
- **Deduplicates** across runs and search tracks so you never apply to the same job twice
|
||||
- **Retries** failed applications up to a configurable number of times before giving up
|
||||
- **Recovers** from browser crashes, session timeouts, and network errors automatically
|
||||
|
||||
## Quick start
|
||||
|
||||
@@ -42,16 +44,21 @@ claw-apply uses [Kernel](https://kernel.sh) for stealth browser sessions that by
|
||||
|
||||
```bash
|
||||
npm install -g @onkernel/cli
|
||||
kernel login
|
||||
|
||||
# Create a residential proxy
|
||||
kernel proxies create --type residential --country US --name "claw-apply-proxy"
|
||||
|
||||
# Create authenticated browser profiles (follow prompts to log in)
|
||||
kernel auth create --name "LinkedIn-YourName"
|
||||
kernel auth create --name "WellFound-YourName"
|
||||
# Create authenticated browser profiles
|
||||
kernel auth connections create --profile-name "LinkedIn-YourName" --domain linkedin.com
|
||||
kernel auth connections create --profile-name "WellFound-YourName" --domain wellfound.com
|
||||
|
||||
# Complete initial login flows
|
||||
kernel auth connections login <linkedin-connection-id>
|
||||
kernel auth connections login <wellfound-connection-id>
|
||||
```
|
||||
|
||||
Add the profile names and proxy ID to `config/settings.json`.
|
||||
Add the profile names, connection IDs, and proxy ID to `config/settings.json`.
|
||||
|
||||
### 3. Set up Telegram notifications
|
||||
|
||||
@@ -60,33 +67,46 @@ Add the profile names and proxy ID to `config/settings.json`.
|
||||
3. Message [@userinfobot](https://t.me/userinfobot) to get your user ID
|
||||
4. Add it to `settings.json` -> `notifications.telegram_user_id`
|
||||
|
||||
### 4. Verify setup
|
||||
### 4. Create .env
|
||||
|
||||
```bash
|
||||
KERNEL_API_KEY=your_key node setup.mjs
|
||||
echo "KERNEL_API_KEY=your_kernel_api_key" > .env
|
||||
echo "ANTHROPIC_API_KEY=your_anthropic_api_key" >> .env
|
||||
```
|
||||
|
||||
This validates your config, tests LinkedIn and Wellfound logins, and sends a test Telegram message.
|
||||
The `.env` file is gitignored. `ANTHROPIC_API_KEY` is optional but enables AI keyword generation and AI-suggested answers.
|
||||
|
||||
### 5. Run
|
||||
### 5. Verify setup
|
||||
|
||||
```bash
|
||||
# Search for jobs
|
||||
KERNEL_API_KEY=your_key node job_searcher.mjs
|
||||
|
||||
# Preview what's in the queue before applying
|
||||
KERNEL_API_KEY=your_key node job_applier.mjs --preview
|
||||
|
||||
# Apply to queued jobs
|
||||
KERNEL_API_KEY=your_key node job_applier.mjs
|
||||
node setup.mjs
|
||||
```
|
||||
|
||||
For automated runs, set up cron or use OpenClaw's scheduler:
|
||||
Validates config, tests logins, and sends a test Telegram message.
|
||||
|
||||
### 6. Run
|
||||
|
||||
```bash
|
||||
node job_searcher.mjs # search now
|
||||
node job_filter.mjs # AI filter + score jobs
|
||||
node job_applier.mjs --preview # preview queue without applying
|
||||
node job_applier.mjs # apply now
|
||||
node telegram_poller.mjs # process Telegram answer replies
|
||||
node status.mjs # show queue + run status
|
||||
```
|
||||
Search: 0 * * * * (hourly)
|
||||
Apply: 0 */6 * * * (every 6 hours)
|
||||
```
|
||||
|
||||
### 7. Schedule (OpenClaw crons)
|
||||
|
||||
Scheduling is managed via OpenClaw cron jobs:
|
||||
|
||||
| Job | Schedule | Description |
|
||||
|-----|----------|-------------|
|
||||
| Searcher | `0 */12 * * *` | Search every 12 hours |
|
||||
| Filter | `30 * * * *` | AI filter every hour at :30 |
|
||||
| Applier | disabled by default | Enable when ready |
|
||||
| Telegram Poller | `* * * * *` | Process answer replies every minute |
|
||||
|
||||
The lockfile mechanism ensures only one instance of each agent runs at a time.
|
||||
|
||||
## How it works
|
||||
|
||||
@@ -94,32 +114,48 @@ Apply: 0 */6 * * * (every 6 hours)
|
||||
|
||||
1. Runs your configured keyword searches on LinkedIn and Wellfound
|
||||
2. Paginates through results (LinkedIn) and infinite-scrolls (Wellfound)
|
||||
3. Filters out excluded keywords and companies
|
||||
4. Deduplicates against the existing queue by job ID and URL
|
||||
5. Saves new jobs to `data/jobs_queue.json` with status `new`
|
||||
6. Sends a Telegram summary
|
||||
3. Classifies each job: Easy Apply, external ATS (Greenhouse, Lever, etc.), or recruiter-only
|
||||
4. Filters out excluded keywords and companies
|
||||
5. Deduplicates against the existing queue by job ID and URL
|
||||
6. Saves new jobs to `data/jobs_queue.json` with status `new`
|
||||
7. Sends a Telegram summary
|
||||
|
||||
### Filter flow
|
||||
|
||||
1. Submits jobs to Claude AI via Anthropic Batch API (50% cost savings)
|
||||
2. Scores each job 1-10 based on match to your profile and search track
|
||||
3. Jobs below the minimum score (default 5) are marked `filtered`
|
||||
4. Cross-track deduplication keeps the highest-scoring copy
|
||||
5. Two-phase design: submit batch → collect results (designed for cron)
|
||||
|
||||
### Apply flow
|
||||
|
||||
1. Picks up all `new` and `needs_answer` jobs from the queue (up to `max_applications_per_run`)
|
||||
2. Opens a stealth browser session per platform
|
||||
3. For each job:
|
||||
- **LinkedIn Easy Apply**: navigates to job, clicks Easy Apply, fills the multi-step modal, submits
|
||||
1. Processes Telegram replies first — saves new answers, flips answered jobs back to `new`
|
||||
2. Picks up all `new` and `needs_answer` jobs, sorted by priority (Easy Apply first)
|
||||
3. Reloads `answers.json` before each job (picks up Telegram replies mid-run)
|
||||
4. Opens a stealth browser session per platform (LinkedIn, Wellfound, external)
|
||||
5. For each job:
|
||||
- **LinkedIn Easy Apply**: navigates to job, clicks Easy Apply, fills the multi-step modal (Next → Review → Submit), handles post-submit confirmation dialogs
|
||||
- **Wellfound**: navigates to job, clicks Apply, fills the form, submits
|
||||
- Detects and skips recruiter-only listings, external ATS jobs, and honeypot questions
|
||||
4. On unknown required fields, messages you on Telegram and moves on
|
||||
5. Failed jobs are retried on the next run (up to `max_retries`, default 2)
|
||||
6. Sends a summary with counts: applied, failed, needs answer, skipped
|
||||
- Selects resume from previously uploaded resumes (radio buttons) or uploads via file input
|
||||
6. On unknown required fields: asks Claude for a suggested answer, messages you on Telegram with the question + AI suggestion, moves on
|
||||
7. Failed jobs are retried on the next run (up to `max_retries`, default 2)
|
||||
8. Browser crash recovery: detects dead sessions and creates fresh browsers automatically
|
||||
9. Sends a summary with counts: applied, failed, needs answer, skipped
|
||||
|
||||
### Self-learning answers
|
||||
|
||||
When the applier encounters a form question it doesn't know how to answer:
|
||||
|
||||
1. Marks the job as `needs_answer` with the question text
|
||||
2. Sends you a Telegram message with the question
|
||||
3. You reply with the answer
|
||||
4. The answer is saved to `config/answers.json` as a pattern match
|
||||
5. Next run, it retries the job and fills in the answer automatically
|
||||
1. Claude generates a suggested answer based on your profile and resume
|
||||
2. Telegram message sent with the question, options (if select), and AI suggestion
|
||||
3. You reply with your answer, or reply "ACCEPT" to use the AI suggestion
|
||||
4. The Telegram poller (cron, every minute) saves your answer to `answers.json` and flips the job back to `new`
|
||||
5. Next applier run retries the job with the saved answer
|
||||
6. **Every future job** with the same question is answered automatically
|
||||
|
||||
Over time, all common questions get answered and the applier runs fully autonomously.
|
||||
|
||||
Patterns support regex:
|
||||
|
||||
@@ -137,8 +173,9 @@ Patterns support regex:
|
||||
|
||||
| Key | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `max_applications_per_run` | no limit | Cap applications per run (optional, set to avoid rate limits) |
|
||||
| `max_retries` | `2` | Times to retry a failed application before marking it permanently failed |
|
||||
| `max_applications_per_run` | no limit | Cap applications per run |
|
||||
| `max_retries` | `2` | Times to retry a failed application |
|
||||
| `enabled_apply_types` | `["easy_apply"]` | Which apply types to process |
|
||||
| `browser.provider` | `"kernel"` | `"kernel"` for stealth browsers, `"local"` for local Playwright |
|
||||
|
||||
### Search filters
|
||||
@@ -156,23 +193,30 @@ Patterns support regex:
|
||||
```
|
||||
claw-apply/
|
||||
├── job_searcher.mjs Search agent
|
||||
├── job_filter.mjs AI filter + scoring agent
|
||||
├── job_applier.mjs Apply agent
|
||||
├── telegram_poller.mjs Telegram answer reply processor
|
||||
├── setup.mjs Setup wizard
|
||||
├── status.mjs Queue status report
|
||||
├── lib/
|
||||
│ ├── constants.mjs Shared constants and defaults
|
||||
│ ├── browser.mjs Kernel/Playwright browser factory
|
||||
│ ├── session.mjs Kernel Managed Auth session refresh
|
||||
│ ├── form_filler.mjs Generic form filling with pattern matching
|
||||
│ ├── keywords.mjs AI-generated search keywords via Claude
|
||||
│ ├── env.mjs .env loader (no dotenv dependency)
|
||||
│ ├── form_filler.mjs Form filling with pattern matching
|
||||
│ ├── ai_answer.mjs AI answer generation via Claude
|
||||
│ ├── filter.mjs AI job scoring via Anthropic Batch API
|
||||
│ ├── keywords.mjs AI-generated search keywords
|
||||
│ ├── linkedin.mjs LinkedIn search + job classification
|
||||
│ ├── wellfound.mjs Wellfound search
|
||||
│ ├── queue.mjs Job queue and config management
|
||||
│ ├── lock.mjs Process lock to prevent parallel runs
|
||||
│ ├── notify.mjs Telegram notifications with rate limiting
|
||||
│ ├── queue.mjs Job queue with atomic writes
|
||||
│ ├── lock.mjs PID-based process lock
|
||||
│ ├── notify.mjs Telegram Bot API (send, getUpdates, reply)
|
||||
│ ├── search_progress.mjs Per-platform search resume tracking
|
||||
│ ├── telegram_answers.mjs Telegram reply → answers.json processing
|
||||
│ └── apply/
|
||||
│ ├── index.mjs Apply handler registry
|
||||
│ ├── easy_apply.mjs LinkedIn Easy Apply
|
||||
│ ├── index.mjs Apply handler registry + status normalization
|
||||
│ ├── easy_apply.mjs LinkedIn Easy Apply (multi-step modal)
|
||||
│ ├── wellfound.mjs Wellfound apply
|
||||
│ ├── greenhouse.mjs Greenhouse ATS (stub)
|
||||
│ ├── lever.mjs Lever ATS (stub)
|
||||
@@ -187,7 +231,8 @@ claw-apply/
|
||||
│ └── settings.json Your settings (gitignored)
|
||||
└── data/
|
||||
├── jobs_queue.json Job queue (auto-managed)
|
||||
└── applications_log.json Application history (auto-managed)
|
||||
├── applications_log.json Application history (auto-managed)
|
||||
└── telegram_offset.json Telegram polling offset (auto-managed)
|
||||
```
|
||||
|
||||
## Job statuses
|
||||
@@ -199,27 +244,47 @@ claw-apply/
|
||||
| `needs_answer` | Blocked on unknown question, waiting for your reply |
|
||||
| `failed` | Failed after max retries |
|
||||
| `already_applied` | Duplicate detected, previously applied |
|
||||
| `filtered` | Below AI score threshold |
|
||||
| `duplicate` | Cross-track duplicate (lower-scoring copy) |
|
||||
| `skipped_honeypot` | Honeypot question detected |
|
||||
| `skipped_recruiter_only` | LinkedIn recruiter-only listing |
|
||||
| `skipped_external_unsupported` | External ATS (Greenhouse, Lever, etc. — stubs ready) |
|
||||
| `skipped_external_unsupported` | External ATS (Greenhouse, Lever, etc.) |
|
||||
| `skipped_easy_apply_unsupported` | LinkedIn job without Easy Apply button |
|
||||
| `skipped_no_apply` | No apply button, modal, or submit found on page |
|
||||
| `stuck` | Modal progress stalled |
|
||||
| `incomplete` | Ran out of modal steps without submitting |
|
||||
| `skipped_no_apply` | No apply button found on page |
|
||||
| `no_modal` | Easy Apply button found but modal didn't open |
|
||||
| `stuck` | Modal progress stalled after repeated clicks |
|
||||
| `incomplete` | Modal flow didn't reach submit |
|
||||
|
||||
## ATS support
|
||||
|
||||
| Platform | Status |
|
||||
|---|---|
|
||||
| LinkedIn Easy Apply | Full |
|
||||
| Wellfound | Full |
|
||||
| Greenhouse | Stub |
|
||||
| Lever | Stub |
|
||||
| Workday | Stub |
|
||||
| Ashby | Stub |
|
||||
| Jobvite | Stub |
|
||||
|
||||
External ATS jobs are queued and classified — stubs will be promoted to full implementations based on usage data.
|
||||
|
||||
## Roadmap
|
||||
|
||||
- [x] LinkedIn Easy Apply
|
||||
- [x] LinkedIn Easy Apply (multi-step modal)
|
||||
- [x] Wellfound apply
|
||||
- [x] Kernel stealth browsers + residential proxy
|
||||
- [x] Self-learning answer bank
|
||||
- [x] Retry logic for transient failures
|
||||
- [x] AI job filtering via Anthropic Batch API
|
||||
- [x] Self-learning answer bank with Telegram Q&A loop
|
||||
- [x] AI-suggested answers via Claude
|
||||
- [x] Telegram answer polling (instant save + applier safety net)
|
||||
- [x] Browser crash recovery
|
||||
- [x] Retry logic with configurable max retries
|
||||
- [x] Preview mode (`--preview`)
|
||||
- [x] Configurable application caps and retry limits
|
||||
- [ ] Indeed support
|
||||
- [ ] External ATS support (Greenhouse, Lever, Workday, Ashby, Jobvite — stubs ready)
|
||||
- [ ] Job scoring and ranking
|
||||
- [ ] External ATS support (Greenhouse, Lever, Workday, Ashby, Jobvite)
|
||||
- [ ] Per-job cover letter generation via LLM
|
||||
- [ ] Indeed support
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Reference in New Issue
Block a user