From jobpilot
Autonomously searches job boards, scores positions against resume, presents batch for approval, then applies without further prompts. Tracks progress in JSON for resumability.
npx claudepluginhub suxrobgm/jobpilotThis skill uses the workspace's default tool permissions.
You autonomously search job boards, score results against the user's resume, present a batch for one-time approval, then apply to every approved job without further confirmation. Progress is tracked in a JSON file so runs can be resumed if interrupted.
Automates filling job applications on Greenhouse, Lever, and Workday via browser automation with resume, cover letter, and personal data. Handles job URLs, recent jobs, or current tab.
Automates job search via JobGPT MCP server: search filtered jobs, auto-apply, generate tailored resumes, track applications, salary intelligence, recruiter outreach.
Automates job searches and applications on LinkedIn, Indeed, Glassdoor, ZipRecruiter, Wellfound. Generates cover letters, fills forms, tracks status. Use for 'find and apply to jobs'.
Share bugs, ideas, or general feedback.
You autonomously search job boards, score results against the user's resume, present a batch for one-time approval, then apply to every approved job without further confirmation. Progress is tracked in a JSON file so runs can be resumed if interrupted.
Read and follow the instructions in ${CLAUDE_PLUGIN_ROOT}/skills/_shared/setup.md to load the profile, resume, and credentials.
Read the autopilot section from profile.json. Apply these defaults for any missing fields:
| Setting | Default | Description |
|---|---|---|
minMatchScore | 6 | Minimum score (1-10) to qualify for application |
maxApplicationsPerRun | 10 | Max jobs to apply to in one run |
skipCompanies | [] | Company names to skip |
skipTitleKeywords | [] | Title keywords to skip (e.g., "intern", "principal") |
confirmMode | "batch" | "batch" = review and approve the list before applying. "auto" = skip confirmation and apply immediately when ALL qualified jobs score >= minMatchScore. If any job scores below minMatchScore, falls back to batch confirmation. |
minSalary | 0 | Minimum annual salary (USD). Skip jobs that list compensation below this. 0 = no filter. |
maxSalary | 0 | Maximum annual salary (USD). Skip jobs above this. 0 = no filter. |
salaryExpectation | "" | Auto-fill salary expectation fields (e.g., "$100,001 to $125,000"). If empty, asks the user on first encounter. |
defaultStartDate | "2 weeks notice" | Default answer for start date fields |
Inline argument overrides take precedence. Examples:
/jobpilot:autopilot "senior fullstack React remote" --min-score 7 --max-apps 5/jobpilot:autopilot "senior fullstack React remote" (uses profile.json defaults)Parse the argument to decide the run mode:
"resume" -> list incomplete runs from ${CLAUDE_PLUGIN_ROOT}/runs/, ask the user to pick one, then skip to Phase 3 (apply loop) with remaining approved or pending jobs."retry-failed <run-id>" -> load the specified run, reset all failed jobs to approved, then skip to Phase 3. Before retrying each job, read its retryNotes field (if set) to understand what went wrong last time and try a different approach (e.g., if the note says "Quick Apply button led to a broken iframe", try navigating to the company's careers page directly instead).Check ${CLAUDE_PLUGIN_ROOT}/runs/ for any file with status: "in_progress" whose query matches (or is very similar to) the current search query.
If found, ask the user: "Found an incomplete run from [startedAt] with [remaining] jobs left. Resume it or start fresh?"
Create a new run file at ${CLAUDE_PLUGIN_ROOT}/runs/<run-id>.json where <run-id> is formatted as YYYY-MM-DDTHH-MM-SS_<slugified-query> (e.g., 2026-03-22T14-30-00_senior-fullstack-developer).
Initialize with:
{
"runId": "<run-id>",
"query": "<user's search query>",
"config": {
"minMatchScore": <resolved value>,
"maxApplications": <resolved value>,
"boards": ["<enabled boards>"]
},
"status": "in_progress",
"startedAt": "<ISO timestamp>",
"updatedAt": "<ISO timestamp>",
"completedAt": null,
"jobs": [],
"summary": {
"totalFound": 0,
"qualified": 0,
"applied": 0,
"failed": 0,
"skipped": 0,
"remaining": 0
}
}
Extract from the user's query:
If the query is vague, ask the user to clarify before searching.
Read the jobBoards array from profile.json. Only search boards where enabled: true and type: "search". Boards with type: "ats" (e.g., Greenhouse, Lever, Workday) are apply-only platforms -- skip them during search.
For each searchable board:
browser_navigate to go to the board's searchUrl (defined in the board entry).${CLAUDE_PLUGIN_ROOT}/skills/_shared/auth.md to log in proactively.searchUrl if needed, then proceed to search.browser_snapshot to read results.status: "pending".Handle rate limiting gracefully -- if a board blocks or throttles, note it and move to the next board.
Cross-board deduplication: Remove duplicate jobs across boards. A duplicate = same company name AND same or very similar job title. Keep the entry with the richer description.
Previously applied filter: Before scoring, check each job URL against the persistent applied-jobs database:
bash ${CLAUDE_PLUGIN_ROOT}/scripts/check-applied.sh "<job-url>"
If the script outputs already-applied (exit code 0), mark the job as status: "skipped" with skipReason: "Already applied (found in applied-jobs database)" and exclude it from scoring and confirmation.
For each job, assign a match score (1-10) based on:
Write scores and matchReason to the progress file.
Filter out:
minMatchScore -> set status: "skipped", skipReason: "Below minimum match score (X < Y)"skipCompanies -> set status: "skipped", skipReason: "Company in skip list"skipTitleKeywords -> set status: "skipped", skipReason: "Title contains blocked keyword: <keyword>"minSalary (if > 0) -> set status: "skipped", skipReason: "Salary below minimum ($X < $Y)"maxSalary (if > 0) -> set status: "skipped", skipReason: "Salary above maximum ($X > $Y)"Note on salary filtering: Only filter if the job listing explicitly shows a salary range in the preview. Do not skip jobs that don't mention salary -- many good jobs omit compensation from listings.
Update the summary counts in the progress file.
confirmMode: "auto")If confirmMode is "auto" AND every qualified job has a match score >= minMatchScore:
status: "approved" automatically.If any qualified job scores below minMatchScore, fall back to batch mode regardless of the confirmMode setting. This ensures borderline matches always get human review.
confirmMode: "batch", or auto mode fallback)Present all qualified jobs (score >= minMatchScore, not filtered) in a ranked table:
## Autopilot Run: "<query>"
Found <totalFound> jobs across <N> boards. <qualified> qualify (score >= <minMatchScore>/10).
| # | Score | Title | Company | Location | Board |
|---|-------|-------|---------|----------|-------|
| 1 | 9/10 | Senior Full Stack Dev | Acme Corp | Remote | LinkedIn |
| 2 | 8/10 | Full Stack Engineer | StartupCo | Portland, ME | Indeed |
| ... |
Applying to up to <maxApplications> jobs.
**Commands:**
- "go" -- apply to all qualified jobs
- "go 1,3,5" -- apply only to specific jobs
- "remove 3,7" -- exclude specific jobs
- "details 2" -- show full job description including job URL before deciding
- "stop" -- cancel the run
This is the single confirmation gate. After the user says "go", apply to all approved jobs autonomously without asking again per-job.
Process the user's response:
status: "approved"approved, mark the rest as skipped with skipReason: "Not selected by user"skipped with skipReason: "Removed by user", then re-present the tablestatus: "paused", save, and stopUpdate the progress file after processing the response.
For each job with status: "approved", in order of match score (highest first):
"applying" in the progress file.browser_navigate to open the job URL.browser_snapshot to assess the page.Determine the page type:
After clicking Apply, use browser_wait_for for page load, then browser_snapshot to reassess.
Read and follow the instructions in ${CLAUDE_PLUGIN_ROOT}/skills/_shared/auth.md to log in.
Additional autopilot rules for auth failures:
failed with failReason: "Login failed for <domain>". Mark ALL remaining approved jobs on the same domain as failed with failReason: "Login failed for <domain> -- skipped after earlier failure". Continue to the next job on a different domain.Read and follow the instructions in ${CLAUDE_PLUGIN_ROOT}/skills/_shared/form-filling.md.
Autopilot-specific overrides:
autopilot.defaultStartDate from config (default: "2 weeks notice").In autonomous mode, submit the application without waiting for per-job confirmation. The user already approved the batch in Phase 2.
browser_wait_for to confirm submission.On success:
"applied".appliedAt to the current ISO timestamp.summary.applied count.bash ${CLAUDE_PLUGIN_ROOT}/scripts/log-applied.sh "<job-url>" "<title>" "<company>" "autopilot" "<run-id>"
On failure (any of these: CAPTCHA, unexpected page state, form error, submission error, page crash):
"failed".failReason to a clear description (e.g., "CAPTCHA required", "Unexpected page: saw pricing page instead of form", "Form validation error: missing required field 'Portfolio URL'").retryNotes with actionable context for a future retry attempt. Describe what was tried and suggest an alternative approach. Examples:
"Quick Apply opened a broken iframe. Try navigating to the company careers page directly: https://company.com/careers""Form required a Portfolio URL field not in profile. User should add it to profile.json before retrying.""Login succeeded but application page returned 403. May need different credentials or direct application URL."summary.failed count.After each application:
summary.remaining count.summary.applied >= config.maxApplications, mark all remaining approved jobs as skipped with skipReason: "Max applications limit reached". End the loop.After every status change, use the update script to modify the progress file without reading the full JSON into context. This saves tokens on large run files.
# Update a job's status
bash ${CLAUDE_PLUGIN_ROOT}/scripts/update-run.sh <run-file> job <job-id> status applied
bash ${CLAUDE_PLUGIN_ROOT}/scripts/update-run.sh <run-file> job <job-id> appliedAt "2026-03-22T14:00:00Z"
bash ${CLAUDE_PLUGIN_ROOT}/scripts/update-run.sh <run-file> job <job-id> failReason "CAPTCHA required"
bash ${CLAUDE_PLUGIN_ROOT}/scripts/update-run.sh <run-file> job <job-id> retryNotes "Try direct careers page"
# Recalculate summary counts
bash ${CLAUDE_PLUGIN_ROOT}/scripts/update-run.sh <run-file> summary
# Update run status
bash ${CLAUDE_PLUGIN_ROOT}/scripts/update-run.sh <run-file> status completed
Do NOT read the full progress file to update it. Always use the script. This ensures the run can be resumed from the exact point of interruption without wasting context tokens.
After all jobs are processed (or the limit is reached):
status: "completed" and completedAt to current ISO timestamp.## Autopilot Run Complete: "<query>"
| Metric | Count |
|--------|-------|
| Jobs found | <totalFound> |
| Qualified | <qualified> |
| Applied | <applied> |
| Failed | <failed> |
| Skipped | <skipped> |
### Successfully Applied
- #1 Senior Full Stack Dev at Acme Corp (9/10)
- #2 Full Stack Engineer at StartupCo (8/10)
- ...
### Failed (can retry)
- #4 Backend Dev at BigCo -- CAPTCHA required on application form
- ...
### Skipped
- #6 Junior Dev at SmallCo -- Below minimum match score (4 < 6)
- ...
Progress saved to: runs/<run-id>.json
**Next steps:**
- "retry-failed <run-id>" to retry failed applications
- Start a new search with a different query
failed with failReason: "Payment required" and continue.auth.md). These are typically one-time per board -- once resolved, remaining jobs on that board proceed without interruption. Only mark a job as failed if the user explicitly says to skip it, or if the CAPTCHA appears mid-application (not during login).browser_wait_for with a brief timeout.personal.resumes.default, STOP the entire run and ask the user to fix it. Save the run as paused so it can be resumed.Read and follow ${CLAUDE_PLUGIN_ROOT}/skills/_shared/browser-tips.md for handling large pages, popups, and general browser best practices.