🖨 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.
Quickstart · See real output · FAQ · Contribute
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.
pipx install offerprinter
offerprinter --cv ~/cv.pdf --jd "https://careers.company.com/jobs/123"
What is OfferPrinter?
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.
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.
What does OfferPrinter actually produce?
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.
| # | 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/ — an anonymised marketing analyst applying for a Senior Product Analyst role at "Northbank":
tailored CV · cover letter · fit memo · ATS report · interview prep · full package
What is the no-fabrication guarantee?
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.
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:
## 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
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.
✓ 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:
# 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:
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:
[output]
template = "rule"
How do I install OfferPrinter?
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.
⚡ The fastest way
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
pip
pip install offerprinter # CLI only
pip install "offerprinter[web]" # + the Streamlit web UI
No Python at all — standalone binary
Download the archive for your platform from the
latest release,
unpack it, and run ./offerprinter. Nothing else to install.
Docker
# 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
Homebrew
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:
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.
From source
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
Not sure it's worth the API spend? Find out before you pay for anything:
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?
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.
1. ⌨️ CLI — nicest for scripting
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
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. 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?
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.
🎯 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?
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.
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?
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.
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?
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.
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?
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.
# copy the advert, then:
offerprinter --cv ~/cv.pdf --jd-clipboard
The browser 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?
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.
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?
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.
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?
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.
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?
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.
Add to Claude Desktop's claude_desktop_config.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 for agents that
prefer to drive the CLI.
Which AI model should I use with OfferPrinter?
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.
| 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:
[llm]
provider = "openai" # was "anthropic"
…or per run: offerprinter --provider openai --cv … --jd ….
Want your CV never to leave your machine at all?
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?
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.
[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?
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.
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 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.
Want a new provider? Add one subclass in offerprinter/llm/ — nothing else changes.
FAQ
Does OfferPrinter upload my CV anywhere?
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.
Will OfferPrinter lie to make me look better?
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.
What CV file formats does OfferPrinter support?
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.
Can OfferPrinter read a job posting from a URL?
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.
How much does a single OfferPrinter run cost?
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.
How long does a run take?
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.
Does OfferPrinter write British or American English?
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.
Is my CV used to train an AI model?
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.
How can I contribute to OfferPrinter?
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.
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/that catches a failure mode the suite misses. Synthetic and anonymised only.
Read CONTRIBUTING.md · SECURITY.md · CHANGELOG.md · CODE_OF_CONDUCT.md
Licensed under the MIT License. Use it, fork it, ship it.
⭐ 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.
Release files for offerprinter 0.4.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 | |
|---|---|---|---|
| offerprinter-0.4.0.tar.gz | 370.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| offerprinter-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 473.8 kB
Release files / offerprinter-0.4.0.tar.gz
| Download URL | offerprinter-0.4.0.tar.gz |
|---|---|
| Size | 370.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
63844cf72cb1988ef64ebcf8799dcf3e303a78fc415710661a9becfe833b4c7b
|
|
BLAKE2b-256 checksum How to use checksums |
d47bcdb2a9abb1fe08aa9be5c3c4128571c1818d78eecabccdd9e5a543a607c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency logRelease files / offerprinter-0.4.0-py3-none-any.whl
| Download URL | offerprinter-0.4.0-py3-none-any.whl |
|---|---|
| Size | 103.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
589e08f6d5ca5a04fd5c63d43e1bf92a5ed5a0b91078658b180dbed35a2916cf
|
|
BLAKE2b-256 checksum How to use checksums |
2802599745860141b0f49ff7a513abf97d8fee334db29ca5c677c30eecd629c6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 25, 2026.
Transparency log