offerprinter 0.2.0__tar.gz → 0.3.0__tar.gz

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.
Files changed (73) hide show
  1. {offerprinter-0.2.0 → offerprinter-0.3.0}/.gitignore +3 -1
  2. {offerprinter-0.2.0 → offerprinter-0.3.0}/CHANGELOG.md +76 -1
  3. {offerprinter-0.2.0 → offerprinter-0.3.0}/PKG-INFO +191 -7
  4. {offerprinter-0.2.0 → offerprinter-0.3.0}/README.md +190 -6
  5. {offerprinter-0.2.0 → offerprinter-0.3.0}/config.example.toml +23 -0
  6. {offerprinter-0.2.0 → offerprinter-0.3.0}/docs/skill/SKILL.md +33 -0
  7. offerprinter-0.3.0/evals/README.md +75 -0
  8. offerprinter-0.3.0/extension/README.md +57 -0
  9. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/__init__.py +1 -1
  10. offerprinter-0.3.0/offerprinter/cli.py +1015 -0
  11. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/config.py +4 -0
  12. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/controllers/pipeline.py +38 -5
  13. offerprinter-0.3.0/offerprinter/mcp_server.py +345 -0
  14. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/models/schemas.py +122 -0
  15. offerprinter-0.3.0/offerprinter/prompts/__init__.py +58 -0
  16. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/prompts/templates.py +273 -0
  17. offerprinter-0.3.0/offerprinter/services/cache.py +152 -0
  18. offerprinter-0.3.0/offerprinter/services/differ.py +193 -0
  19. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/services/generator.py +77 -0
  20. offerprinter-0.3.0/offerprinter/services/jd_fetcher.py +216 -0
  21. offerprinter-0.3.0/offerprinter/services/ranker.py +95 -0
  22. offerprinter-0.3.0/offerprinter/services/redactor.py +174 -0
  23. offerprinter-0.3.0/offerprinter/services/verifier.py +367 -0
  24. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/services/writer.py +23 -0
  25. {offerprinter-0.2.0 → offerprinter-0.3.0}/pyproject.toml +1 -1
  26. offerprinter-0.3.0/tests/test_new_services.py +400 -0
  27. offerprinter-0.3.0/tests/test_verifier.py +240 -0
  28. offerprinter-0.2.0/offerprinter/cli.py +0 -523
  29. offerprinter-0.2.0/offerprinter/prompts/__init__.py +0 -32
  30. offerprinter-0.2.0/offerprinter/services/jd_fetcher.py +0 -86
  31. {offerprinter-0.2.0 → offerprinter-0.3.0}/LICENSE +0 -0
  32. {offerprinter-0.2.0 → offerprinter-0.3.0}/app.py +0 -0
  33. {offerprinter-0.2.0 → offerprinter-0.3.0}/cli.py +0 -0
  34. {offerprinter-0.2.0 → offerprinter-0.3.0}/deploy/README.md +0 -0
  35. {offerprinter-0.2.0 → offerprinter-0.3.0}/deploy/huggingface/README.md +0 -0
  36. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/README.md +0 -0
  37. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/ats-keyword-report.docx +0 -0
  38. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/ats-keyword-report.md +0 -0
  39. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/cover-letter.docx +0 -0
  40. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/cover-letter.md +0 -0
  41. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/fit-memo.docx +0 -0
  42. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/fit-memo.md +0 -0
  43. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/full-package.docx +0 -0
  44. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/full-package.md +0 -0
  45. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/interview-prep-pack.docx +0 -0
  46. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/interview-prep-pack.md +0 -0
  47. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/tailored-cv.docx +0 -0
  48. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/output/northbank-senior-product-analyst/tailored-cv.md +0 -0
  49. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/sample_cv.md +0 -0
  50. {offerprinter-0.2.0 → offerprinter-0.3.0}/examples/sample_jd.md +0 -0
  51. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/controllers/__init__.py +0 -0
  52. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/llm/__init__.py +0 -0
  53. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/llm/anthropic_provider.py +0 -0
  54. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/llm/base.py +0 -0
  55. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/llm/factory.py +0 -0
  56. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/llm/gemini_provider.py +0 -0
  57. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/llm/kimi_provider.py +0 -0
  58. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/llm/ollama_provider.py +0 -0
  59. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/llm/openai_provider.py +0 -0
  60. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/models/__init__.py +0 -0
  61. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/pricing.py +0 -0
  62. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/services/__init__.py +0 -0
  63. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/services/cv_parser.py +0 -0
  64. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/services/pdf_writer.py +0 -0
  65. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/services/tracker.py +0 -0
  66. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/ui/__init__.py +0 -0
  67. {offerprinter-0.2.0 → offerprinter-0.3.0}/offerprinter/ui/printer.py +0 -0
  68. {offerprinter-0.2.0 → offerprinter-0.3.0}/tests/__init__.py +0 -0
  69. {offerprinter-0.2.0 → offerprinter-0.3.0}/tests/conftest.py +0 -0
  70. {offerprinter-0.2.0 → offerprinter-0.3.0}/tests/test_offerprinter.py +0 -0
  71. {offerprinter-0.2.0 → offerprinter-0.3.0}/tests/test_pdf_writer.py +0 -0
  72. {offerprinter-0.2.0 → offerprinter-0.3.0}/tests/test_pricing_and_retries.py +0 -0
  73. {offerprinter-0.2.0 → offerprinter-0.3.0}/tests/test_tracker.py +0 -0
@@ -1,6 +1,8 @@
1
1
  # --- OfferPrinter ---
2
2
  # Never commit secrets or generated user output.
3
- config.toml
3
+ # Anchored to the repo root: an unanchored "config.toml" also matches nested
4
+ # files like .streamlit/config.toml, which is checked in on purpose.
5
+ /config.toml
4
6
  .env
5
7
  output/
6
8
  !examples/output/
@@ -7,6 +7,80 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] — 2026-08-20
11
+
12
+ The "prove it" release. v0.2.0 made OfferPrinter installable; this one makes its
13
+ central claim checkable, and extends the tool across the rest of a job hunt.
14
+
15
+ ### Added
16
+
17
+ **Proving the guarantee**
18
+ - **Fabrication verifier.** Every number, date and named entity in the tailored
19
+ CV and cover letter is checked against your source CV, and the tailored CV is
20
+ cross-checked against the ATS report's own list of gaps — so a package that
21
+ contradicts itself is caught automatically. Writes `fabrication-check.md`.
22
+ `--strict` exits 3 if anything is unverified, which makes it usable as a gate.
23
+ Until now "never fabricate" was an instruction in a prompt; now it is an
24
+ assertion the tool makes about its own output.
25
+ - **`offerprinter verify`** re-checks an already-generated package — after you
26
+ have hand-edited it, or in CI. Makes no API calls, so it is free and offline.
27
+ - **Tailoring diff.** `tailoring-diff.md` shows exactly what changed: which
28
+ bullets were reworded, what was added or dropped, and which vocabulary is new.
29
+ The verifier proves nothing was invented; the diff shows what was done.
30
+ - **Eval harness** (`evals/`, `scripts/run_evals.py`). Golden cases with
31
+ per-case assertions about which gaps must be named and which terms must never
32
+ be claimed, plus an LLM-as-judge pass scoring grounding, specificity, honesty,
33
+ usefulness and format. The deterministic half runs in CI on every PR; the
34
+ judge runs on demand. Prompt changes are no longer a guess.
35
+
36
+ **New commands**
37
+ - **`offerprinter rank`** — score a folder of adverts and rank them by fit
38
+ without generating a single document. Two cheap calls per job; triage twenty
39
+ roles for a couple of pence, then print packages only for the ones worth it.
40
+ - **`offerprinter followup`** — thank-you email, recruiter message, LinkedIn
41
+ note (hard-capped at 300 characters) or a polite nudge. Written from your CV
42
+ and your notes; it will not invent a conversation that did not happen.
43
+ - **`offerprinter practice`** — an interactive mock interview. It asks, you
44
+ answer, it critiques and rewrites your answer using only your real experience,
45
+ then summarises the patterns worth fixing.
46
+ - **`offerprinter mcp`** — run as an MCP server on stdio, so Claude Desktop,
47
+ Claude Code and other agents can call OfferPrinter natively and get structured
48
+ results back. Hand-rolled JSON-RPC; no new dependencies.
49
+ - **`offerprinter cache stats` / `cache clear`**.
50
+
51
+ **Privacy and job-board reality**
52
+ - **PII redaction** (`--redact`). Your name, email, phone and profile links are
53
+ replaced with placeholders before the provider sees anything, and restored in
54
+ the output. The model keeps every employer, date and metric it needs.
55
+ - **`--jd-clipboard`** and a **browser extension** (`extension/`). Job boards
56
+ are the most common reason a run fails; the advert is already rendered in your
57
+ tab, so read it from there. The extension has no host permissions and makes no
58
+ network requests.
59
+ - **schema.org `JobPosting` JSON-LD extraction.** Many boards embed the whole
60
+ advert as structured data — cleaner than anything scraped, and it supplies the
61
+ company and job title for free.
62
+
63
+ **Cost**
64
+ - **Response caching.** Keyed on provider, model, temperature and both prompts,
65
+ so anything that could change the answer correctly misses. Re-running after a
66
+ CV tweak is now mostly free.
67
+
68
+ ### Fixed
69
+
70
+ - Multi-word entities could match across a line break, producing nonsense claims
71
+ glued together from two sentences.
72
+ - A level-two heading (`## Missing keywords`) was not recognised as a sentence
73
+ start, so its first word was treated as a named entity.
74
+ - A capitalised word at the very start of a bullet ("Used SQL daily") was read
75
+ as an entity, because the sentence-start pattern only handled bullets that
76
+ followed a newline, not one at the start of the text.
77
+
78
+ ### Changed
79
+
80
+ - Config gains `[output] redact`, `[generation] verify`, `diff` and `cache`.
81
+ - The test suite grew from 73 to 143 tests, still fully offline.
82
+
83
+
10
84
  ## [0.2.0] — 2026-08-18
11
85
 
12
86
  The "actually installable, and quite a lot faster" release.
@@ -99,6 +173,7 @@ First release — "First Print".
99
173
  - Config via env vars, `config.toml`, or defaults.
100
174
  - Dockerfile and docker-compose, offline test suite, ruff lint.
101
175
 
102
- [Unreleased]: https://github.com/mohitagw15856/OfferPrinter/compare/v0.2.0...HEAD
176
+ [Unreleased]: https://github.com/mohitagw15856/OfferPrinter/compare/v0.3.0...HEAD
177
+ [0.3.0]: https://github.com/mohitagw15856/OfferPrinter/compare/v0.2.0...v0.3.0
103
178
  [0.2.0]: https://github.com/mohitagw15856/OfferPrinter/compare/v0.1.0...v0.2.0
104
179
  [0.1.0]: https://github.com/mohitagw15856/OfferPrinter/releases/tag/v0.1.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: offerprinter
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
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
5
  Project-URL: Homepage, https://github.com/mohitagw15856/OfferPrinter
6
6
  Project-URL: Repository, https://github.com/mohitagw15856/OfferPrinter
@@ -153,6 +153,28 @@ Covered 9 of 15 key terms.
153
153
  > 🚩 **"Do not add — this is a genuine gap."**
154
154
  > No other CV tool says that to you. That sentence is the product.
155
155
 
156
+ ### And OfferPrinter checks its own homework
157
+
158
+ <!-- AEO Answer Capsule — 75 words -->
159
+ 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.
160
+ <!-- End AEO Capsule -->
161
+
162
+ ```
163
+ ✓ Fabrication check: all 63 checkable claims trace back to your CV.
164
+ ```
165
+
166
+ And when something slips through:
167
+
168
+ ```
169
+ ⚠ Fabrication check: 2 of 64 claims could not be traced to your CV (2 high severity).
170
+ • '41%' — this figure does not appear in your CV
171
+ • 'dbt' — your own ATS report lists this as a gap
172
+ ```
173
+
174
+ Use `--strict` to exit non-zero when anything is unverified, so it can gate a
175
+ script. `offerprinter verify <folder>` re-checks a package later — after you
176
+ have hand-edited it, say — and makes no API calls at all, so it is free.
177
+
156
178
  ---
157
179
 
158
180
  ## How do I install OfferPrinter?
@@ -212,6 +234,19 @@ docker run --rm -v "$PWD:/work" -w /work -e ANTHROPIC_API_KEY="sk-ant-..." \
212
234
  brew tap mohitagw15856/tap
213
235
  brew install offerprinter
214
236
  ```
237
+
238
+ Homebrew 6 requires third-party taps to be trusted before it will use them.
239
+ If `brew doctor` says the tap is untrusted, either trust this one formula:
240
+
241
+ ```bash
242
+ brew trust --formula mohitagw15856/tap/offerprinter
243
+ ```
244
+
245
+ …or the whole tap, with `brew trust --tap mohitagw15856/tap`.
246
+
247
+ Homebrew builds every Python dependency from source, so the first install
248
+ compiles `pydantic-core` with Rust and takes a few minutes. `pipx install
249
+ offerprinter` is faster if you don't specifically want Homebrew to manage it.
215
250
  </details>
216
251
 
217
252
  <details>
@@ -255,17 +290,32 @@ offerprinter --cv-text "paste CV here…" --jd-file jd.txt
255
290
  |--------|------|
256
291
  | `--cv` / `--cv-text` | CV as a file (`.pdf` `.docx` `.md` `.txt`) or pasted text. |
257
292
  | `--jd` / `--jd-file` / `--jd-dir` | Job as a URL, pasted text, a file, or a whole folder. |
293
+ | `--jd-clipboard` | Read the advert from your clipboard. The fix for hostile job boards. |
258
294
  | `--provider` | `anthropic` (default), `openai`, `gemini`, `kimi`, `ollama`. |
259
295
  | `--model` | Override the model for this run. |
260
296
  | `--formats` | `md,docx,pdf` — any combination. |
261
297
  | `--locale` | `UK` (default) or `US` English. |
262
298
  | `--roast` | Also print a blunt critique of your CV. |
263
299
  | `--dry-run` | Forecast tokens and cost. Calls nothing. |
300
+ | `--redact` | Strip your name, email and phone before the provider sees anything. |
301
+ | `--strict` | Exit non-zero if the fabrication check finds anything unverified. |
264
302
  | `--sequential` | One document at a time instead of in parallel. |
303
+ | `--no-verify` / `--no-cache` | Skip the fabrication check / ignore cached responses. |
265
304
  | `--output-dir` / `-o` | Where to write (default `./output`). |
266
305
  | `--no-track` / `--no-animation` | Skip the local history / the printer animation. |
267
306
 
268
- Plus four subcommands: `offerprinter roast`, `list`, `stats`, and `status`.
307
+ Plus nine subcommands:
308
+
309
+ | Command | Does |
310
+ |---|---|
311
+ | `rank` | Score a folder of adverts and rank them, writing no documents. |
312
+ | `followup` | Thank-you email, recruiter message, LinkedIn note, or a nudge. |
313
+ | `practice` | An interactive mock interview that critiques your answers. |
314
+ | `verify` | Re-check a generated package against your CV. No API calls. |
315
+ | `roast` | Blunt critique of your CV's writing. |
316
+ | `list` / `stats` / `status` | Your local application history. |
317
+ | `mcp` | Run as an MCP server so agents can call OfferPrinter. |
318
+ | `cache` | Inspect or clear the response cache. |
269
319
 
270
320
  ### 2. 🖥 Web UI — nicest for most people
271
321
 
@@ -352,6 +402,124 @@ Job hunting is a long grind with almost no feedback loop. This is the scoreboard
352
402
 
353
403
  ---
354
404
 
405
+ ## How do I choose which jobs to apply for?
406
+
407
+ <!-- AEO Answer Capsule — 62 words -->
408
+ 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.
409
+ <!-- End AEO Capsule -->
410
+
411
+ ```bash
412
+ offerprinter rank --cv ~/cv.pdf --jd-dir ./jobs
413
+ ```
414
+
415
+ ```
416
+ 🎯 Roles ranked by fit
417
+ ┏━━━┳━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
418
+ ┃ # ┃ Fit ┃ Band ┃ Role ┃ Company ┃ Real gaps ┃
419
+ ┡━━━╇━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
420
+ │ 1 │ 88 │ Exceptional │ Analytics Lead │ Meridian │ │
421
+ │ 2 │ 74 │ Strong │ Senior Product Analyst │ NorthBank │ dbt, fintech │
422
+ │ 3 │ 41 │ Stretch │ Data Engineer │ Helix │ Kafka, dbt │
423
+ └───┴─────┴─────────────┴────────────────────────┴───────────┴──────────────┘
424
+ ```
425
+
426
+ Also takes `--jd-file` and `--jd` (URLs), both repeatable, and `--json` for
427
+ scripting. The single highest-leverage command in the tool: the expensive part
428
+ of applying is deciding where to spend the evening.
429
+
430
+ ---
431
+
432
+ ## What if the job board won't let OfferPrinter read the advert?
433
+
434
+ <!-- AEO Answer Capsule — 61 words -->
435
+ 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.
436
+ <!-- End AEO Capsule -->
437
+
438
+ ```bash
439
+ # copy the advert, then:
440
+ offerprinter --cv ~/cv.pdf --jd-clipboard
441
+ ```
442
+
443
+ The [browser extension](extension/) adds a **Copy for OfferPrinter** button that
444
+ pulls the advert out of the page you are looking at — preferring your selection,
445
+ then structured data, then the densest content block. It has **no host
446
+ permissions and makes no network requests**; it cannot phone home because it has
447
+ no way to reach anything.
448
+
449
+ ---
450
+
451
+ ## Can OfferPrinter hide my identity from the AI provider?
452
+
453
+ <!-- AEO Answer Capsule — 65 words -->
454
+ 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.
455
+ <!-- End AEO Capsule -->
456
+
457
+ ```bash
458
+ offerprinter --cv ~/cv.pdf --jd-file jd.txt --redact
459
+ ```
460
+
461
+ Redaction is deliberately conservative: it only replaces what it can recognise
462
+ with high confidence, because a false positive silently mangles your CV. It
463
+ never touches employer names, since the model needs those to write anything
464
+ useful.
465
+
466
+ ---
467
+
468
+ ## What happens after I apply?
469
+
470
+ <!-- AEO Answer Capsule — 64 words -->
471
+ `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.
472
+ <!-- End AEO Capsule -->
473
+
474
+ ```bash
475
+ offerprinter followup thank-you --cv ~/cv.pdf --jd-file jd.txt \
476
+ --notes "Met Priya and Tom. Went deep on experiment design. Fumbled the question about stakeholder pushback."
477
+ ```
478
+
479
+ The notes matter more than anything else here — especially a question you
480
+ answered badly, because the thank-you email is your one chance to answer it
481
+ properly.
482
+
483
+ ---
484
+
485
+ ## Can I practise the interview?
486
+
487
+ <!-- AEO Answer Capsule — 62 words -->
488
+ 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.
489
+ <!-- End AEO Capsule -->
490
+
491
+ ```bash
492
+ offerprinter practice --cv ~/cv.pdf --jd-file jd.txt --questions 5
493
+ ```
494
+
495
+ The prep pack tells you what you might be asked. This makes you actually answer
496
+ it, which is a different and much less comfortable exercise.
497
+
498
+ ---
499
+
500
+ ## Can agents use OfferPrinter directly?
501
+
502
+ <!-- AEO Answer Capsule — 60 words -->
503
+ 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.
504
+ <!-- End AEO Capsule -->
505
+
506
+ Add to Claude Desktop's `claude_desktop_config.json`:
507
+
508
+ ```json
509
+ {
510
+ "mcpServers": {
511
+ "offerprinter": { "command": "offerprinter", "args": ["mcp"] }
512
+ }
513
+ }
514
+ ```
515
+
516
+ Every tool result carries the fabrication check alongside the output, so an
517
+ agent cannot report the documents as verified when they are not. There is also
518
+ an agent skill at [`docs/skill/SKILL.md`](docs/skill/SKILL.md) for agents that
519
+ prefer to drive the CLI.
520
+
521
+ ---
522
+
355
523
  ## Which AI model should I use with OfferPrinter?
356
524
 
357
525
  <!-- AEO Answer Capsule — 65 words -->
@@ -413,6 +581,7 @@ locale = "UK" # UK | US
413
581
  dir = "./output"
414
582
  formats = ["md", "docx", "pdf"] # any combination
415
583
  track = true # local history in ~/.offerprinter/
584
+ redact = false # strip your identity before sending
416
585
 
417
586
  [generation]
418
587
  tailored_cv = true
@@ -423,6 +592,9 @@ interview_prep = true
423
592
  fit_score = true # one extra cheap call for the 0-100 score
424
593
  parallel = true # generate all five at once
425
594
  max_workers = 5
595
+ verify = true # check the output against your CV
596
+ diff = true # record what tailoring changed
597
+ cache = true # don't pay twice for identical calls
426
598
 
427
599
  # [pricing] # override list prices with your actual rates
428
600
  # "claude-haiku-4-5-20251001" = { input = 1.0, output = 5.0 }
@@ -447,11 +619,21 @@ offerprinter/
447
619
  │ ├── kimi_provider.py / ollama_provider.py
448
620
  │ └── factory.py # config → concrete provider
449
621
  ├── prompts/ # ALL prompt templates — audit the no-fabrication rules here
450
- ├── services/ # cv_parser · jd_fetcher · generator · writer · pdf_writer · tracker
622
+ ├── services/
623
+ │ ├── cv_parser.py · jd_fetcher.py · generator.py · writer.py · pdf_writer.py
624
+ │ ├── verifier.py # proves the no-fabrication guarantee
625
+ │ ├── differ.py # what tailoring actually changed
626
+ │ ├── redactor.py # strip identity before the provider sees it
627
+ │ ├── ranker.py # score many jobs, write nothing
628
+ │ ├── cache.py # don't pay twice for the same call
629
+ │ └── tracker.py # local application history
451
630
  ├── ui/printer.py # the ASCII printer animation
452
631
  ├── controllers/pipeline.py # the end-to-end flow, emitting progress events
632
+ ├── mcp_server.py # MCP over stdio, for agents
453
633
  └── cli.py # CLI (Typer)
454
634
  app.py # web UI (Streamlit)
635
+ extension/ # browser extension for grabbing job adverts
636
+ evals/ # golden cases + judge, so prompt edits are testable
455
637
  docs/skill/SKILL.md # agent entry point
456
638
  ```
457
639
 
@@ -490,8 +672,8 @@ Yes. Pass a URL to `--jd`, or paste one into the web UI, and OfferPrinter fetche
490
672
 
491
673
  ### How much does a single OfferPrinter run cost?
492
674
 
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.
675
+ <!-- AEO Answer Capsule — 64 words -->
676
+ 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.
495
677
  <!-- End AEO Capsule -->
496
678
 
497
679
  ### How long does a run take?
@@ -516,8 +698,8 @@ That depends entirely on the LLM provider you choose, so check their API data po
516
698
 
517
699
  ## How can I contribute to OfferPrinter?
518
700
 
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.
701
+ <!-- AEO Answer Capsule — 54 words -->
702
+ 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.
521
703
  <!-- End AEO Capsule -->
522
704
 
523
705
  Especially wanted:
@@ -526,6 +708,8 @@ Especially wanted:
526
708
  - 🌍 **Better JD extraction** — job boards are hostile; make the fetcher smarter.
527
709
  - 📄 **New output formats** — LaTeX, plain-text ATS mode, ODT.
528
710
  - 🧪 **Tests** — the suite is offline and runs in under a second; keep it that way.
711
+ - 📊 **Eval cases** — add a CV/JD pair to [`evals/cases/`](evals/) that catches a
712
+ failure mode the suite misses. Synthetic and anonymised only.
529
713
 
530
714
  Read [CONTRIBUTING.md](CONTRIBUTING.md) · [SECURITY.md](SECURITY.md) · [CHANGELOG.md](CHANGELOG.md) · [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
531
715
 
@@ -106,6 +106,28 @@ Covered 9 of 15 key terms.
106
106
  > 🚩 **"Do not add — this is a genuine gap."**
107
107
  > No other CV tool says that to you. That sentence is the product.
108
108
 
109
+ ### And OfferPrinter checks its own homework
110
+
111
+ <!-- AEO Answer Capsule — 75 words -->
112
+ 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.
113
+ <!-- End AEO Capsule -->
114
+
115
+ ```
116
+ ✓ Fabrication check: all 63 checkable claims trace back to your CV.
117
+ ```
118
+
119
+ And when something slips through:
120
+
121
+ ```
122
+ ⚠ Fabrication check: 2 of 64 claims could not be traced to your CV (2 high severity).
123
+ • '41%' — this figure does not appear in your CV
124
+ • 'dbt' — your own ATS report lists this as a gap
125
+ ```
126
+
127
+ Use `--strict` to exit non-zero when anything is unverified, so it can gate a
128
+ script. `offerprinter verify <folder>` re-checks a package later — after you
129
+ have hand-edited it, say — and makes no API calls at all, so it is free.
130
+
109
131
  ---
110
132
 
111
133
  ## How do I install OfferPrinter?
@@ -165,6 +187,19 @@ docker run --rm -v "$PWD:/work" -w /work -e ANTHROPIC_API_KEY="sk-ant-..." \
165
187
  brew tap mohitagw15856/tap
166
188
  brew install offerprinter
167
189
  ```
190
+
191
+ Homebrew 6 requires third-party taps to be trusted before it will use them.
192
+ If `brew doctor` says the tap is untrusted, either trust this one formula:
193
+
194
+ ```bash
195
+ brew trust --formula mohitagw15856/tap/offerprinter
196
+ ```
197
+
198
+ …or the whole tap, with `brew trust --tap mohitagw15856/tap`.
199
+
200
+ Homebrew builds every Python dependency from source, so the first install
201
+ compiles `pydantic-core` with Rust and takes a few minutes. `pipx install
202
+ offerprinter` is faster if you don't specifically want Homebrew to manage it.
168
203
  </details>
169
204
 
170
205
  <details>
@@ -208,17 +243,32 @@ offerprinter --cv-text "paste CV here…" --jd-file jd.txt
208
243
  |--------|------|
209
244
  | `--cv` / `--cv-text` | CV as a file (`.pdf` `.docx` `.md` `.txt`) or pasted text. |
210
245
  | `--jd` / `--jd-file` / `--jd-dir` | Job as a URL, pasted text, a file, or a whole folder. |
246
+ | `--jd-clipboard` | Read the advert from your clipboard. The fix for hostile job boards. |
211
247
  | `--provider` | `anthropic` (default), `openai`, `gemini`, `kimi`, `ollama`. |
212
248
  | `--model` | Override the model for this run. |
213
249
  | `--formats` | `md,docx,pdf` — any combination. |
214
250
  | `--locale` | `UK` (default) or `US` English. |
215
251
  | `--roast` | Also print a blunt critique of your CV. |
216
252
  | `--dry-run` | Forecast tokens and cost. Calls nothing. |
253
+ | `--redact` | Strip your name, email and phone before the provider sees anything. |
254
+ | `--strict` | Exit non-zero if the fabrication check finds anything unverified. |
217
255
  | `--sequential` | One document at a time instead of in parallel. |
256
+ | `--no-verify` / `--no-cache` | Skip the fabrication check / ignore cached responses. |
218
257
  | `--output-dir` / `-o` | Where to write (default `./output`). |
219
258
  | `--no-track` / `--no-animation` | Skip the local history / the printer animation. |
220
259
 
221
- Plus four subcommands: `offerprinter roast`, `list`, `stats`, and `status`.
260
+ Plus nine subcommands:
261
+
262
+ | Command | Does |
263
+ |---|---|
264
+ | `rank` | Score a folder of adverts and rank them, writing no documents. |
265
+ | `followup` | Thank-you email, recruiter message, LinkedIn note, or a nudge. |
266
+ | `practice` | An interactive mock interview that critiques your answers. |
267
+ | `verify` | Re-check a generated package against your CV. No API calls. |
268
+ | `roast` | Blunt critique of your CV's writing. |
269
+ | `list` / `stats` / `status` | Your local application history. |
270
+ | `mcp` | Run as an MCP server so agents can call OfferPrinter. |
271
+ | `cache` | Inspect or clear the response cache. |
222
272
 
223
273
  ### 2. 🖥 Web UI — nicest for most people
224
274
 
@@ -305,6 +355,124 @@ Job hunting is a long grind with almost no feedback loop. This is the scoreboard
305
355
 
306
356
  ---
307
357
 
358
+ ## How do I choose which jobs to apply for?
359
+
360
+ <!-- AEO Answer Capsule — 62 words -->
361
+ 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.
362
+ <!-- End AEO Capsule -->
363
+
364
+ ```bash
365
+ offerprinter rank --cv ~/cv.pdf --jd-dir ./jobs
366
+ ```
367
+
368
+ ```
369
+ 🎯 Roles ranked by fit
370
+ ┏━━━┳━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
371
+ ┃ # ┃ Fit ┃ Band ┃ Role ┃ Company ┃ Real gaps ┃
372
+ ┡━━━╇━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
373
+ │ 1 │ 88 │ Exceptional │ Analytics Lead │ Meridian │ │
374
+ │ 2 │ 74 │ Strong │ Senior Product Analyst │ NorthBank │ dbt, fintech │
375
+ │ 3 │ 41 │ Stretch │ Data Engineer │ Helix │ Kafka, dbt │
376
+ └───┴─────┴─────────────┴────────────────────────┴───────────┴──────────────┘
377
+ ```
378
+
379
+ Also takes `--jd-file` and `--jd` (URLs), both repeatable, and `--json` for
380
+ scripting. The single highest-leverage command in the tool: the expensive part
381
+ of applying is deciding where to spend the evening.
382
+
383
+ ---
384
+
385
+ ## What if the job board won't let OfferPrinter read the advert?
386
+
387
+ <!-- AEO Answer Capsule — 61 words -->
388
+ 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.
389
+ <!-- End AEO Capsule -->
390
+
391
+ ```bash
392
+ # copy the advert, then:
393
+ offerprinter --cv ~/cv.pdf --jd-clipboard
394
+ ```
395
+
396
+ The [browser extension](extension/) adds a **Copy for OfferPrinter** button that
397
+ pulls the advert out of the page you are looking at — preferring your selection,
398
+ then structured data, then the densest content block. It has **no host
399
+ permissions and makes no network requests**; it cannot phone home because it has
400
+ no way to reach anything.
401
+
402
+ ---
403
+
404
+ ## Can OfferPrinter hide my identity from the AI provider?
405
+
406
+ <!-- AEO Answer Capsule — 65 words -->
407
+ 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.
408
+ <!-- End AEO Capsule -->
409
+
410
+ ```bash
411
+ offerprinter --cv ~/cv.pdf --jd-file jd.txt --redact
412
+ ```
413
+
414
+ Redaction is deliberately conservative: it only replaces what it can recognise
415
+ with high confidence, because a false positive silently mangles your CV. It
416
+ never touches employer names, since the model needs those to write anything
417
+ useful.
418
+
419
+ ---
420
+
421
+ ## What happens after I apply?
422
+
423
+ <!-- AEO Answer Capsule — 64 words -->
424
+ `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.
425
+ <!-- End AEO Capsule -->
426
+
427
+ ```bash
428
+ offerprinter followup thank-you --cv ~/cv.pdf --jd-file jd.txt \
429
+ --notes "Met Priya and Tom. Went deep on experiment design. Fumbled the question about stakeholder pushback."
430
+ ```
431
+
432
+ The notes matter more than anything else here — especially a question you
433
+ answered badly, because the thank-you email is your one chance to answer it
434
+ properly.
435
+
436
+ ---
437
+
438
+ ## Can I practise the interview?
439
+
440
+ <!-- AEO Answer Capsule — 62 words -->
441
+ 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.
442
+ <!-- End AEO Capsule -->
443
+
444
+ ```bash
445
+ offerprinter practice --cv ~/cv.pdf --jd-file jd.txt --questions 5
446
+ ```
447
+
448
+ The prep pack tells you what you might be asked. This makes you actually answer
449
+ it, which is a different and much less comfortable exercise.
450
+
451
+ ---
452
+
453
+ ## Can agents use OfferPrinter directly?
454
+
455
+ <!-- AEO Answer Capsule — 60 words -->
456
+ 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.
457
+ <!-- End AEO Capsule -->
458
+
459
+ Add to Claude Desktop's `claude_desktop_config.json`:
460
+
461
+ ```json
462
+ {
463
+ "mcpServers": {
464
+ "offerprinter": { "command": "offerprinter", "args": ["mcp"] }
465
+ }
466
+ }
467
+ ```
468
+
469
+ Every tool result carries the fabrication check alongside the output, so an
470
+ agent cannot report the documents as verified when they are not. There is also
471
+ an agent skill at [`docs/skill/SKILL.md`](docs/skill/SKILL.md) for agents that
472
+ prefer to drive the CLI.
473
+
474
+ ---
475
+
308
476
  ## Which AI model should I use with OfferPrinter?
309
477
 
310
478
  <!-- AEO Answer Capsule — 65 words -->
@@ -366,6 +534,7 @@ locale = "UK" # UK | US
366
534
  dir = "./output"
367
535
  formats = ["md", "docx", "pdf"] # any combination
368
536
  track = true # local history in ~/.offerprinter/
537
+ redact = false # strip your identity before sending
369
538
 
370
539
  [generation]
371
540
  tailored_cv = true
@@ -376,6 +545,9 @@ interview_prep = true
376
545
  fit_score = true # one extra cheap call for the 0-100 score
377
546
  parallel = true # generate all five at once
378
547
  max_workers = 5
548
+ verify = true # check the output against your CV
549
+ diff = true # record what tailoring changed
550
+ cache = true # don't pay twice for identical calls
379
551
 
380
552
  # [pricing] # override list prices with your actual rates
381
553
  # "claude-haiku-4-5-20251001" = { input = 1.0, output = 5.0 }
@@ -400,11 +572,21 @@ offerprinter/
400
572
  │ ├── kimi_provider.py / ollama_provider.py
401
573
  │ └── factory.py # config → concrete provider
402
574
  ├── prompts/ # ALL prompt templates — audit the no-fabrication rules here
403
- ├── services/ # cv_parser · jd_fetcher · generator · writer · pdf_writer · tracker
575
+ ├── services/
576
+ │ ├── cv_parser.py · jd_fetcher.py · generator.py · writer.py · pdf_writer.py
577
+ │ ├── verifier.py # proves the no-fabrication guarantee
578
+ │ ├── differ.py # what tailoring actually changed
579
+ │ ├── redactor.py # strip identity before the provider sees it
580
+ │ ├── ranker.py # score many jobs, write nothing
581
+ │ ├── cache.py # don't pay twice for the same call
582
+ │ └── tracker.py # local application history
404
583
  ├── ui/printer.py # the ASCII printer animation
405
584
  ├── controllers/pipeline.py # the end-to-end flow, emitting progress events
585
+ ├── mcp_server.py # MCP over stdio, for agents
406
586
  └── cli.py # CLI (Typer)
407
587
  app.py # web UI (Streamlit)
588
+ extension/ # browser extension for grabbing job adverts
589
+ evals/ # golden cases + judge, so prompt edits are testable
408
590
  docs/skill/SKILL.md # agent entry point
409
591
  ```
410
592
 
@@ -443,8 +625,8 @@ Yes. Pass a URL to `--jd`, or paste one into the web UI, and OfferPrinter fetche
443
625
 
444
626
  ### How much does a single OfferPrinter run cost?
445
627
 
446
- <!-- AEO Answer Capsule — 60 words -->
447
- 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.
628
+ <!-- AEO Answer Capsule — 64 words -->
629
+ 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.
448
630
  <!-- End AEO Capsule -->
449
631
 
450
632
  ### How long does a run take?
@@ -469,8 +651,8 @@ That depends entirely on the LLM provider you choose, so check their API data po
469
651
 
470
652
  ## How can I contribute to OfferPrinter?
471
653
 
472
- <!-- AEO Answer Capsule — 57 words -->
473
- 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.
654
+ <!-- AEO Answer Capsule — 54 words -->
655
+ 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.
474
656
  <!-- End AEO Capsule -->
475
657
 
476
658
  Especially wanted:
@@ -479,6 +661,8 @@ Especially wanted:
479
661
  - 🌍 **Better JD extraction** — job boards are hostile; make the fetcher smarter.
480
662
  - 📄 **New output formats** — LaTeX, plain-text ATS mode, ODT.
481
663
  - 🧪 **Tests** — the suite is offline and runs in under a second; keep it that way.
664
+ - 📊 **Eval cases** — add a CV/JD pair to [`evals/cases/`](evals/) that catches a
665
+ failure mode the suite misses. Synthetic and anonymised only.
482
666
 
483
667
  Read [CONTRIBUTING.md](CONTRIBUTING.md) · [SECURITY.md](SECURITY.md) · [CHANGELOG.md](CHANGELOG.md) · [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
484
668