con un clic
extract-resume
// Parse a resume's uploaded PDF into structured JSON (basics, experience, projects, skills, education) and save it to the editor.
// Parse a resume's uploaded PDF into structured JSON (basics, experience, projects, skills, education) and save it to the editor.
Apply to a single job (URL or pasted page) with fit review, or drain the pending queue when no argument is given.
Search a job board and autonomously apply to matching jobs one at a time, until paused, exhausted, or the max-applications cap is hit.
Migrate a React @tanstack/react-form codebase from the prop-drilled `useForm` + erased-form-type pattern to the official `createFormHook` composition API (`useAppForm` / `withForm` / `field.X`). Use when a project threads a `form` object (often cast to an `any`-erased type like `ReactFormExtendedApi<any,...>`) through field-wrapper components that take `form`+`name` props, and you want typed field names/values, no casts, and reusable bound field components. Triggers: "migrate forms to createFormHook", "adopt useAppForm/withForm", "remove AnyReactForm cast", "type-safe tanstack form fields".
Re-score a run's skipped jobs and promote the eligible ones to `approved` for later applying. Recovers jobs wrongly dropped for location, a sparse JD, 1099, or seniority. Does not apply.
Resume an interrupted or paused JobPilot run by id. Re-flips the run to in_progress and replays the apply loop on any remaining approved jobs without re-asking for fit confirmation.
Search a chosen job board via Playwright, rank results by fit against the user's resume, and save them to the run so the user can review.
| name | extract-resume |
| description | Parse a resume's uploaded PDF into structured JSON (basics, experience, projects, skills, education) and save it to the editor. |
| argument-hint | [resume-id] [--force] |
Read a resume's uploaded source PDF and produce JSON matching the JobPilot resume schema, then save via the API. Inverse of the editor.
Follow plugin/skills/shared/setup.md. The profile response provides data.profile.primaryResumeId, data.primaryResumeSourceAbsolutePath, and data.resumes (every base with id, label, sourceFilename, hasData, isPrimary).
Parse the argument:
data.profile.primaryResumeId. If no primary, stop:
No primary resume set. Pass an explicit id, or set a primary at http://localhost:8000/resumes.
--force (anywhere) → overwrite existing structured data. Otherwise refuse to overwrite (Step 3).Let RESUME_ID be the resolved id, FORCE be true/false.
curl -fsS "$JOBPILOT_API/api/resumes/$RESUME_ID"
If 404, stop and report the id doesn't exist.
sourceFilename must be set. If null, stop:
Resume {id} ({label}) has no uploaded source PDF. Upload one at http://localhost:8000/resumes/{id}, then re-run.
Resolve the absolute path:
data.primaryResumeSourceAbsolutePath.${JOBPILOT_WORKSPACE_ROOT}/src/web/storage/resumes/{sourceFilename}.If sourceMimeType !== "application/pdf", stop and ask the user to re-upload as PDF.
If content is non-null and FORCE === false, stop:
Resume {id} ({label}) already has structured data (version {n}). Edit at http://localhost:8000/resumes/{id}, or re-run with
--forceto overwrite from the PDF.
If FORCE, proceed and overwrite.
Read the PDF at the path from Step 2. Produce a single JSON object matching:
{
basics: {
name: string, // required
headline?: string, // professional title/headline if present
email?: string,
phone?: string,
website?: string,
linkedin?: string,
github?: string,
location?: string,
},
summary?: string, // 1–3 sentences
experience: Array<{
company: string,
title: string,
location?: string,
start: string, // free-form, e.g. "Jul 2022"
end?: string, // omit or "Present" if current
bullets: string[],
}>,
projects: Array<{
name: string,
url?: string,
description?: string, // one prose line; omit if there's only a tech-stack line
bullets: string[],
keywords: string[], // the tech-stack line (e.g. "Next.js, Prisma, Docker")
}>,
skills: Array<{
group: string, // e.g. "Languages"
items: string[],
}>,
education: Array<{
school: string,
degree: string,
start?: string,
end?: string,
details: string[],
}>,
}
Hard rules:
[] (or omit optional field).end: "Present" (or omit)."Skills".keywords only — never copy it into description.Read with pages to ingest all pages — don't silently drop later-page entries.The PUT body must be { "content": <resume-object> } — the API rejects a bare resume payload with 400 "label or content required". Write the file with that wrapper, then send it:
curl -fsS -X PUT "$JOBPILOT_API/api/resumes/$RESUME_ID" \
-H "Content-Type: application/json" \
--data-binary @resume.json
Where resume.json looks like {"content": {"basics": {...}, "experience": [...], ...}}. On 422, read the issue list, fix the field, retry once.
Extracted resume {id} ({label}) → version {n}. Review at http://localhost:8000/resumes/{id}.
Do not echo the parsed fields — the editor and preview show them.