VITA — CV as Code
One base CV. Infinite targeted variants. Zero duplicates.
vita-cvis a CLI tool that treats your resume like source code — versioned with git, branched per company, built from LaTeX, and sharpened with AI assistance.
VITA can generate targeted AI prompts or run an autonomous AI workflow to analyze job descriptions, identify gaps, improve keywords, and adapt your CV title, summary, skills, and experience for each application.
uv tool install vita-cv
How It Works
VITA manages your CV using git branches:
| Branch | Purpose |
|---|---|
master |
Your canonical base — the CV you're always proud of |
gen-<role> |
Domain-specific base (e.g. gen-swe, gen-ml) |
etp-<company>-<role> |
Tailored CV for a specific application (e.g. etp-google-swe) |
Every time you apply somewhere, you create a branch. VITA tracks that in a registry so you never accidentally send the wrong version or apply twice.
Prerequisites
- uv — install (
pip install uvorcurl -LsSf https://astral.sh/uv/install.sh | sh) - Python 3.10+ — managed automatically by uv
- Git (must be in PATH)
- LaTeX — TeX Live or MiKTeX, with
latexmk
Quick Start
# 1. Install vita from PyPI as a persistent tool
uv tool install vita-cv
# → vita is now available globally in your terminal
# 2. Create your CV project directory
mkdir my-cv && cd my-cv
# 3. Initialize VITA (creates .vita/ and git repo)
vita init
# 4. Edit .vita/config.json — set your name
# "author": "yourname"
# 5. Create your LaTeX CV (see below)
# ... write main.tex, sections/, etc.
# 6. Commit your base CV
git add .
git commit -m "base: initial CV"
# 7. Apply to a company
vita new etp google "software engineer"
# → creates branch etp-google-swe, updates registry
# 8. Tailor your CV for this job, then build
vita build
Your CV Project Structure
After running vita init, your project should look like this:
my-cv/
├── main.tex ← LaTeX entry point (you create this)
├── sections/ ← Modular CV sections (you create these)
│ ├── experience.tex
│ ├── education.tex
│ ├── skills.tex
│ └── ...
├── job.md ← Paste job descriptions here (for AI prompts)
├── out/ ← PDF output (auto-created on build)
│
└── .vita/ ← Auto-created by `vita init`
├── config.json ← Your settings (author, output paths, etc.)
├── companies.json ← Registry of all companies you've applied to
├── extensions.json ← Your custom role aliases, languages, and LLM providers
├── .env ← Local API keys for --auto mode
├── .env.example ← Example API key file
└── logs/ ← CLI activity log
.vita/is gitignored — your registry and config stay local.
Files You Need to Create
VITA manages the .vita/ directory for you. You are responsible for the LaTeX files.
main.tex — Entry Point
Your main LaTeX file. VITA will compile this with latexmk. Example minimal structure:
\documentclass{article}
\begin{document}
\input{sections/experience}
\input{sections/education}
\input{sections/skills}
\end{document}
You can use any LaTeX CV class (e.g. moderncv, altacv, awesome-cv).
sections/ — Modular Content
Split your CV into files so each branch only touches relevant sections:
sections/
├── experience.tex ← Work history (most frequently changed per company)
├── education.tex ← Education
├── skills.tex ← Skills (adjust keywords per role)
├── summary.tex ← Optional: tailored headline
└── projects.tex ← Optional
job.md — Job Description (for AI prompts)
When you run vita adapt or vita analyze, VITA looks for job.md in your project root. Paste the job posting there:
Company: Google
Role: Senior Software Engineer
[Paste full job description here]
Configuration Reference
vita init creates .vita/config.json with these defaults:
{
"author": "",
"output_dir": "out",
"output_filename": "cv-{author}.pdf",
"tex_entry": "main.tex",
"build_mode": "auto",
"default_base_branch": "master",
"strict_mode": false,
"allow_multiple_per_company": true,
"warn_on_duplicate": true,
"auto_add_codex_provider": false
}
| Key | What it does |
|---|---|
author |
Your name — used in the PDF filename |
output_dir |
Where the compiled PDF goes (out/ by default) |
output_filename |
PDF name template. {author} is substituted automatically |
tex_entry |
Your LaTeX entry file (default: main.tex) |
build_mode |
Reserved build mode setting (auto by default) |
default_base_branch |
The branch vita new checks out from if no base exists |
strict_mode |
Reserved stricter workflow setting (false by default) |
allow_multiple_per_company |
Reserved duplicate-policy setting |
warn_on_duplicate |
If true, warns (but doesn't block) on duplicate company |
auto_add_codex_provider |
If true, automatically adds a missing Codex provider to extensions.json without prompting |
CLI Commands
vita init
Initialize VITA in the current directory. Creates .vita/, scaffolds config files, and runs git init.
vita init # first-time setup
vita init --force # wipe and re-initialize
vita new etp <company> <role>
Create a new tailored CV branch for a job application.
vita new etp google "software engineer"
vita new etp openai ml
vita new etp stripe "backend engineer" --commit # commit pending changes first
- Normalizes the role (
software engineer→swe) - Creates branch
etp-<company>-<role>off your base branch - Registers the company in
.vita/companies.json - Warns if you've already applied to this company
vita status
See all companies, branches, and where you currently are.
vita status
VITA Status — yourname
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Current branch : etp-google-swe
Companies (2):
google [2 CVs]
├── etp-google-swe base: gen-swe ← you are here
└── etp-google-ml base: gen-ml
meta [1 CV] [LOCKED]
└── etp-meta-swe base: gen-swe
Base branches:
gen-swe · gen-ml
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
vita sync
Reconcile the .vita directory and registry with your actual git branches. Useful if you created or deleted branches manually, or if local .vita files such as config.json, extensions.json, .env, or generated artifact directories are missing.
vita sync # interactive
vita sync --dry-run # preview only, no changes written
vita sync --auto # apply all changes without prompting
vita save
Stage all changes, commit them, and push to the remote repository.
vita save
vita save -m "update Google CV"
vita save -r
-m/--messagesets a custom commit message.- Without
-m, VITA uses a generic commit message. -r/--reconcilesaves the current branch, checks outmaster, runsvita sync --auto, then savesmaster.
vita build
Compile your LaTeX CV to PDF using latexmk.
vita build
Output goes to out/cv-<author>.pdf (or whatever you configured).
vita diff <company>
Show what changed in a company branch versus its base.
vita diff google
vita lock / unlock <company>
Lock a company to prevent creating new CVs for it (e.g. you've accepted an offer).
vita lock amazon
vita unlock amazon
vita analyze
Generate an AI prompt to analyze your CV against the job in job.md.
vita analyze
vita analyze --auto
vita analyze --auto --provider codex
Without --auto, VITA saves the prompt to .vita/current_prompt.md; open it and give it to your AI assistant.
With --auto, VITA sends the prompt and local context to your configured LLM provider and saves the report to .vita/results/analyze.md.
job.md can contain one role, or multiple roles from the same company using # Job N sections:
# Job 1
Software Engineer, Infrastructure
# Job 2
Research Engineer, GenAI
When multiple jobs are detected, vita analyze --auto and vita run --auto produce one unified cross-role analysis with per-job scores and an overall match probability.
vita adapt [--language <code>]
Generate an AI prompt to tailor your CV to the job in job.md.
vita adapt # English (default)
vita adapt --language fr # French
vita adapt --language ar # Arabic
vita adapt --auto --provider gemini
VITA adapts the CV title/headline, professional summary, experience, and skills based on the job description. For multi-job job.md files, it creates one coherent CV optimized for shared requirements across all listed roles instead of separate CV versions.
The CV title uses one role name, such as Applied AI Engineer, Data Engineer, Software Engineer, or ML Engineer. Specializations and keywords belong in the summary or skills, without a tagline appended to the title. For multiple jobs, VITA selects the single role that best fits the shared requirements and your experience.
vita review
Generate an AI prompt to review and improve your CV in general.
vita review
vita review --auto
vita run
Run the full AI workflow: analyze → adapt → review.
vita run
vita run --auto
vita run --auto --provider codex
vita run --auto --provider gemini --language fr
Without --auto, this generates prompts. With --auto, VITA calls the configured LLM provider and writes outputs into .vita/results/, updating main.tex during the adapt step.
vita keys
Manage API keys for LLM providers.
vita keys list
vita keys set gemini <your-key>
vita keys remove gemini
Keys are stored locally in .vita/.env. Codex CLI support uses your local Codex login instead of an API key.
Typical Workflow
# Day 1 — Set up your CV project
uv tool install vita-cv
mkdir my-cv && cd my-cv
vita init
# → edit .vita/config.json (set author)
# → write main.tex and sections/
git add .
git commit -m "base: initial CV"
# Applying to a company
git checkout -b gen-swe # create your domain baseline
# → tailor the CV for SWE roles generally
git add . && git commit -m "gen-swe: baseline"
vita new etp google "software engineer"
# → now on branch etp-google-swe
# Paste job description
echo "..." > job.md
vita analyze # get analysis prompt
vita adapt # get tailoring prompt
# → run prompts with AI, apply suggestions to sections/
# Or run the autonomous pipeline with a configured provider
vita run --auto --provider codex
vita build # compile to PDF
# → out/cv-yourname.pdf
vita status # review all companies
Building the PDF
VITA uses latexmk automatically. If you need to build manually:
# Standard
latexmk -pdf -outdir=out main.tex
# With bibliography (BibTeX/Biber)
pdflatex -output-directory=out main.tex
biber --input-directory=out --output-directory=out main
pdflatex -output-directory=out main.tex
pdflatex -output-directory=out main.tex
Extensibility
VITA ships with built-in role aliases, language codes, and LLM provider support. You can add your own in .vita/extensions.json (created by vita init):
{
"llm_providers": {
"codex": {
"model": ""
},
"gemini": {
"model": "gemini-2.0-flash"
}
},
"role_aliases": {
"research engineer": "re",
"applied scientist": "as",
"solutions architect": "sa"
},
"language_map": {
"zh": "Chinese (Simplified)",
"ja": "Japanese"
}
}
Your entries are merged on top of the built-ins — your values win on conflict. Use --provider with --auto commands to choose a configured LLM provider.
See EXTENSIONS.md for the full list of built-in aliases and all format details.
CI/CD and automated releases
GitHub Actions runs linting, dependency auditing, package validation, CodeQL, regression tests on Python 3.10 through 3.14, and an isolated installed-package smoke test. The full pipeline also runs every Monday at 05:23 UTC. Dependabot checks Python and GitHub Actions dependencies every Monday.
Release Please manages versions and release notes from Conventional Commit pull request titles. It keeps a release pull request up to date, including vita.__version__ and CHANGELOG.md. Merging that tested release pull request creates the version tag and GitHub Release. The tag starts the publishing workflow, which verifies that the tag matches vita.__version__, requires the tagged commit to be on master, runs every release check, builds the package once, publishes it to TestPyPI, installs and verifies that TestPyPI package, and only then publishes the same files to PyPI.
Use these pull request title prefixes to select the next version automatically:
fix: ...creates a patch release, such as2.3.0to2.3.1.feat: ...creates a minor release, such as2.3.0to2.4.0.feat!: ...,fix!: ..., or aBREAKING CHANGE:footer creates a major release.chore: ...,ci: ..., andtest: ...normally do not create a release.
Use a scope when useful, for example fix(cli): handle a missing config file. CI rejects pull request titles that are not Conventional Commits. To select an exact exceptional version, add Release-As: X.Y.Z to the squash commit body.
Add the publishing tokens to GitHub
Never commit either token to this repository. Store each token as a GitHub environment secret:
- Open the GitHub repository and go to Settings → Environments.
- Create an environment named
testpypi. Under Environment secrets, addTEST_PYPI_API_TOKENwith a project-scoped token created at TestPyPI account settings. - Create an environment named
pypi. Under Environment secrets, addPYPI_API_TOKENwith a project-scoped token created at PyPI account settings. - Restrict both environments to tags matching
v*. For the strongest production control, enable Required reviewers on thepypienvironment so production publishing waits for approval after TestPyPI verification. Leave required reviewers disabled if releases must be completely unattended.
The token value includes the pypi- prefix. The workflow supplies the required __token__ username automatically.
Add the release automation token to GitHub
Release Please needs a fine-grained GitHub personal access token so that its pull requests run the required CI checks and its version tags start the publishing workflow. In GitHub, open Settings → Secrets and variables → Actions, create a repository secret named RELEASE_PLEASE_TOKEN, and paste a fine-grained token with access only to this repository. Grant it Contents: Read and write, Pull requests: Read and write, and Issues: Read and write. Keep the token owner authorized to create tags and pull requests in this repository.
After the three secrets are configured, normal development and releases use this flow:
- Merge a pull request with a Conventional Commit title into
master. - Release Please creates or updates the release pull request with the calculated version and release notes.
- Review and merge the release pull request after its required checks pass.
- Release Please creates the GitHub Release and
vX.Y.Ztag. - GitHub Actions publishes to TestPyPI, verifies the uploaded wheel, and publishes the identical files to PyPI.
AI Skills
VITA includes a library of AI agent skills for specialized tasks. Reference them when prompting your AI assistant:
| Category | Skills |
|---|---|
| Writing | resume-writer, resume-bullet-writer, resume-quantifier |
| Reviewing | resume-reviewer, resume-ats-optimizer |
| Targeting | resume-tailor, job-description-analyzer, resume-section-builder |
| Documents | cover-letter-generator, linkedin-profile-optimizer |
| Prep | interview-prep-generator, salary-negotiation-prep, offer-comparison-analyzer |
| Specialized | tech-resume-optimizer, executive-resume-writer, academic-cv-builder |
| Transitions | career-changer-translator, creative-portfolio-resume |
Example usage:
"Use the
resume-tailorskill to updatesections/experience.texbased on the job description I pasted injob.md."
"Run
interview-prep-generatoron my CV and generate 5 STAR stories for a Google interview."
Design Philosophy
- Warn, don't block — duplicate CVs trigger a warning, not a hard stop (
--forceoverrides,locked: trueblocks permanently) - CLI enforces naming — roles are normalized automatically (
data engineer→de,machine learning engineer→ml) - Registry is the truth —
.vita/companies.jsonis the authoritative record; usevita syncif it drifts from git - User-extensible — role aliases and language codes are fully overridable via
.vita/extensions.json - Local-first — prompt generation, git workflows, and LaTeX builds are local.
--autosends CV/job context to the LLM provider you configure.
Metadata
Release files for vita-cv 2.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| vita_cv-2.3.0.tar.gz | 117.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vita_cv-2.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 249.3 kB
Release files / vita_cv-2.3.0.tar.gz
| Download URL | vita_cv-2.3.0.tar.gz |
|---|---|
| Size | 117.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c5995567d8884b96bcddf5b93a9f9dff0e19b6a92aa9d10d0b0a07441c4c6a70
|
|
BLAKE2b-256 checksum How to use checksums |
7c42ac23cdf680d0c59f67cf1cf8d9eb94157b4821be2642c4aeb8ff80144e45
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / vita_cv-2.3.0-py3-none-any.whl
| Download URL | vita_cv-2.3.0-py3-none-any.whl |
|---|---|
| Size | 131.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
00b76c17fe1f5fc5b0ee575beb4ede4a8f703c7043ae5f11d9d06f9a376dac13
|
|
BLAKE2b-256 checksum How to use checksums |
993df264fa080c9fac9b6944882f6fc649d38ec143305c4b255d00022aee6036
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for vita-cv 2.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.