Metadata-Version: 2.5
Name: honest-cv
Version: 0.2.0
Summary: Honest, ATS-safe, per-role CVs generated by your AI agent (MCP server + CLI + Claude Code skill).
Project-URL: Homepage, https://github.com/JesusCabreraReveles/honest-cv
Project-URL: Repository, https://github.com/JesusCabreraReveles/honest-cv
Project-URL: Issues, https://github.com/JesusCabreraReveles/honest-cv/issues
Project-URL: Changelog, https://github.com/JesusCabreraReveles/honest-cv/blob/master/CHANGELOG.md
Author: Jesus Cabrera
License-Expression: MIT
License-File: LICENSE
Keywords: ats,claude,cv,mcp,resume
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: End Users/Desktop
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business
Classifier: Topic :: Text Processing :: Markup
Requires-Python: >=3.11
Requires-Dist: mcp<3,>=2.3
Requires-Dist: pdfminer-six>=20231228
Requires-Dist: pydantic>=2.6
Requires-Dist: pypdf>=4.0
Requires-Dist: python-docx>=1.1
Requires-Dist: pyyaml>=6.0
Requires-Dist: typst>=0.15
Description-Content-Type: text/markdown

# honest-cv

[![CI](https://github.com/JesusCabreraReveles/honest-cv/actions/workflows/ci.yml/badge.svg)](https://github.com/JesusCabreraReveles/honest-cv/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/JesusCabreraReveles/honest-cv/blob/master/LICENSE)

<!-- mcp-name: io.github.JesusCabreraReveles/honest-cv -->

**Honest, ATS-safe CVs tailored to each role, generated by your AI agent.** honest-cv is an MCP
server, a CLI and a Claude Code skill. It keeps one source of truth about your experience and
turns it into a PDF and DOCX for each type of role. It never claims a skill you don't have.

[Leer en español](https://github.com/JesusCabreraReveles/honest-cv/blob/master/README.es.md)

![Demo: skill checklists including your own, scoring a job post, a verified build, a cover letter that refuses a skill the person lacks, LinkedIn texts and logging the application](https://raw.githubusercontent.com/JesusCabreraReveles/honest-cv/master/docs/assets/demo.gif)

## Why

Many CVs that get no callbacks don't fail on design. They fail on targeting, and on details that
quietly break text extraction. AI makes the opposite problem easy: a model will happily stuff a CV
with keywords you can't defend in an interview. honest-cv is built around three ideas:

- **The code enforces honesty, not a prompt.** Every skill has a level. A skill at familiarity
  level is always labeled as such, and a skill you don't have is never printed. Raising a level
  takes written evidence and *your* confirmation, which the server asks you for directly. The
  model can't confirm on your behalf.
- **Gaps are measured against your own inventory.** For each keyword a job post asks for,
  honest-cv tells you which of three cases it is:
  - you have it, but this CV doesn't show it;
  - you only know it at familiarity level;
  - you don't have it.
- **ATS safety is checked on every build.** Every PDF goes through two text extractors (pypdf and
  pdfminer.six). The build checks that the text is one column, has no ligatures, keeps each date on
  its role's line, follows the right reading order and includes every bullet.

The server is deterministic and needs no API key. Your MCP client's model runs the conversation;
honest-cv validates, builds, verifies and measures. It keeps your profile in a folder on your
machine and never sends it anywhere: see [Privacy](#privacy) for what your AI client does with it.

<p align="center"><img src="https://raw.githubusercontent.com/JesusCabreraReveles/honest-cv/master/docs/assets/ada-platform-en.png" width="560" alt="A generated one-page CV for the fictional Ada Example"></p>

## Quick start

honest-cv needs [uv](https://docs.astral.sh/uv/). Typst and the fonts ship inside the package,
so there is nothing else to install.
It runs on Linux (glibc) on x86_64 and ARM64, macOS on Intel and Apple
Silicon, and Windows on x64. Alpine Linux and Windows on ARM aren't supported, because the `typst`
package doesn't publish wheels for them.

### Claude Code

```bash
claude mcp add honest-cv -- uvx honest-cv serve --profile ~/cv-profile
```

To install the skill as well, which guides the interview and the tailoring:

```bash
mkdir -p ~/.claude/skills/honest-cv
curl -fsSL https://raw.githubusercontent.com/JesusCabreraReveles/honest-cv/master/skills/honest-cv/SKILL.md \
  -o ~/.claude/skills/honest-cv/SKILL.md
```

Then start with *"interview me for my CV"*, or run `/mcp__honest-cv__interview`. You can answer
by voice with your system's dictation (on macOS: System Settings → Keyboard → Dictation): the
interview reads names and numbers back to you before saving them, since dictation mishears them.

> **The first interview is long.** With a long career and many skills it can take several hours.
> You don't have to do it in one sitting: everything is saved as you go, so stop whenever you like
> and say *"let's continue my CV interview"* later. A good first session covers your contact
> details, your two most recent roles and the skills checklist; with that you can already build a
> CV, and older roles can wait.

### Cursor

Add this to `~/.cursor/mcp.json`, or to `.cursor/mcp.json` in a project:

```json
{
  "mcpServers": {
    "honest-cv": {
      "command": "uvx",
      "args": ["honest-cv", "serve", "--profile", "/absolute/path/to/cv-profile"]
    }
  }
}
```

### Claude Desktop

Edit `claude_desktop_config.json`:
- on macOS, `~/Library/Application Support/Claude/`;
- on Windows, `%APPDATA%\Claude\`.

Add the same `mcpServers` block as in the Cursor example and restart the app. If the app can't
find `uvx`, use its absolute path (`which uvx`).

### How each client confirms a skill upgrade

Raising a skill's level needs your own confirmation, which the server asks for through MCP
elicitation. Not every client supports it yet:

| Client | Where you confirm |
|---|---|
| Claude Code | In the chat: a form with one "no / sí" field per upgrade, up to 10 at a time. Only the ones you answer "sí" are saved; Decline saves nothing. |
| Claude Desktop | In a terminal. The server refuses the change and gives you the command to run: `uvx honest-cv skill <profile> "<name>" <level> --evidence "..."`. |
| Other clients | In the chat if they support elicitation, otherwise in a terminal as above. |

## Your profile

A profile is a folder with four YAML files. Create one with `honest-cv init ~/cv-profile`, or let
the `interview` prompt fill it in with you. Keep it out of public repositories.

| File | What it holds |
|---|---|
| `profile.yaml` | Contact details, roles, education, certifications, publications, courses and languages. |
| `bullets.yaml` | A pool of achievements, each with an `id`, a `role` and `tags`. |
| `skills.yaml` | Your skill inventory, with levels and evidence. |
| `variants.yaml` | One CV plan per type of role. A variant can inherit from another with `extends:`. |

Any text can be a single string or one string per language: `{en: ..., es: ...}`. CVs can be
built in English and in Spanish.

| Level | Meaning | On the CV |
|---|---|---|
| `P` | Used in production at a job | Shown |
| `L` | Your own project or lab | Shown |
| `F` | Familiarity | Only on the line labeled "Familiarity" |
| `X` | You don't have it | Never shown, but recorded so gap reports are honest |

### Skill checklists

Recording dozens of skills one question at a time is slow. A checklist is a Markdown file you mark
in your editor, one letter per line:

```markdown
## Messaging and streaming
evidence: Payments platform at Acme, 2020–2026
- [P] Kafka — reconciliation flow, 2M events a day · years: 6 · last year: 2026
- [F] RabbitMQ — a workshop and the official tutorials
- [X] NATS
```

Text after " — " is the evidence, and an `evidence:` line under a heading covers that section.
Lines without evidence are never saved: they come back as pending, and your assistant asks you
about them in groups. Upgrades are confirmed once for the whole file.

```bash
honest-cv checklist list                                     # backend-jvm, platform-sre, ...
honest-cv checklist new ~/cv-profile backend-jvm engineering-lead
honest-cv checklist apply ~/cv-profile ~/cv-profile/checklist.md
```

**Any profession works.** The built-in checklists are for software roles, but a template is just a
Markdown list in this format: save your own (or one someone shared) in `~/cv-profile/checklists/`
and it shows up in `checklist list` and combines with the others in `checklist new`, which also
takes a path. A template can declare synonyms, such as `same: Work Breakdown Structure = WBS`,
and the job score, the gaps and the letter check use them, along with its skill names. Start from
[`examples/checklists/project-management.md`](https://github.com/JesusCabreraReveles/honest-cv/blob/master/examples/checklists/project-management.md). You can also apply a file you marked yourself, in English or Spanish (`años`,
`último uso`). When you're done, honest-cv names the P and L skills that no achievement shows, and
your assistant asks you for one real example of each.

```bash
honest-cv checklist list ~/cv-profile                        # yours too, e.g. accounting
honest-cv checklist new ~/cv-profile accounting engineering-lead
```

New checklists for other kinds of roles are welcome: see [CONTRIBUTING.md](https://github.com/JesusCabreraReveles/honest-cv/blob/master/CONTRIBUTING.md).

See [`examples/ada/`](https://github.com/JesusCabreraReveles/honest-cv/tree/master/examples/ada) for a complete, fictional profile with three variants, a job
post and its keywords.

## Privacy

- **honest-cv itself makes no network calls.** No accounts, no telemetry, no API keys. Your
  profile is a folder of YAML files on your machine, and the PDF engine and fonts run locally. You
  can check it: every command works with the network turned off.
- **Your AI client is a different matter.** Whatever the client's model reads through the tools
  goes to that model's provider, under the provider's terms, like anything else you share in a
  chat. That includes your skills, bullets and the text of your CVs.
- **Some clients copy files to the cloud.** To edit files directly, Claude Desktop may ask for
  access to the profile folder and say that the files leave your device. honest-cv's editing tools
  make that unnecessary: decline the request and ask the assistant to use the tools.
- **If you want nothing to leave your machine,** use only the CLI (`build`, `verify`, `score`,
  `skill`) and edit the YAML files yourself.

## Import what you already have

`honest-cv import` reads a [JSON Resume](https://jsonresume.org) file or LinkedIn's data export
(Settings → Data privacy → Download your data, a ZIP of CSVs, or its folder if your browser
unzipped it). Ask for the full archive: the basic one, offered when you pick files, has only your
headline and summary, without roles. Contact, roles, education,
certifications, languages and each role's achievements go into your profile; with a headline and
a summary it also creates a first variant, so `build` works right away.

- It only adds what your profile lacks and never overwrites. Try `--dry-run` first.
- Imported achievements are tagged `imported`: review their wording.
- **Skills arrive without a level**, because neither format says whether you used a skill in
  production. They go to `imported-skills.md`, a checklist for you to mark.

LinkedIn's "Save to PDF" profile changes layout without notice, so it isn't parsed: with an MCP
client, the `import_linkedin_pdf` prompt reads its text and proposes each section for you to
approve.

## Track your applications

`honest-cv apps` keeps a log in `applications.yaml`, inside your profile folder: which variant and
files went to which company, and each status with its date (`draft`, `applied`, `interview`,
`offer`, `rejected`, `withdrawn`). Logging the same company and role again updates it. With an MCP
client, your assistant offers to log an application after building the CV or letter, and
`list_applications` points out the ones with no news for two weeks. It also keeps the job post's
text in `posts/`, since links expire, and what to prepare for the interview: ask your assistant
*"what did I need to prepare for Acme?"*.

## LinkedIn

`honest-cv linkedin` turns a variant into LinkedIn's texts, each within its limit: the headline
(your title and up to five production skills), About (the summary and impact bullets), one
description per position from its bullets, and the skills list. It writes them to a Markdown file
to copy from. The skills are P and L only: LinkedIn can't label familiarity, so F skills stay out,
and X skills never appear. With an MCP client, the `linkedin_profile` prompt polishes the wording
and checks it with `check_claims`.

## Export

To go the other way, `honest-cv export` writes a variant as JSON Resume for other CV tools and
sites, with the same rules as the CV: X skills never appear, and F skills only in a "Familiarity"
group with that level.

## Cover letters

The `cover_letter` prompt drafts a one-page letter for a job post from your own bullets. Before it
is built, `check_claims` reads the text against your profile:

- **Problems block the build:** a skill you marked X, or a familiarity skill presented as
  experience ("I use Rust in production" when Rust is F; "I am familiar with Rust" is fine).
- **Warnings are for you to review:** a technology neither your inventory nor your bullets show,
  or a number that appears nowhere in your profile. If it is true, record it first; if not, it
  comes out of the letter.

The letter uses the CV's fonts and passes the same ATS checks. Without an AI client, write it in a
text file (paragraphs separated by blank lines) and use `honest-cv letter check` and `letter build`.

## CLI

Run any command with `uvx honest-cv ...`, or install it once with `uv tool install honest-cv` to
type just `honest-cv`.

```bash
honest-cv init ~/cv-profile                        # create the profile templates
honest-cv build ~/cv-profile -v backend -l en -l es # PDF + DOCX, verified on every build
honest-cv verify any-cv.pdf --text                  # check any PDF, even one made elsewhere
honest-cv score ~/cv-profile -k "Kubernetes,Go" -p "Rust"   # coverage per variant, with gaps
honest-cv skill ~/cv-profile "Rust" L --evidence "CLI tool on GitHub"  # asks you to confirm
honest-cv import ~/cv-profile linkedin-export.zip --dry-run         # or resume.json
honest-cv export ~/cv-profile -v backend -l en                       # JSON Resume
honest-cv apps log ~/cv-profile --company Acme --role "SRE" -s applied -v platform
honest-cv apps list ~/cv-profile                                     # latest first, with counts
honest-cv linkedin ~/cv-profile -v platform -l en                    # texts to paste into LinkedIn
honest-cv remove skill ~/cv-profile "Kafka"                          # asks you to confirm
honest-cv letter check ~/cv-profile letter.txt -k "Kubernetes,Go"     # claims your profile doesn't support
honest-cv letter build ~/cv-profile letter.txt --company Acme --role "Platform Engineer" -l en
honest-cv serve --profile ~/cv-profile              # the MCP server, over stdio
```

`verify` works on any PDF. Use `--text` to see exactly what a parser extracts. Its
`single_column` check also catches a common defect: a date aligned to the right, which parsers
split away from its role.

## MCP tools and prompts

| Name | What it does |
|---|---|
| `init_profile` | Creates a profile folder with commented templates. |
| `get_inventory` | Returns your skills, grouped by level, with years and evidence. |
| `set_skill` | Adds or changes a skill. To raise its level it needs evidence and asks you to confirm. |
| `set_skills` | Adds or changes several skills at once. Upgrades are confirmed with one question that lists each change. |
| `checklist_list` | Lists the built-in checklists. |
| `checklist_new` | Merges built-in checklists into one file, prefilled from your inventory. |
| `checklist_apply` | Saves the skills marked in a checklist and returns the lines that still need evidence. |
| `add_bullet` | Adds an achievement to the pool. |
| `remove_skill`, `remove_bullet`, `remove_role`, `remove_publication` | Delete for good, and remove every use in the variants. A role with bullets needs `with_bullets`. Clients ask before running them. |
| `set_contact` | Changes contact details: name, email, phone, location, links. |
| `set_profile_details` | Replaces the certifications list, the courses list and the spoken-languages line. |
| `upsert_role` | Adds a role in date order, or changes fields of an existing one. |
| `upsert_education` | Adds or changes an education entry. |
| `upsert_publication` | Adds or changes a book, article or paper the person wrote. Each variant shows all of them, or the ones it lists. |
| `save_variant` | Adds or replaces a variant. Every reference is checked before saving. |
| `list_variants` | Lists the variants after inheritance, with their bullet texts and the bullets no variant uses. |
| `build_cv` | Renders a PDF and/or DOCX and verifies it. |
| `verify_pdf` | Runs the ATS checks on any PDF and returns its extracted text. |
| `score_job` | Measures the keyword coverage of each variant. The client extracts the keywords. |
| `gap_report` | Groups missing keywords by what you can honestly do about each one. |
| prompt `interview` | Fills your inventory and bullet pool, one question at a time. |
| `check_claims` | Checks any text against your profile: blocks X skills and familiarity sold as experience, flags what the profile doesn't show. |
| `build_letter` | Renders a cover letter to PDF and DOCX, one page, with the CV's ATS checks. Refuses while `check_claims` finds problems. |
| prompt `tailor_for_job` | Scores the variants, explains the gaps, adapts a variant and rebuilds. |
| prompt `cover_letter` | Drafts a letter for a job post from your bullets, checks its claims and builds it. |
| `import_profile` | Imports JSON Resume or a LinkedIn data export, adding only what is missing. For a LinkedIn PDF it returns the text. |
| `export_json_resume` | Exports a variant as JSON Resume; X skills never appear and F skills keep their label. |
| `log_application` | Records an application (company, role, CV variant, files sent, the post's text, what to prepare) or a new status, with its date. |
| `linkedin_profile` | LinkedIn headline, About, position descriptions and skills (P and L only) from a variant, within LinkedIn's limits. |
| `list_applications` | Lists applications, latest first, with a count per status and the ones with no news for 14+ days. |
| prompt `import_linkedin_pdf` | Turns the PDF from LinkedIn's "Save to PDF" into a profile, section by section, with your approval. |
| prompt `linkedin_profile` | Polishes the LinkedIn texts of a variant without adding claims, checked with `check_claims`. |

## ATS guarantees

The template enforces these. `verify` checks them on every build, and the test suite runs in CI on
Linux, macOS and Windows:

- One column. No layout tables, images, icons, headers or footers.
- Ligatures and kerning off, no hyphenation, and terms like "CI/CD", URLs and emails never split
  across lines.
- Reading order equals source order. Dates sit on the same line as their role.
- Text is extractable by both pypdf and pdfminer.six, and the two agree.
- The fonts (Carlito, metric-compatible with Calibri) are embedded and identical on every machine.

## Development

```bash
uv sync
uv run pytest
uv run honest-cv build examples/ada -l en -l es
```

The repository's `.mcp.json` registers `honest-cv-dev` with the fictional profile, so Claude Code
in this folder can use your working copy. See [CONTRIBUTING.md](https://github.com/JesusCabreraReveles/honest-cv/blob/master/CONTRIBUTING.md).

## License

MIT. The bundled Carlito font is under the SIL Open Font License; see
`src/honest_cv/render/fonts/OFL.txt`.
