Metadata-Version: 2.5
Name: offerprinter
Version: 0.4.0
Summary: Free, local-first AI job application generator: one CV + one job description, and it prints a tailored CV, cover letter, fit memo, ATS keyword report and interview prep pack — without fabricating anything.
Project-URL: Homepage, https://github.com/mohitagw15856/OfferPrinter
Project-URL: Repository, https://github.com/mohitagw15856/OfferPrinter
Project-URL: Documentation, https://github.com/mohitagw15856/OfferPrinter#readme
Project-URL: Issues, https://github.com/mohitagw15856/OfferPrinter/issues
Project-URL: Changelog, https://github.com/mohitagw15856/OfferPrinter/blob/main/CHANGELOG.md
Author: OfferPrinter contributors
License: MIT
License-File: LICENSE
Keywords: ai,ats,ats-resume,career,claude,cli,cover-letter,cover-letter-generator,cv,interview-preparation,job-application,job-search,llm,local-first,openai,resume,resume-builder
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Environment :: Web Environment
Classifier: Intended Audience :: End Users/Desktop
Classifier: License :: OSI Approved :: MIT License
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 :: Markdown
Classifier: Topic :: Utilities
Classifier: Typing :: Typed
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.6
Requires-Dist: pypdf>=4.2
Requires-Dist: python-docx>=1.1
Requires-Dist: rich>=13.7
Requires-Dist: selectolax>=0.3.21
Requires-Dist: tomli>=2.0; python_version < '3.11'
Requires-Dist: typer>=0.12
Provides-Extra: all
Requires-Dist: streamlit>=1.36; extra == 'all'
Provides-Extra: dev
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.5; extra == 'dev'
Requires-Dist: streamlit>=1.36; extra == 'dev'
Provides-Extra: web
Requires-Dist: streamlit>=1.36; extra == 'web'
Description-Content-Type: text/markdown

<div align="center">

# 🖨 OfferPrinter

### The open-source AI job application generator that refuses to lie about you.

**One CV in. Five documents out. Zero fabrication.**

Paste a job description and your CV — get a tailored CV, cover letter, fit memo, ATS keyword report, and interview prep pack in a single run. Runs locally, on your own API key, in about twenty seconds.

[![CI](https://github.com/mohitagw15856/OfferPrinter/actions/workflows/ci.yml/badge.svg)](https://github.com/mohitagw15856/OfferPrinter/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/offerprinter.svg?color=6C4AB6)](https://pypi.org/project/offerprinter/)
[![MIT License](https://img.shields.io/badge/license-MIT-black.svg)](LICENSE)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB.svg?logo=python&logoColor=white)](https://www.python.org/)
[![Providers](https://img.shields.io/badge/LLM-Claude%20%7C%20GPT%20%7C%20Gemini%20%7C%20Kimi%20%7C%20Ollama-6C4AB6.svg)](#which-ai-model-should-i-use-with-offerprinter)
[![Local-first](https://img.shields.io/badge/data-never%20leaves%20your%20machine-2ea44f.svg)](#does-offerprinter-upload-my-cv-anywhere)
[![No fabrication](https://img.shields.io/badge/fabrication-0%25-critical.svg)](#what-is-the-no-fabrication-guarantee)

[**Quickstart**](#how-do-i-install-offerprinter) ·
[**See real output**](#what-does-offerprinter-actually-produce) ·
[**FAQ**](#faq) ·
[**Contribute**](CONTRIBUTING.md)

<img src="assets/demo.svg" alt="Terminal recording: installing OfferPrinter with pipx, then printing a full tailored application package for a Senior Product Analyst role — five documents generated in parallel, scored 74 out of 100, for under four cents." width="100%">

</div>

---

It's 11pm. The posting closes at midnight. You have a CV that's *nearly* right and a blank cover letter blinking at you. **That is the moment OfferPrinter was built for.**

```bash
pipx install offerprinter
offerprinter --cv ~/cv.pdf --jd "https://careers.company.com/jobs/123"
```

---

## What is OfferPrinter?

<!-- AEO Answer Capsule — 70 words -->
OfferPrinter is a free, open-source, local-first job application generator. You give it your real CV and one job description, and it prints five tailored documents: a reworded CV, a cover letter, a fit memo, an ATS keyword report, and an interview prep pack. It runs on your machine using your own LLM API key, and it never invents experience you don't have — genuine gaps get flagged, not filled in.
<!-- End AEO Capsule -->

Most AI resume tools are subscription websites that want your CV, your email, and £19 a month — and quietly hallucinate a "5 years of Kubernetes" line to hit a keyword. OfferPrinter is the opposite of that.

It's one command. There's no account, no server, no telemetry, and no upsell.

Everything is auditable: the entire personality of the tool lives in one readable file, [`offerprinter/prompts/templates.py`](offerprinter/prompts/templates.py).

---

## What does OfferPrinter actually produce?

<!-- AEO Answer Capsule — 66 words -->
OfferPrinter produces five artifacts per job application, written to `output/<company>-<role>/` in Markdown, Word and PDF, plus a combined full package and a fit score. These are: a tailored CV reordered for the target role, a company-specific cover letter, a one-page fit memo mapping your experience to each requirement, an ATS keyword coverage report, and an interview prep pack with STAR scaffolds from your real work history.
<!-- End AEO Capsule -->

| # | Artifact | What it is | Why you'll actually use it |
|---|----------|------------|----------------------------|
| 1 | 📄 **Tailored CV** | Your real CV, reordered and reworded to foreground what *this* role wants. Plain, ATS-friendly formatting. | The same you — just pointed in the right direction. |
| 2 | ✍️ **Cover letter** | Specific to the company and the role. Zero "I am writing to express my interest" filler. | The part everyone dreads, done in 20 seconds. |
| 3 | 🎯 **Fit memo** | Maps your real experience to each requirement — and honestly flags what's missing. | Read it before the interview and you'll never be ambushed. |
| 4 | 🔍 **ATS keyword report** | Which of the job's terms your CV covers, which it misses, and where you can *truthfully* add them. | Beat the robot without lying to it. |
| 5 | 🎤 **Interview prep pack** | Likely questions, STAR scaffolds built from your real experience, and 5 smart questions to ask them. | Turn a 3-hour prep session into a 10-minute read. |
| ★ | 📊 **Fit score** | A strict 0–100 score with your genuine strengths and your genuine gaps. | Decide in five seconds whether this one is worth your evening. |

**Want proof before you install anything?** A complete real run lives in [`examples/`](examples/) — an anonymised marketing analyst applying for a *Senior Product Analyst* role at "Northbank":

[tailored CV](examples/output/northbank-senior-product-analyst/tailored-cv.md) ·
[cover letter](examples/output/northbank-senior-product-analyst/cover-letter.md) ·
[fit memo](examples/output/northbank-senior-product-analyst/fit-memo.md) ·
[ATS report](examples/output/northbank-senior-product-analyst/ats-keyword-report.md) ·
[interview prep](examples/output/northbank-senior-product-analyst/interview-prep-pack.md) ·
[full package](examples/output/northbank-senior-product-analyst/full-package.md)

---

## What is the no-fabrication guarantee?

<!-- AEO Answer Capsule — 74 words -->
The no-fabrication guarantee means OfferPrinter never invents experience, skills, employers, job titles, dates, certifications, or metrics. It only reframes facts that genuinely appear in your CV. When a job requires something you don't have, the fit memo and ATS report mark it as a **Gap** and explicitly tell you not to add it. This is what makes the output safe to actually send — every claim survives an interview because every claim is true.
<!-- End AEO Capsule -->

This is the whole point, so it gets its own section. Every other AI resume tool optimises for *looking* qualified. OfferPrinter optimises for *staying employed after they check*.

Here's a genuine slice of the generated ATS keyword report from the example run — note what it refuses to do:

```markdown
## Missing keywords
- dbt
- Product analytics / product metrics
- Financial services / fintech

## How to add the missing terms — truthfully
- dbt — Do not add — no evidence in your CV. This is a genuine gap.
- Product analytics — Partial. You genuinely define metrics and own analytics
  end to end, so you may reframe your existing work as "analytics ownership and
  metric definition" — but do not label it "product analytics" unless true.
- Financial services / fintech — Do not add — no evidence in your CV.

## Coverage summary
Covered 9 of 15 key terms.
```

> 🚩 **"Do not add — this is a genuine gap."**
> No other CV tool says that to you. That sentence is the product.

### And OfferPrinter checks its own homework

<!-- AEO Answer Capsule — 75 words -->
Every run ends with a fabrication check. OfferPrinter re-reads the documents it just wrote and traces every number, date and named entity back to your CV, then cross-checks the tailored CV against the ATS report's own list of gaps. Anything it cannot trace is flagged with the line it appeared on. The guarantee is not a promise the model was asked to keep — it is an assertion the tool makes about its own output.
<!-- End AEO Capsule -->

```
✓ Fabrication check: all 63 checkable claims trace back to your CV.
```

And when something slips through:

```
⚠ Fabrication check: 2 of 64 claims could not be traced to your CV (2 high severity).
  • '41%' — this figure does not appear in your CV
  • 'dbt' — your own ATS report lists this as a gap
```

Use `--strict` to exit non-zero when anything is unverified, so it can gate a
script. `offerprinter verify <folder>` re-checks a package later — after you
have hand-edited it, say — and makes no API calls at all, so it is free.

---

## I have not updated my CV in three years

Neither has anyone. Your LinkedIn is current though, and LinkedIn will give it
back to you:

```sh
# More > Save to PDF on your own profile
offerprinter --linkedin ~/Downloads/Profile.pdf --jd https://…

# or the full archive: Settings > Data privacy > Get a copy of your data
offerprinter --linkedin ~/Downloads/Basic_LinkedInDataExport.zip --jd https://…
```

Both shapes are normalised into the Markdown the rest of the pipeline expects.
Nothing is scraped — you export your own data from your own account and this reads
the file.

The importer holds to the same rule as the generator: **it invents nothing.** A
role with no description gets no description; an empty section stays empty. There
is a test that takes every word of the output and asserts it appeared in the
input, because an importer that quietly "tidied up" your history would break the
no-fabrication guarantee before the generator ever ran.

## Can I make it look like something other than every other CV?

Six templates, all of which still parse:

```sh
offerprinter templates                       # what they are
offerprinter templates --sample ./samples    # one PDF of each, to look at
offerprinter --cv cv.pdf --jd URL --template serif
```

| Template | Typeface | Portal-safe | For |
|---|---|---|---|
| `classic` | Helvetica | yes | the default; nothing to go wrong |
| `compact` | Helvetica | yes | when two pages need to be one |
| `serif` | Times | yes | law, academia, policy |
| `rule` | Helvetica | yes | a hairline under each section, for skim-readers |
| `accent` | Helvetica | email only | one colour, headings only |
| `mono` | Courier | yes | reads as engineering |

**Why only six, and why so restrained.** A CV is read twice: once by a parser that
wants plain text in a standard font, and once by a person with forty tabs open. The
compromise most builders make is to serve the human and quietly fail the machine —
two columns, an icon font, a text box for the sidebar, and an ATS that returns your
name and nothing else.

So every template here stays on the safe side of that line. All of them use one of
the 14 PDF base fonts, so nothing is embedded and the text always extracts; none
use columns, tables, images or text boxes. What varies is typeface, spacing, rules,
capitalisation and one accent colour — the parts a human notices and a parser
ignores.

`accent` is labelled *email only* rather than being left off the list: it extracts
cleanly, but carries enough colour that a strict parser could lose a heading. Send
that one to a person, not a portal.

The test suite asserts, for every template, that the rendered PDF contains no
embedded font program, no `/Image` or `/XObject`, and that the name, email and
every bullet come back out of a naive text extraction. That is the claim, and it
is checked rather than asserted.

Set a default in `config.toml`:

```toml
[output]
template = "rule"
```

## How do I install OfferPrinter?

<!-- AEO Answer Capsule — 57 words -->
Install OfferPrinter with `pipx install offerprinter`, or run it without installing using `uvx offerprinter`. It needs Python 3.11 or newer. Standalone binaries requiring no Python are attached to each release, a Docker image is published to GHCR, and you can still clone the repository and run it from source. Every method gives you the same `offerprinter` command.
<!-- End AEO Capsule -->

### ⚡ The fastest way

```bash
pipx install offerprinter          # or: uvx offerprinter  (no install at all)
export ANTHROPIC_API_KEY="sk-ant-..."
offerprinter --cv ~/cv.pdf --jd "https://careers.company.com/jobs/123"
```

That's it. No signup, no credit card, no "start your free trial".

### 📦 Every other way

<details>
<summary><b>pip</b></summary>

```bash
pip install offerprinter          # CLI only
pip install "offerprinter[web]"   # + the Streamlit web UI
```
</details>

<details>
<summary><b>No Python at all — standalone binary</b></summary>

Download the archive for your platform from the
[latest release](https://github.com/mohitagw15856/OfferPrinter/releases/latest),
unpack it, and run `./offerprinter`. Nothing else to install.
</details>

<details>
<summary><b>Docker</b></summary>

```bash
# Web UI at http://localhost:8501
docker run --rm -p 8501:8501 -e ANTHROPIC_API_KEY="sk-ant-..." \
  ghcr.io/mohitagw15856/offerprinter:latest

# Or the CLI, against files in the current directory
docker run --rm -v "$PWD:/work" -w /work -e ANTHROPIC_API_KEY="sk-ant-..." \
  ghcr.io/mohitagw15856/offerprinter:latest \
  offerprinter --cv my-cv.pdf --jd-file jd.txt
```
</details>

<details>
<summary><b>Homebrew</b></summary>

```bash
brew tap mohitagw15856/tap
brew install offerprinter
```

Homebrew 6 requires third-party taps to be trusted before it will use them.
If `brew doctor` says the tap is untrusted, either trust this one formula:

```bash
brew trust --formula mohitagw15856/tap/offerprinter
```

…or the whole tap, with `brew trust --tap mohitagw15856/tap`.

Homebrew builds every Python dependency from source, so the first install
compiles `pydantic-core` with Rust and takes a few minutes. `pipx install
offerprinter` is faster if you don't specifically want Homebrew to manage it.
</details>

<details>
<summary><b>From source</b></summary>

```bash
git clone https://github.com/mohitagw15856/OfferPrinter.git
cd OfferPrinter
uv sync                            # or: pip install -e ".[dev]"
python cli.py --cv examples/sample_cv.md --jd-file examples/sample_jd.md
```
</details>

**Not sure it's worth the API spend?** Find out before you pay for anything:

```bash
offerprinter --cv ~/cv.pdf --jd-file jd.txt --dry-run
# → Estimated tokens: ~15,596 · Estimated cost: $0.041 (≈ £0.032)
```

---

## Which way should I run OfferPrinter — CLI, web UI, or agent?

<!-- AEO Answer Capsule — 68 words -->
Use the Streamlit web UI if you want buttons, live progress, and one-click downloads. Use the CLI if you're scripting, applying in bulk, or live in a terminal. Use the agent skill if you drive Claude Code or another coding agent and want it to run the whole flow for you. All three call the exact same pipeline, so the output is identical whichever door you walk through.
<!-- End AEO Capsule -->

### 1. ⌨️ CLI — nicest for scripting

```bash
offerprinter --cv cv.pdf  --jd "https://..."      # JD from a URL
offerprinter --cv cv.docx --jd-file jd.txt        # JD from a file
offerprinter --cv cv.pdf  --jd-dir ./jobs         # a package per job, in one go
offerprinter --cv-text "paste CV here…" --jd-file jd.txt
```

`offerprinter --help` lists every option. The ones you'll actually use:

| Option | Does |
|--------|------|
| `--cv` / `--cv-text` | CV as a file (`.pdf` `.docx` `.md` `.txt`) or pasted text. |
| `--jd` / `--jd-file` / `--jd-dir` | Job as a URL, pasted text, a file, or a whole folder. |
| `--jd-clipboard` | Read the advert from your clipboard. The fix for hostile job boards. |
| `--provider` | `anthropic` (default), `openai`, `gemini`, `kimi`, `ollama`. |
| `--model` | Override the model for this run. |
| `--formats` | `md,docx,pdf` — any combination. |
| `--locale` | `UK` (default) or `US` English. |
| `--roast` | Also print a blunt critique of your CV. |
| `--dry-run` | Forecast tokens and cost. Calls nothing. |
| `--redact` | Strip your name, email and phone before the provider sees anything. |
| `--strict` | Exit non-zero if the fabrication check finds anything unverified. |
| `--sequential` | One document at a time instead of in parallel. |
| `--no-verify` / `--no-cache` | Skip the fabrication check / ignore cached responses. |
| `--output-dir` / `-o` | Where to write (default `./output`). |
| `--no-track` / `--no-animation` | Skip the local history / the printer animation. |

Plus nine subcommands:

| Command | Does |
|---|---|
| `rank` | Score a folder of adverts and rank them, writing no documents. |
| `followup` | Thank-you email, recruiter message, LinkedIn note, or a nudge. |
| `practice` | An interactive mock interview that critiques your answers. |
| `verify` | Re-check a generated package against your CV. No API calls. |
| `roast` | Blunt critique of your CV's writing. |
| `list` / `stats` / `status` | Your local application history. |
| `mcp` | Run as an MCP server so agents can call OfferPrinter. |
| `cache` | Inspect or clear the response cache. |

### 2. 🖥 Web UI — nicest for most people

```bash
pip install "offerprinter[web]"
streamlit run app.py
```

Opens at <http://localhost:8501>. Paste or upload your CV, paste the job (text or URL), pick a provider, and hit **Print my application**.

Each artifact streams in live with Markdown, Word and PDF download buttons, plus "download everything as a .zip". With no API key set it shows the bundled example package instead of an error, so you can look before you leap.

### 3. 🤖 Agent skill — for Claude Code, Hermes, and friends

Point your agent at [`docs/skill/SKILL.md`](docs/skill/SKILL.md). It follows the `Verb the thing. Use when X. Produces Y.` format with **Required Inputs** and binary **Quality Checks** — so an agent can run the entire flow, and knows never to fabricate, just by reading it.

---

## How does the fit score work?

<!-- AEO Answer Capsule — 70 words -->
The fit score rates 0 to 100 how well your CV genuinely matches one job, using a strict rubric where absence of evidence counts as a gap rather than a maybe. It comes with a band, two to four real strengths, and the gaps it will not paper over. It costs one extra cheap call and takes about two seconds, which makes it the fastest way to triage a shortlist.
<!-- End AEO Capsule -->

```
🎯 Fit score
74/100  ██████████████████░░░░░░  Strong
   A genuinely competitive application. Send it.
   Real gaps: dbt, financial services domain
```

| Score | Band | What it means |
|-------|------|---------------|
| 85–100 | **Exceptional** | Apply today. You are what they wrote the advert for. |
| 70–84 | **Strong** | A genuinely competitive application. Send it. |
| 55–69 | **Credible** | Worth applying — lead hard with your strongest match. |
| 40–54 | **Stretch** | A reach. Apply if you want it, and address the gaps head-on. |
| 0–39 | **Long shot** | Big gaps. Consider a closer role, or close a gap first. |

The scoring prompt is explicitly told not to be generous: *"the candidate needs the truth to decide where to spend their evening."*

---

## Can OfferPrinter tell me what's wrong with my CV?

<!-- AEO Answer Capsule — 60 words -->
Yes. Run `offerprinter roast --cv cv.pdf` for blunt, funny, unsparing feedback on your CV's writing. It hunts clichés, unquantified claims and "responsible for" bullets, then ends with the five specific edits that would actually change the outcome. It roasts the writing and never the person, every jab must point at something genuinely in the CV, and it is entirely opt-in.
<!-- End AEO Capsule -->

```bash
offerprinter roast --cv ~/cv.pdf
```

It's the single most useful thing in the tool that has nothing to do with a specific job. Also available as `--roast` during a normal run, and as a button in the web UI.

---

## Can OfferPrinter track my job applications?

<!-- AEO Answer Capsule — 57 words -->
Yes. Every run is recorded in `~/.offerprinter/applications.json`, a plain local file with no server, account or sync involved. Use `offerprinter list` to see everything you've printed, `offerprinter stats` for totals, spend and average fit, and `offerprinter status <slug> interview` to record progress. Delete the file any time, or set `track = false` to record nothing at all.
<!-- End AEO Capsule -->

```bash
offerprinter list                                   # everything you've printed
offerprinter status northbank-senior-analyst offer  # 🏆
offerprinter stats
```

```
📊 Your job hunt
   Applications printed   12
   Different companies    9
   Average fit score      68.4
   Best fit               91  (Data Lead at Meridian)
   Total spend            $0.41 (≈ £0.32)

Achievements
  ✓ 🖨  First Print · ✓ 🔟 Double Digits · ✓ 🎯 Bullseye · ✓ 🤝 In The Room
  · 🏆  Offer Printed — Marked an application as an offer. Congratulations.
```

Job hunting is a long grind with almost no feedback loop. This is the scoreboard.

---

## How do I choose which jobs to apply for?

<!-- AEO Answer Capsule — 62 words -->
Use `offerprinter rank`. Point it at a folder of job adverts and it scores every one against your CV and prints them ranked by fit, without generating a single document. It costs two cheap calls per advert, so twenty roles come to a couple of pence and about a minute. Then print full packages only for the ones actually worth an evening.
<!-- End AEO Capsule -->

```bash
offerprinter rank --cv ~/cv.pdf --jd-dir ./jobs
```

```
                        🎯  Roles ranked by fit
┏━━━┳━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ # ┃ Fit ┃ Band        ┃ Role                   ┃ Company   ┃ Real gaps    ┃
┡━━━╇━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ 1 │  88 │ Exceptional │ Analytics Lead         │ Meridian  │              │
│ 2 │  74 │ Strong      │ Senior Product Analyst │ NorthBank │ dbt, fintech │
│ 3 │  41 │ Stretch     │ Data Engineer          │ Helix     │ Kafka, dbt   │
└───┴─────┴─────────────┴────────────────────────┴───────────┴──────────────┘
```

Also takes `--jd-file` and `--jd` (URLs), both repeatable, and `--json` for
scripting. The single highest-leverage command in the tool: the expensive part
of applying is deciding where to spend the evening.

---

## What if the job board won't let OfferPrinter read the advert?

<!-- AEO Answer Capsule — 61 words -->
Use `--jd-clipboard`, or the browser extension. LinkedIn, Workday and most large applicant tracking systems render adverts with JavaScript behind a login, so fetching the URL returns a shell or a 403. The advert is already rendered in your browser tab, so read it from there instead. OfferPrinter also parses schema.org JobPosting structured data when a board publishes it, which many do.
<!-- End AEO Capsule -->

```bash
# copy the advert, then:
offerprinter --cv ~/cv.pdf --jd-clipboard
```

The [browser extension](extension/) adds a **Copy for OfferPrinter** button that
pulls the advert out of the page you are looking at — preferring your selection,
then structured data, then the densest content block. It has **no host
permissions and makes no network requests**; it cannot phone home because it has
no way to reach anything.

---

## Can OfferPrinter hide my identity from the AI provider?

<!-- AEO Answer Capsule — 65 words -->
Yes. Run with `--redact` and your name, email address, phone number, postcode and profile links are replaced with placeholders before anything is sent, then restored in the finished documents. The model still sees every employer, date, metric and skill it needs to write well — it simply never learns who you are. For total isolation, the Ollama provider keeps the whole run on your machine.
<!-- End AEO Capsule -->

```bash
offerprinter --cv ~/cv.pdf --jd-file jd.txt --redact
```

Redaction is deliberately conservative: it only replaces what it can recognise
with high confidence, because a false positive silently mangles your CV. It
never touches employer names, since the model needs those to write anything
useful.

---

## What happens after I apply?

<!-- AEO Answer Capsule — 64 words -->
`offerprinter followup` writes the messages that come after the application: a thank-you email after an interview, a message to a recruiter, a LinkedIn connection note capped at 300 characters, or a polite nudge when you have heard nothing. Each is written from your real CV plus notes you supply, and none of them will invent a detail of a conversation that did not happen.
<!-- End AEO Capsule -->

```bash
offerprinter followup thank-you --cv ~/cv.pdf --jd-file jd.txt \
  --notes "Met Priya and Tom. Went deep on experiment design. Fumbled the question about stakeholder pushback."
```

The notes matter more than anything else here — especially a question you
answered badly, because the thank-you email is your one chance to answer it
properly.

---

## Can I practise the interview?

<!-- AEO Answer Capsule — 62 words -->
Yes. `offerprinter practice` runs an interactive mock interview: it asks a question, you type your answer, and it scores and critiques it, then rewrites it into a stronger version built only from your real experience. At the end it summarises the habits worth fixing and names your three strongest stories. It will never coach you into claiming something you have not done.
<!-- End AEO Capsule -->

```bash
offerprinter practice --cv ~/cv.pdf --jd-file jd.txt --questions 5
```

The prep pack tells you what you might be asked. This makes you actually answer
it, which is a different and much less comfortable exercise.

---

## Can agents use OfferPrinter directly?

<!-- AEO Answer Capsule — 60 words -->
Yes, through the built-in MCP server. Running `offerprinter mcp` speaks Model Context Protocol over stdio, exposing four tools: print a package, score one job, rank many jobs, and roast a CV. Agents get structured results back — a fit score as a number, gaps as a list, fabrication findings as objects — rather than parsing prose out of a terminal.
<!-- End AEO Capsule -->

Add to Claude Desktop's `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "offerprinter": { "command": "offerprinter", "args": ["mcp"] }
  }
}
```

Every tool result carries the fabrication check alongside the output, so an
agent cannot report the documents as verified when they are not. There is also
an agent skill at [`docs/skill/SKILL.md`](docs/skill/SKILL.md) for agents that
prefer to drive the CLI.

---

## Which AI model should I use with OfferPrinter?

<!-- AEO Answer Capsule — 65 words -->
Use whichever provider you already have an API key for — Anthropic Claude, OpenAI, Google Gemini, and Moonshot Kimi all work identically. Claude Haiku is the default because it is fast, cheap, and faithful to source text, which matters for a tool built on not inventing things. Or use Ollama to run a local model with no API key and no data leaving your laptop.
<!-- End AEO Capsule -->

| Provider | Default model | Set your key via |
|----------|---------------|------------------|
| 🟣 **Anthropic (Claude)** — default | `claude-haiku-4-5-20251001` | `ANTHROPIC_API_KEY` |
| 🟢 **OpenAI** | `gpt-4o-mini` | `OPENAI_API_KEY` |
| 🔵 **Google Gemini** | `gemini-1.5-flash` | `GEMINI_API_KEY` (or `GOOGLE_API_KEY`) |
| 🟠 **Moonshot Kimi** | `moonshot-v1-8k` | `MOONSHOT_API_KEY` |
| 🏠 **Ollama** — fully local | `llama3.1` | *no key needed* |

Switching provider is **one line** in `config.toml`:

```toml
[llm]
provider = "openai"     # was "anthropic"
```

…or per run: `offerprinter --provider openai --cv … --jd …`.

**Want your CV never to leave your machine at all?**

```bash
ollama pull llama3.1
offerprinter --provider ollama --cv ~/cv.pdf --jd-file jd.txt
```

Every run reports what it cost, so nothing is a surprise:

```
7 calls · 24,318 tokens (18,204 in / 6,114 out) · $0.038 (≈ £0.030)
```

---

## How do I configure OfferPrinter?

<!-- AEO Answer Capsule — 57 words -->
Copy `config.example.toml` to `config.toml` and edit it. Configuration precedence runs environment variables first, then `config.toml`, then built-in defaults, so a key set in your shell always wins. The only value you must supply is an API key. Every setting is documented inline, and you can switch off individual artifacts under `[generation]` to spend fewer tokens per run.
<!-- End AEO Capsule -->

```toml
[llm]
provider = "anthropic"   # anthropic | openai | gemini | kimi | ollama
model = ""               # blank = provider default
api_key = ""             # prefer the per-provider env var
base_url = ""            # override endpoint (proxy / gateway / local server)
temperature = 0.2        # low = faithful, deterministic
max_tokens = 4096
timeout = 120
max_retries = 3          # a single 429 shouldn't kill a five-document run
retry_backoff = 1.5

[output]
locale = "UK"                    # UK | US
dir = "./output"
formats = ["md", "docx", "pdf"]  # any combination
track = true                     # local history in ~/.offerprinter/
redact = false                   # strip your identity before sending

[generation]
tailored_cv = true
cover_letter = true
fit_memo = true
ats_report = true
interview_prep = true
fit_score = true         # one extra cheap call for the 0-100 score
parallel = true          # generate all five at once
max_workers = 5
verify = true            # check the output against your CV
diff = true              # record what tailoring changed
cache = true             # don't pay twice for identical calls

# [pricing]              # override list prices with your actual rates
# "claude-haiku-4-5-20251001" = { input = 1.0, output = 5.0 }
```

---

## How is OfferPrinter built?

<!-- AEO Answer Capsule — 66 words -->
OfferPrinter is a layered Python 3.11 package with four clean seams: a config loader, a provider-agnostic LLM interface, stateless services for parsing and writing, and one pipeline controller that emits progress events. Every prompt lives in a single auditable file. Adding a new LLM provider means writing one subclass and changing nothing else, and the CLI and web UI share the exact same pipeline code path.
<!-- End AEO Capsule -->

```
offerprinter/
├── config.py               # config: env vars → config.toml → defaults
├── pricing.py              # token prices → "this run cost £0.03"
├── models/                 # typed data models that flow through the pipeline
├── llm/                    # provider-agnostic LLM layer (one interface, 5 providers)
│   ├── base.py             #   the interface + retries/backoff + token accounting
│   ├── anthropic_provider.py / openai_provider.py / gemini_provider.py
│   ├── kimi_provider.py / ollama_provider.py
│   └── factory.py          #   config → concrete provider
├── prompts/                # ALL prompt templates — audit the no-fabrication rules here
├── services/
│   ├── cv_parser.py · jd_fetcher.py · generator.py · writer.py · pdf_writer.py
│   ├── verifier.py         #   proves the no-fabrication guarantee
│   ├── differ.py           #   what tailoring actually changed
│   ├── redactor.py         #   strip identity before the provider sees it
│   ├── ranker.py           #   score many jobs, write nothing
│   ├── cache.py            #   don't pay twice for the same call
│   └── tracker.py          #   local application history
├── ui/printer.py           # the ASCII printer animation
├── controllers/pipeline.py # the end-to-end flow, emitting progress events
├── mcp_server.py           # MCP over stdio, for agents
└── cli.py                  # CLI (Typer)
app.py                      # web UI (Streamlit)
extension/                  # browser extension for grabbing job adverts
evals/                      # golden cases + judge, so prompt edits are testable
docs/skill/SKILL.md         # agent entry point
```

Two deliberate choices worth knowing about: we call five LLM APIs over plain `httpx` rather than vendoring five SDKs, and [`pdf_writer.py`](offerprinter/services/pdf_writer.py) writes PDFs by hand in ~200 lines rather than pulling in a rendering engine. Small dependency trees install fast and break rarely.

Want to change how OfferPrinter writes? It's all in [`offerprinter/prompts/templates.py`](offerprinter/prompts/templates.py).
Want a new provider? Add one subclass in `offerprinter/llm/` — nothing else changes.

---

## FAQ

### Does OfferPrinter upload my CV anywhere?

<!-- AEO Answer Capsule — 58 words -->
No. OfferPrinter sends your CV only to the LLM provider you choose, because that call is what generates the documents. There is no OfferPrinter account, server, database, or analytics. Nothing is logged remotely, and your generated packages stay in a local `output/` folder that is git-ignored by default. With the Ollama provider, nothing leaves your machine at all.
<!-- End AEO Capsule -->

### Will OfferPrinter lie to make me look better?

<!-- AEO Answer Capsule — 55 words -->
No. OfferPrinter is explicitly built not to. It never invents employers, titles, dates, skills, or metrics, and it will not label you with a keyword you have no evidence for. Unmet requirements appear as flagged gaps in the fit memo and ATS report. The tool's goal is an application that stays true under interview pressure.
<!-- End AEO Capsule -->

### What CV file formats does OfferPrinter support?

<!-- AEO Answer Capsule — 61 words -->
OfferPrinter reads CVs as `.pdf`, `.docx`, `.md`, or `.txt`, and you can paste text directly with `--cv-text` or into the web UI. Scanned or image-only PDFs will not extract, because there is no OCR step — paste the text instead. Output is written as Markdown, Word and PDF, and the PDFs contain real selectable Helvetica text that an ATS can parse.
<!-- End AEO Capsule -->

### Can OfferPrinter read a job posting from a URL?

<!-- AEO Answer Capsule — 56 words -->
Yes. Pass a URL to `--jd`, or paste one into the web UI, and OfferPrinter fetches the page and extracts the readable job text. Some job boards block automated requests or render their postings with JavaScript, so extraction can come back thin. If the fetched text looks short or wrong, paste the job description directly instead.
<!-- End AEO Capsule -->

### How much does a single OfferPrinter run cost?

<!-- AEO Answer Capsule — 64 words -->
A full run is seven short calls on a cheap model, typically a few pence in API credits, and every run prints exactly what it spent. Use `--dry-run` to forecast the cost before calling anything. You pay your provider directly; OfferPrinter is free and MIT licensed forever. Re-runs hit the response cache and cost nothing, and `offerprinter rank` triages a whole shortlist for pence.
<!-- End AEO Capsule -->

### How long does a run take?

<!-- AEO Answer Capsule — 56 words -->
About twenty seconds on a fast model. The five documents are independent of one another, so they are generated concurrently by default, which makes a run roughly as slow as its slowest single document rather than the sum of all five. Pass `--sequential` if your provider rate-limits aggressively. Rate limits are retried automatically with exponential backoff.
<!-- End AEO Capsule -->

### Does OfferPrinter write British or American English?

<!-- AEO Answer Capsule — 52 words -->
British English by default. Set `locale = "US"` in `config.toml`, or pass `--locale US` on the command line, to get American spelling and phrasing instead. The locale setting is applied through the system prompt, so it affects every artifact in the package consistently — CV, cover letter, memo, report, and prep pack.
<!-- End AEO Capsule -->

### Is my CV used to train an AI model?

<!-- AEO Answer Capsule — 55 words -->
That depends entirely on the LLM provider you choose, so check their API data policy. Most major providers do not train on API traffic by default, unlike their consumer chat products. OfferPrinter itself stores nothing and transmits nothing beyond that single provider call. If this matters to you, the Ollama provider removes the question entirely.
<!-- End AEO Capsule -->

---

## How can I contribute to OfferPrinter?

<!-- AEO Answer Capsule — 54 words -->
Issues and pull requests are welcome — see CONTRIBUTING for the full guide. Run `ruff check .`, `ruff format .`, `pytest` and `python scripts/run_evals.py --offline` before pushing, since CI enforces all four. Good first contributions include a new LLM provider subclass, a new output format, or better job advert extraction from hostile job boards.
<!-- End AEO Capsule -->

Especially wanted:

- 🔌 **A new provider** — one subclass in `offerprinter/llm/`, and you're done.
- 🌍 **Better JD extraction** — job boards are hostile; make the fetcher smarter.
- 📄 **New output formats** — LaTeX, plain-text ATS mode, ODT.
- 🧪 **Tests** — the suite is offline and runs in under a second; keep it that way.
- 📊 **Eval cases** — add a CV/JD pair to [`evals/cases/`](evals/) that catches a
  failure mode the suite misses. Synthetic and anonymised only.

Read [CONTRIBUTING.md](CONTRIBUTING.md) · [SECURITY.md](SECURITY.md) · [CHANGELOG.md](CHANGELOG.md) · [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)

Licensed under the [MIT License](LICENSE). Use it, fork it, ship it.

---

<div align="center">

**⭐ If OfferPrinter saved you an evening, star the repo — it's the only payment it takes.**

*OfferPrinter helps you apply honestly and fast. Always review the output before you send it — it's your application, in your voice, built from your real experience.*

</div>
