offerprinter 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,542 @@
1
+ Metadata-Version: 2.5
2
+ Name: offerprinter
3
+ Version: 0.2.0
4
+ 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.
5
+ Project-URL: Homepage, https://github.com/mohitagw15856/OfferPrinter
6
+ Project-URL: Repository, https://github.com/mohitagw15856/OfferPrinter
7
+ Project-URL: Documentation, https://github.com/mohitagw15856/OfferPrinter#readme
8
+ Project-URL: Issues, https://github.com/mohitagw15856/OfferPrinter/issues
9
+ Project-URL: Changelog, https://github.com/mohitagw15856/OfferPrinter/blob/main/CHANGELOG.md
10
+ Author: OfferPrinter contributors
11
+ License: MIT
12
+ License-File: LICENSE
13
+ 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
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Environment :: Console
16
+ Classifier: Environment :: Web Environment
17
+ Classifier: Intended Audience :: End Users/Desktop
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Office/Business
25
+ Classifier: Topic :: Text Processing :: Markup :: Markdown
26
+ Classifier: Topic :: Utilities
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.11
29
+ Requires-Dist: httpx>=0.27
30
+ Requires-Dist: pydantic>=2.6
31
+ Requires-Dist: pypdf>=4.2
32
+ Requires-Dist: python-docx>=1.1
33
+ Requires-Dist: rich>=13.7
34
+ Requires-Dist: selectolax>=0.3.21
35
+ Requires-Dist: tomli>=2.0; python_version < '3.11'
36
+ Requires-Dist: typer>=0.12
37
+ Provides-Extra: all
38
+ Requires-Dist: streamlit>=1.36; extra == 'all'
39
+ Provides-Extra: dev
40
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
41
+ Requires-Dist: pytest>=8.0; extra == 'dev'
42
+ Requires-Dist: ruff>=0.5; extra == 'dev'
43
+ Requires-Dist: streamlit>=1.36; extra == 'dev'
44
+ Provides-Extra: web
45
+ Requires-Dist: streamlit>=1.36; extra == 'web'
46
+ Description-Content-Type: text/markdown
47
+
48
+ <div align="center">
49
+
50
+ # ๐Ÿ–จ OfferPrinter
51
+
52
+ ### The open-source AI job application generator that refuses to lie about you.
53
+
54
+ **One CV in. Five documents out. Zero fabrication.**
55
+
56
+ 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.
57
+
58
+ [![CI](https://github.com/mohitagw15856/OfferPrinter/actions/workflows/ci.yml/badge.svg)](https://github.com/mohitagw15856/OfferPrinter/actions/workflows/ci.yml)
59
+ [![PyPI](https://img.shields.io/pypi/v/offerprinter.svg?color=6C4AB6)](https://pypi.org/project/offerprinter/)
60
+ [![MIT License](https://img.shields.io/badge/license-MIT-black.svg)](LICENSE)
61
+ [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-3776AB.svg?logo=python&logoColor=white)](https://www.python.org/)
62
+ [![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)
63
+ [![Local-first](https://img.shields.io/badge/data-never%20leaves%20your%20machine-2ea44f.svg)](#does-offerprinter-upload-my-cv-anywhere)
64
+ [![No fabrication](https://img.shields.io/badge/fabrication-0%25-critical.svg)](#what-is-the-no-fabrication-guarantee)
65
+
66
+ [**Quickstart**](#how-do-i-install-offerprinter) ยท
67
+ [**See real output**](#what-does-offerprinter-actually-produce) ยท
68
+ [**FAQ**](#faq) ยท
69
+ [**Contribute**](CONTRIBUTING.md)
70
+
71
+ <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%">
72
+
73
+ </div>
74
+
75
+ ---
76
+
77
+ 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.**
78
+
79
+ ```bash
80
+ pipx install offerprinter
81
+ offerprinter --cv ~/cv.pdf --jd "https://careers.company.com/jobs/123"
82
+ ```
83
+
84
+ ---
85
+
86
+ ## What is OfferPrinter?
87
+
88
+ <!-- AEO Answer Capsule โ€” 70 words -->
89
+ 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.
90
+ <!-- End AEO Capsule -->
91
+
92
+ 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.
93
+
94
+ It's one command. There's no account, no server, no telemetry, and no upsell.
95
+
96
+ Everything is auditable: the entire personality of the tool lives in one readable file, [`offerprinter/prompts/templates.py`](offerprinter/prompts/templates.py).
97
+
98
+ ---
99
+
100
+ ## What does OfferPrinter actually produce?
101
+
102
+ <!-- AEO Answer Capsule โ€” 66 words -->
103
+ 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.
104
+ <!-- End AEO Capsule -->
105
+
106
+ | # | Artifact | What it is | Why you'll actually use it |
107
+ |---|----------|------------|----------------------------|
108
+ | 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. |
109
+ | 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. |
110
+ | 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. |
111
+ | 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. |
112
+ | 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. |
113
+ | โ˜… | ๐Ÿ“Š **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. |
114
+
115
+ **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":
116
+
117
+ [tailored CV](examples/output/northbank-senior-product-analyst/tailored-cv.md) ยท
118
+ [cover letter](examples/output/northbank-senior-product-analyst/cover-letter.md) ยท
119
+ [fit memo](examples/output/northbank-senior-product-analyst/fit-memo.md) ยท
120
+ [ATS report](examples/output/northbank-senior-product-analyst/ats-keyword-report.md) ยท
121
+ [interview prep](examples/output/northbank-senior-product-analyst/interview-prep-pack.md) ยท
122
+ [full package](examples/output/northbank-senior-product-analyst/full-package.md)
123
+
124
+ ---
125
+
126
+ ## What is the no-fabrication guarantee?
127
+
128
+ <!-- AEO Answer Capsule โ€” 74 words -->
129
+ 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.
130
+ <!-- End AEO Capsule -->
131
+
132
+ 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*.
133
+
134
+ Here's a genuine slice of the generated ATS keyword report from the example run โ€” note what it refuses to do:
135
+
136
+ ```markdown
137
+ ## Missing keywords
138
+ - dbt
139
+ - Product analytics / product metrics
140
+ - Financial services / fintech
141
+
142
+ ## How to add the missing terms โ€” truthfully
143
+ - dbt โ€” Do not add โ€” no evidence in your CV. This is a genuine gap.
144
+ - Product analytics โ€” Partial. You genuinely define metrics and own analytics
145
+ end to end, so you may reframe your existing work as "analytics ownership and
146
+ metric definition" โ€” but do not label it "product analytics" unless true.
147
+ - Financial services / fintech โ€” Do not add โ€” no evidence in your CV.
148
+
149
+ ## Coverage summary
150
+ Covered 9 of 15 key terms.
151
+ ```
152
+
153
+ > ๐Ÿšฉ **"Do not add โ€” this is a genuine gap."**
154
+ > No other CV tool says that to you. That sentence is the product.
155
+
156
+ ---
157
+
158
+ ## How do I install OfferPrinter?
159
+
160
+ <!-- AEO Answer Capsule โ€” 57 words -->
161
+ 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.
162
+ <!-- End AEO Capsule -->
163
+
164
+ ### โšก The fastest way
165
+
166
+ ```bash
167
+ pipx install offerprinter # or: uvx offerprinter (no install at all)
168
+ export ANTHROPIC_API_KEY="sk-ant-..."
169
+ offerprinter --cv ~/cv.pdf --jd "https://careers.company.com/jobs/123"
170
+ ```
171
+
172
+ That's it. No signup, no credit card, no "start your free trial".
173
+
174
+ ### ๐Ÿ“ฆ Every other way
175
+
176
+ <details>
177
+ <summary><b>pip</b></summary>
178
+
179
+ ```bash
180
+ pip install offerprinter # CLI only
181
+ pip install "offerprinter[web]" # + the Streamlit web UI
182
+ ```
183
+ </details>
184
+
185
+ <details>
186
+ <summary><b>No Python at all โ€” standalone binary</b></summary>
187
+
188
+ Download the archive for your platform from the
189
+ [latest release](https://github.com/mohitagw15856/OfferPrinter/releases/latest),
190
+ unpack it, and run `./offerprinter`. Nothing else to install.
191
+ </details>
192
+
193
+ <details>
194
+ <summary><b>Docker</b></summary>
195
+
196
+ ```bash
197
+ # Web UI at http://localhost:8501
198
+ docker run --rm -p 8501:8501 -e ANTHROPIC_API_KEY="sk-ant-..." \
199
+ ghcr.io/mohitagw15856/offerprinter:latest
200
+
201
+ # Or the CLI, against files in the current directory
202
+ docker run --rm -v "$PWD:/work" -w /work -e ANTHROPIC_API_KEY="sk-ant-..." \
203
+ ghcr.io/mohitagw15856/offerprinter:latest \
204
+ offerprinter --cv my-cv.pdf --jd-file jd.txt
205
+ ```
206
+ </details>
207
+
208
+ <details>
209
+ <summary><b>Homebrew</b></summary>
210
+
211
+ ```bash
212
+ brew tap mohitagw15856/tap
213
+ brew install offerprinter
214
+ ```
215
+ </details>
216
+
217
+ <details>
218
+ <summary><b>From source</b></summary>
219
+
220
+ ```bash
221
+ git clone https://github.com/mohitagw15856/OfferPrinter.git
222
+ cd OfferPrinter
223
+ uv sync # or: pip install -e ".[dev]"
224
+ python cli.py --cv examples/sample_cv.md --jd-file examples/sample_jd.md
225
+ ```
226
+ </details>
227
+
228
+ **Not sure it's worth the API spend?** Find out before you pay for anything:
229
+
230
+ ```bash
231
+ offerprinter --cv ~/cv.pdf --jd-file jd.txt --dry-run
232
+ # โ†’ Estimated tokens: ~15,596 ยท Estimated cost: $0.041 (โ‰ˆ ยฃ0.032)
233
+ ```
234
+
235
+ ---
236
+
237
+ ## Which way should I run OfferPrinter โ€” CLI, web UI, or agent?
238
+
239
+ <!-- AEO Answer Capsule โ€” 68 words -->
240
+ 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.
241
+ <!-- End AEO Capsule -->
242
+
243
+ ### 1. โŒจ๏ธ CLI โ€” nicest for scripting
244
+
245
+ ```bash
246
+ offerprinter --cv cv.pdf --jd "https://..." # JD from a URL
247
+ offerprinter --cv cv.docx --jd-file jd.txt # JD from a file
248
+ offerprinter --cv cv.pdf --jd-dir ./jobs # a package per job, in one go
249
+ offerprinter --cv-text "paste CV hereโ€ฆ" --jd-file jd.txt
250
+ ```
251
+
252
+ `offerprinter --help` lists every option. The ones you'll actually use:
253
+
254
+ | Option | Does |
255
+ |--------|------|
256
+ | `--cv` / `--cv-text` | CV as a file (`.pdf` `.docx` `.md` `.txt`) or pasted text. |
257
+ | `--jd` / `--jd-file` / `--jd-dir` | Job as a URL, pasted text, a file, or a whole folder. |
258
+ | `--provider` | `anthropic` (default), `openai`, `gemini`, `kimi`, `ollama`. |
259
+ | `--model` | Override the model for this run. |
260
+ | `--formats` | `md,docx,pdf` โ€” any combination. |
261
+ | `--locale` | `UK` (default) or `US` English. |
262
+ | `--roast` | Also print a blunt critique of your CV. |
263
+ | `--dry-run` | Forecast tokens and cost. Calls nothing. |
264
+ | `--sequential` | One document at a time instead of in parallel. |
265
+ | `--output-dir` / `-o` | Where to write (default `./output`). |
266
+ | `--no-track` / `--no-animation` | Skip the local history / the printer animation. |
267
+
268
+ Plus four subcommands: `offerprinter roast`, `list`, `stats`, and `status`.
269
+
270
+ ### 2. ๐Ÿ–ฅ Web UI โ€” nicest for most people
271
+
272
+ ```bash
273
+ pip install "offerprinter[web]"
274
+ streamlit run app.py
275
+ ```
276
+
277
+ Opens at <http://localhost:8501>. Paste or upload your CV, paste the job (text or URL), pick a provider, and hit **Print my application**.
278
+
279
+ 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.
280
+
281
+ ### 3. ๐Ÿค– Agent skill โ€” for Claude Code, Hermes, and friends
282
+
283
+ 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.
284
+
285
+ ---
286
+
287
+ ## How does the fit score work?
288
+
289
+ <!-- AEO Answer Capsule โ€” 70 words -->
290
+ 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.
291
+ <!-- End AEO Capsule -->
292
+
293
+ ```
294
+ ๐ŸŽฏ Fit score
295
+ 74/100 โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ Strong
296
+ A genuinely competitive application. Send it.
297
+ Real gaps: dbt, financial services domain
298
+ ```
299
+
300
+ | Score | Band | What it means |
301
+ |-------|------|---------------|
302
+ | 85โ€“100 | **Exceptional** | Apply today. You are what they wrote the advert for. |
303
+ | 70โ€“84 | **Strong** | A genuinely competitive application. Send it. |
304
+ | 55โ€“69 | **Credible** | Worth applying โ€” lead hard with your strongest match. |
305
+ | 40โ€“54 | **Stretch** | A reach. Apply if you want it, and address the gaps head-on. |
306
+ | 0โ€“39 | **Long shot** | Big gaps. Consider a closer role, or close a gap first. |
307
+
308
+ The scoring prompt is explicitly told not to be generous: *"the candidate needs the truth to decide where to spend their evening."*
309
+
310
+ ---
311
+
312
+ ## Can OfferPrinter tell me what's wrong with my CV?
313
+
314
+ <!-- AEO Answer Capsule โ€” 60 words -->
315
+ 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.
316
+ <!-- End AEO Capsule -->
317
+
318
+ ```bash
319
+ offerprinter roast --cv ~/cv.pdf
320
+ ```
321
+
322
+ 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.
323
+
324
+ ---
325
+
326
+ ## Can OfferPrinter track my job applications?
327
+
328
+ <!-- AEO Answer Capsule โ€” 57 words -->
329
+ 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.
330
+ <!-- End AEO Capsule -->
331
+
332
+ ```bash
333
+ offerprinter list # everything you've printed
334
+ offerprinter status northbank-senior-analyst offer # ๐Ÿ†
335
+ offerprinter stats
336
+ ```
337
+
338
+ ```
339
+ ๐Ÿ“Š Your job hunt
340
+ Applications printed 12
341
+ Different companies 9
342
+ Average fit score 68.4
343
+ Best fit 91 (Data Lead at Meridian)
344
+ Total spend $0.41 (โ‰ˆ ยฃ0.32)
345
+
346
+ Achievements
347
+ โœ“ ๐Ÿ–จ First Print ยท โœ“ ๐Ÿ”Ÿ Double Digits ยท โœ“ ๐ŸŽฏ Bullseye ยท โœ“ ๐Ÿค In The Room
348
+ ยท ๐Ÿ† Offer Printed โ€” Marked an application as an offer. Congratulations.
349
+ ```
350
+
351
+ Job hunting is a long grind with almost no feedback loop. This is the scoreboard.
352
+
353
+ ---
354
+
355
+ ## Which AI model should I use with OfferPrinter?
356
+
357
+ <!-- AEO Answer Capsule โ€” 65 words -->
358
+ 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.
359
+ <!-- End AEO Capsule -->
360
+
361
+ | Provider | Default model | Set your key via |
362
+ |----------|---------------|------------------|
363
+ | ๐ŸŸฃ **Anthropic (Claude)** โ€” default | `claude-haiku-4-5-20251001` | `ANTHROPIC_API_KEY` |
364
+ | ๐ŸŸข **OpenAI** | `gpt-4o-mini` | `OPENAI_API_KEY` |
365
+ | ๐Ÿ”ต **Google Gemini** | `gemini-1.5-flash` | `GEMINI_API_KEY` (or `GOOGLE_API_KEY`) |
366
+ | ๐ŸŸ  **Moonshot Kimi** | `moonshot-v1-8k` | `MOONSHOT_API_KEY` |
367
+ | ๐Ÿ  **Ollama** โ€” fully local | `llama3.1` | *no key needed* |
368
+
369
+ Switching provider is **one line** in `config.toml`:
370
+
371
+ ```toml
372
+ [llm]
373
+ provider = "openai" # was "anthropic"
374
+ ```
375
+
376
+ โ€ฆor per run: `offerprinter --provider openai --cv โ€ฆ --jd โ€ฆ`.
377
+
378
+ **Want your CV never to leave your machine at all?**
379
+
380
+ ```bash
381
+ ollama pull llama3.1
382
+ offerprinter --provider ollama --cv ~/cv.pdf --jd-file jd.txt
383
+ ```
384
+
385
+ Every run reports what it cost, so nothing is a surprise:
386
+
387
+ ```
388
+ 7 calls ยท 24,318 tokens (18,204 in / 6,114 out) ยท $0.038 (โ‰ˆ ยฃ0.030)
389
+ ```
390
+
391
+ ---
392
+
393
+ ## How do I configure OfferPrinter?
394
+
395
+ <!-- AEO Answer Capsule โ€” 57 words -->
396
+ 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.
397
+ <!-- End AEO Capsule -->
398
+
399
+ ```toml
400
+ [llm]
401
+ provider = "anthropic" # anthropic | openai | gemini | kimi | ollama
402
+ model = "" # blank = provider default
403
+ api_key = "" # prefer the per-provider env var
404
+ base_url = "" # override endpoint (proxy / gateway / local server)
405
+ temperature = 0.2 # low = faithful, deterministic
406
+ max_tokens = 4096
407
+ timeout = 120
408
+ max_retries = 3 # a single 429 shouldn't kill a five-document run
409
+ retry_backoff = 1.5
410
+
411
+ [output]
412
+ locale = "UK" # UK | US
413
+ dir = "./output"
414
+ formats = ["md", "docx", "pdf"] # any combination
415
+ track = true # local history in ~/.offerprinter/
416
+
417
+ [generation]
418
+ tailored_cv = true
419
+ cover_letter = true
420
+ fit_memo = true
421
+ ats_report = true
422
+ interview_prep = true
423
+ fit_score = true # one extra cheap call for the 0-100 score
424
+ parallel = true # generate all five at once
425
+ max_workers = 5
426
+
427
+ # [pricing] # override list prices with your actual rates
428
+ # "claude-haiku-4-5-20251001" = { input = 1.0, output = 5.0 }
429
+ ```
430
+
431
+ ---
432
+
433
+ ## How is OfferPrinter built?
434
+
435
+ <!-- AEO Answer Capsule โ€” 66 words -->
436
+ 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.
437
+ <!-- End AEO Capsule -->
438
+
439
+ ```
440
+ offerprinter/
441
+ โ”œโ”€โ”€ config.py # config: env vars โ†’ config.toml โ†’ defaults
442
+ โ”œโ”€โ”€ pricing.py # token prices โ†’ "this run cost ยฃ0.03"
443
+ โ”œโ”€โ”€ models/ # typed data models that flow through the pipeline
444
+ โ”œโ”€โ”€ llm/ # provider-agnostic LLM layer (one interface, 5 providers)
445
+ โ”‚ โ”œโ”€โ”€ base.py # the interface + retries/backoff + token accounting
446
+ โ”‚ โ”œโ”€โ”€ anthropic_provider.py / openai_provider.py / gemini_provider.py
447
+ โ”‚ โ”œโ”€โ”€ kimi_provider.py / ollama_provider.py
448
+ โ”‚ โ””โ”€โ”€ factory.py # config โ†’ concrete provider
449
+ โ”œโ”€โ”€ prompts/ # ALL prompt templates โ€” audit the no-fabrication rules here
450
+ โ”œโ”€โ”€ services/ # cv_parser ยท jd_fetcher ยท generator ยท writer ยท pdf_writer ยท tracker
451
+ โ”œโ”€โ”€ ui/printer.py # the ASCII printer animation
452
+ โ”œโ”€โ”€ controllers/pipeline.py # the end-to-end flow, emitting progress events
453
+ โ””โ”€โ”€ cli.py # CLI (Typer)
454
+ app.py # web UI (Streamlit)
455
+ docs/skill/SKILL.md # agent entry point
456
+ ```
457
+
458
+ 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.
459
+
460
+ Want to change how OfferPrinter writes? It's all in [`offerprinter/prompts/templates.py`](offerprinter/prompts/templates.py).
461
+ Want a new provider? Add one subclass in `offerprinter/llm/` โ€” nothing else changes.
462
+
463
+ ---
464
+
465
+ ## FAQ
466
+
467
+ ### Does OfferPrinter upload my CV anywhere?
468
+
469
+ <!-- AEO Answer Capsule โ€” 58 words -->
470
+ 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.
471
+ <!-- End AEO Capsule -->
472
+
473
+ ### Will OfferPrinter lie to make me look better?
474
+
475
+ <!-- AEO Answer Capsule โ€” 55 words -->
476
+ 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.
477
+ <!-- End AEO Capsule -->
478
+
479
+ ### What CV file formats does OfferPrinter support?
480
+
481
+ <!-- AEO Answer Capsule โ€” 61 words -->
482
+ 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.
483
+ <!-- End AEO Capsule -->
484
+
485
+ ### Can OfferPrinter read a job posting from a URL?
486
+
487
+ <!-- AEO Answer Capsule โ€” 56 words -->
488
+ 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.
489
+ <!-- End AEO Capsule -->
490
+
491
+ ### How much does a single OfferPrinter run cost?
492
+
493
+ <!-- AEO Answer Capsule โ€” 60 words -->
494
+ 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. Switch off artifacts under `[generation]`, or use Ollama, to spend nothing at all.
495
+ <!-- End AEO Capsule -->
496
+
497
+ ### How long does a run take?
498
+
499
+ <!-- AEO Answer Capsule โ€” 56 words -->
500
+ 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.
501
+ <!-- End AEO Capsule -->
502
+
503
+ ### Does OfferPrinter write British or American English?
504
+
505
+ <!-- AEO Answer Capsule โ€” 52 words -->
506
+ 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.
507
+ <!-- End AEO Capsule -->
508
+
509
+ ### Is my CV used to train an AI model?
510
+
511
+ <!-- AEO Answer Capsule โ€” 55 words -->
512
+ 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.
513
+ <!-- End AEO Capsule -->
514
+
515
+ ---
516
+
517
+ ## How can I contribute to OfferPrinter?
518
+
519
+ <!-- AEO Answer Capsule โ€” 57 words -->
520
+ Issues and pull requests are welcome โ€” see CONTRIBUTING for the full guide. Run `ruff check .`, `ruff format .` and `pytest` before pushing, since CI enforces all three across Python 3.11, 3.12 and 3.13. Good first contributions include a new LLM provider subclass, a new output format, or better job description extraction from hostile job boards.
521
+ <!-- End AEO Capsule -->
522
+
523
+ Especially wanted:
524
+
525
+ - ๐Ÿ”Œ **A new provider** โ€” one subclass in `offerprinter/llm/`, and you're done.
526
+ - ๐ŸŒ **Better JD extraction** โ€” job boards are hostile; make the fetcher smarter.
527
+ - ๐Ÿ“„ **New output formats** โ€” LaTeX, plain-text ATS mode, ODT.
528
+ - ๐Ÿงช **Tests** โ€” the suite is offline and runs in under a second; keep it that way.
529
+
530
+ Read [CONTRIBUTING.md](CONTRIBUTING.md) ยท [SECURITY.md](SECURITY.md) ยท [CHANGELOG.md](CHANGELOG.md) ยท [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
531
+
532
+ Licensed under the [MIT License](LICENSE). Use it, fork it, ship it.
533
+
534
+ ---
535
+
536
+ <div align="center">
537
+
538
+ **โญ If OfferPrinter saved you an evening, star the repo โ€” it's the only payment it takes.**
539
+
540
+ *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.*
541
+
542
+ </div>
@@ -0,0 +1,32 @@
1
+ offerprinter/__init__.py,sha256=uctOFWdMnYNEmwh3TUXDvmeSE5frI0b6mb07Rco1HGg,266
2
+ offerprinter/cli.py,sha256=SHBS_8ZjQ91mY6kim3GanbXXyKNcrR-QXoyqqLL7WpY,19731
3
+ offerprinter/config.py,sha256=KUzk0AmrrQl6FTpdBezYOkuh8Ubg3PFuafYFY3_5oXU,6416
4
+ offerprinter/pricing.py,sha256=eAjwjcCgQ4YBzF6kqtlMlNK8m75ZEcr5Zcoe4N3ezAk,3461
5
+ offerprinter/controllers/__init__.py,sha256=1bue-WCiJjbKs7h7RqRNHGXG7J5MC3K4wHqtgNNM3V0,188
6
+ offerprinter/controllers/pipeline.py,sha256=m4GzUDl1AbxNHhJ0zgDzcd_MFzYXvaquuy9ac4lgabs,5120
7
+ offerprinter/llm/__init__.py,sha256=Lx9c1N-ujONH7pC-B6dBycpm2ohxW1ACWuJm9PLMczs,413
8
+ offerprinter/llm/anthropic_provider.py,sha256=yr6vQLOPSgEy7PI9QOHgZqgCUNBsuqlw-zlIa_P8kLk,1425
9
+ offerprinter/llm/base.py,sha256=UgBlgeTBra-Q074MtI_CmfEKj7sIlHp1rsWya4l0_os,4937
10
+ offerprinter/llm/factory.py,sha256=VVmY7v05fssebQ_QQrvFy08V5S2WGq3khe1KwUFtDto,1244
11
+ offerprinter/llm/gemini_provider.py,sha256=-Bp2rBYtoX_8jEyALXXMRaP56jVx6dF5UsgHDRbfHyU,1424
12
+ offerprinter/llm/kimi_provider.py,sha256=W2phTUIaXfXFz57kVjwH0H7_scHHuVrUxq1rrcZ2nDI,459
13
+ offerprinter/llm/ollama_provider.py,sha256=2T9gYXLYgmsEJQjhR_vFG_DqpZ0B3XrdlhYsPtsr14Q,812
14
+ offerprinter/llm/openai_provider.py,sha256=CBuAfIt3n7QALUtNZP52ityFfudMF21imIHq5NTg2_o,1419
15
+ offerprinter/models/__init__.py,sha256=FSOyL7iCMYtTQABOSvJLYAKVeaS_GcgrY6vLZPvGVO0,403
16
+ offerprinter/models/schemas.py,sha256=hGjUkATl9vbj0aYpI38F-AQ59KOU3i6-JfIxydB1Kd8,6138
17
+ offerprinter/prompts/__init__.py,sha256=98sCZoPANk2kUHa827MifCOGYxGPEgV6VZW9Ln07tag,844
18
+ offerprinter/prompts/templates.py,sha256=ZRk-_YknBlHe7w-Zm9djk-PPTT1Vs7oY6nLhRA-L-1s,13127
19
+ offerprinter/services/__init__.py,sha256=IQpnX1gS-7zl2CoZJJTSQy8nbxRQOexA9x4_Oyd3Ojw,462
20
+ offerprinter/services/cv_parser.py,sha256=i0oEUtOIL_E83Vy4OZq16KVQGDp_5S2w03xgctZNfCY,2556
21
+ offerprinter/services/generator.py,sha256=-x-r_-BSslEhlHYxkzphwf19VmJi3OaogcEJEWGmKZY,6739
22
+ offerprinter/services/jd_fetcher.py,sha256=bO2XiRwBJAC_G-7dHo36CjQ4SkraTnNjh3RE2AfNKCM,3016
23
+ offerprinter/services/pdf_writer.py,sha256=-T2Y1dGN99as7c-T58cDXmr6m5o0UVNVMl-Ndvr1_Pw,11353
24
+ offerprinter/services/tracker.py,sha256=9ZAr5aB7aThoWHeZNjQQFfH2pyYkmNYwU4VzGgSUL4c,8014
25
+ offerprinter/services/writer.py,sha256=fmR0JKhEMErGDfSXfMOJ6YQBOSSdZ_ZMOMtVARm6Bww,4892
26
+ offerprinter/ui/__init__.py,sha256=hNKy7vuHpdSYcj2ap5YFN5DSHYBMQJWr-NON67k64Y4,293
27
+ offerprinter/ui/printer.py,sha256=THbc4D-m_mhykBToK-2d6uJioLkaUsBncZmYH19l4kM,6349
28
+ offerprinter-0.2.0.dist-info/METADATA,sha256=jL3XNAIx92jKedlBhrJmpd-1eUJ7eENSdXYgFgKZTgg,27408
29
+ offerprinter-0.2.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
30
+ offerprinter-0.2.0.dist-info/entry_points.txt,sha256=W9thxnRwhFgHCNfZMxxNJ-n7c7i3MPJlNa6NEjfmvmg,81
31
+ offerprinter-0.2.0.dist-info/licenses/LICENSE,sha256=ngSyWJUYHujbvVsNtQzWe1Eu1zCBWlbAKk5y3svXWAU,1082
32
+ offerprinter-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ offerprinter = offerprinter.cli:app
3
+ opr = offerprinter.cli:app
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OfferPrinter contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.