add-api-route
Add a new JobPilot API route or module the standard way - contracts schema, controller, service, response schema, optional web hook. Use for "add an endpoint",…
Parse a resume's uploaded PDF into structured JSON (basics, experience, projects, skills, education) and save it to the editor.
$ npx -y skills add suxrobGM/jobpilot --skill extract-resume --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/extract-resumeContext preview
The summary Claude sees to decide when to auto-load this skill.
Parse a resume's uploaded PDF into structured JSON (basics, experience, projects, skills, education) and save it to the editor.
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 `../_shared/setup.md`. The profile response provides `user.primaryResumeId`, `primaryResumeSourceAbsolutePath`, and `resumes` (every base with `id`, `label`, `sourceFilename`, `hasData`, `isPrimary`).
Parse the argument:
> No primary resume set. Pass an explicit id, or set a primary at <$JOBPILOT_WEB/resumes>.
Let `RESUME_ID` be the resolved id, `FORCE` be `true`/`false`.
curl -fsS -H "authorization: Bearer $JOBPILOT_API_TOKEN" "$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 <$JOBPILOT_WEB/resumes/{id}>, then re-run.
Resolve the absolute path:
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 <$JOBPILOT_WEB/resumes/{id}>, or re-run with `--force` to 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")
start?: string, // only if the PDF states it; same format as experience dates
end?: string, // "Present" if ongoing
}>,
skills: Array<{
group: string, // e.g. "Languages"
items: string[],
}>,
education: Array<{
school: string,
degree: string,
start?: string,
end?: string,
details: string[],
}>,
publications: Array<{
title: string,
authors?: string, // the citation's author list, verbatim, et al. included
venue?: string, // journal, conference, or publisher
year?: string, // free-form: "2024", "In press", "Under review"
url?: string,
doi?: string,
}>,
awards: Array<{
title: string,
issuer?: string,
year?: string,
description?: string,
}>,
certifications: Array<{
name: string,
issuer?: string,
issued?: string,
expires?: string,
credentialId?: string,
url?: string,
}>,
sections: Array<{ // anything above doesn't model - see the rule below
title: string, // the PDF's own heading, verbatim ("Grants", "Invited Talks")
entries: Array<{
heading: string, // the entry's first line - a grant name, a talk title
subheading?: string, // funder, host, venue
meta?: string, // year, amount, or other right-aligned detail
bullets: string[],
}>,
}>,
}Hard rules:
`tailor-resume` promote a project onto the timeline, so a guess becomes a fabricated range.
becomes a `sections[]` entry under the PDF's own heading: grants and funding, invited talks and presentations, patents, teaching, academic service, professional memberships, volunteering, languages. An academic CV is mostly these, and dropping them silently guts the resume.
`title`, `venue` and `year` only where the split is unambiguous; when it is not, put the whole citation in `title` rather than guessing where the venue ends.
The PUT body must be `{ "content": <resume-object> }` - the API rejects a bare resume payload with 400 "label or content
An AI agent that applies to jobs for you, on the Claude or Codex subscription you already have.
Repo: suxrobGM/jobpilot
Add a new JobPilot API route or module the standard way - contracts schema, controller, service, response schema, optional web hook. Use for "add an endpoint",…
Review and clean up the given file(s)/folder(s)/module(s): rate organization, find dead code, duplication, coupling, over-engineering, deep nesting, structural…
Create and apply a Prisma migration safely against the remote (tunneled) PostgreSQL. Use for schema changes - "create a migration", "apply migrations", "add a…
Add knip to a TypeScript project and use it to remove dead exports, collapse pass-through barrels, and narrow every export to what another file actually…
Bump the unified JobPilot version (host + plugin), update the changelog, commit, and tag a new release
Rebuild the .NET terminal host, restart it, and wait for /healthz. Use after any C# change under apps/terminal, or when asked to "restart the terminal host".