offerprinter 0.2.0__tar.gz → 0.4.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.
- {offerprinter-0.2.0 → offerprinter-0.4.0}/.gitignore +3 -1
- offerprinter-0.4.0/CHANGELOG.md +217 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/PKG-INFO +261 -7
- {offerprinter-0.2.0 → offerprinter-0.4.0}/README.md +260 -6
- {offerprinter-0.2.0 → offerprinter-0.4.0}/config.example.toml +23 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/docs/skill/SKILL.md +33 -0
- offerprinter-0.4.0/evals/README.md +75 -0
- offerprinter-0.4.0/extension/README.md +57 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/__init__.py +1 -1
- offerprinter-0.4.0/offerprinter/cli.py +1140 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/config.py +5 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/controllers/pipeline.py +44 -6
- offerprinter-0.4.0/offerprinter/mcp_server.py +345 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/models/schemas.py +124 -0
- offerprinter-0.4.0/offerprinter/prompts/__init__.py +58 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/prompts/templates.py +273 -0
- offerprinter-0.4.0/offerprinter/services/cache.py +152 -0
- offerprinter-0.4.0/offerprinter/services/differ.py +193 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/services/generator.py +77 -0
- offerprinter-0.4.0/offerprinter/services/jd_fetcher.py +216 -0
- offerprinter-0.4.0/offerprinter/services/linkedin.py +267 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/services/pdf_writer.py +109 -95
- offerprinter-0.4.0/offerprinter/services/ranker.py +95 -0
- offerprinter-0.4.0/offerprinter/services/redactor.py +174 -0
- offerprinter-0.4.0/offerprinter/services/themes.py +232 -0
- offerprinter-0.4.0/offerprinter/services/verifier.py +367 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/services/writer.py +28 -4
- {offerprinter-0.2.0 → offerprinter-0.4.0}/pyproject.toml +1 -1
- offerprinter-0.4.0/tests/test_new_services.py +400 -0
- offerprinter-0.4.0/tests/test_templates_and_linkedin.py +329 -0
- offerprinter-0.4.0/tests/test_verifier.py +240 -0
- offerprinter-0.2.0/CHANGELOG.md +0 -104
- offerprinter-0.2.0/offerprinter/cli.py +0 -523
- offerprinter-0.2.0/offerprinter/prompts/__init__.py +0 -32
- offerprinter-0.2.0/offerprinter/services/jd_fetcher.py +0 -86
- {offerprinter-0.2.0 → offerprinter-0.4.0}/LICENSE +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/app.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/cli.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/deploy/README.md +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/deploy/huggingface/README.md +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/README.md +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/ats-keyword-report.docx +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/ats-keyword-report.md +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/cover-letter.docx +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/cover-letter.md +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/fit-memo.docx +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/fit-memo.md +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/full-package.docx +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/full-package.md +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/interview-prep-pack.docx +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/interview-prep-pack.md +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/tailored-cv.docx +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/output/northbank-senior-product-analyst/tailored-cv.md +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/sample_cv.md +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/examples/sample_jd.md +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/controllers/__init__.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/llm/__init__.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/llm/anthropic_provider.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/llm/base.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/llm/factory.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/llm/gemini_provider.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/llm/kimi_provider.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/llm/ollama_provider.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/llm/openai_provider.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/models/__init__.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/pricing.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/services/__init__.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/services/cv_parser.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/services/tracker.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/ui/__init__.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/offerprinter/ui/printer.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/tests/__init__.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/tests/conftest.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/tests/test_offerprinter.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/tests/test_pdf_writer.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.0}/tests/test_pricing_and_retries.py +0 -0
- {offerprinter-0.2.0 → offerprinter-0.4.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/
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to OfferPrinter are documented here.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.4.0] — 2026-08-25
|
|
11
|
+
|
|
12
|
+
The "it has to look like something" release. v0.3.0 made the output provable;
|
|
13
|
+
this one makes it presentable, and makes getting your history in a two-minute job
|
|
14
|
+
rather than an afternoon.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **Six PDF templates**: `classic`, `compact`, `serif`, `rule`, `accent`, `mono`.
|
|
19
|
+
Choose with `--template`, list with `offerprinter templates`, and see them with
|
|
20
|
+
`offerprinter templates --sample ./samples`. Set a default under `[output]` in
|
|
21
|
+
`config.toml`, or `OFFERPRINTER_TEMPLATE`.
|
|
22
|
+
- **LinkedIn import** (`--linkedin`). Takes either export LinkedIn offers: the PDF
|
|
23
|
+
from *More → Save to PDF*, or the CSV archive from *Get a copy of your data*.
|
|
24
|
+
Nothing is scraped and nothing is fetched — you export your own data and this
|
|
25
|
+
reads the file.
|
|
26
|
+
|
|
27
|
+
### Notes on the templates
|
|
28
|
+
|
|
29
|
+
Every template stays inside what an ATS can parse: one of the 14 PDF base fonts,
|
|
30
|
+
no columns, no tables, no images, no text boxes. What varies is typeface, spacing,
|
|
31
|
+
rules, capitalisation and one accent colour. The test suite checks, per template,
|
|
32
|
+
that the PDF embeds no font program, contains no `/Image` or `/XObject`, and that
|
|
33
|
+
a naive text extraction still returns the name, the email and every bullet.
|
|
34
|
+
|
|
35
|
+
`accent` is labelled *email only*: it extracts cleanly, but carries enough colour
|
|
36
|
+
that a strict parser could lose a heading.
|
|
37
|
+
|
|
38
|
+
The LinkedIn importer is held to the no-fabrication guarantee too. A test takes
|
|
39
|
+
every word of its output and asserts it appeared in the input, because an importer
|
|
40
|
+
that quietly tidied up your history would break the guarantee before the generator
|
|
41
|
+
ever ran.
|
|
42
|
+
|
|
43
|
+
### Unchanged
|
|
44
|
+
|
|
45
|
+
`classic` renders byte-for-byte what v0.3.0 rendered. There is a test for that, so
|
|
46
|
+
upgrading changes nothing unless you ask it to.
|
|
47
|
+
|
|
48
|
+
## [0.3.0] — 2026-08-20
|
|
49
|
+
|
|
50
|
+
The "prove it" release. v0.2.0 made OfferPrinter installable; this one makes its
|
|
51
|
+
central claim checkable, and extends the tool across the rest of a job hunt.
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
**Proving the guarantee**
|
|
56
|
+
- **Fabrication verifier.** Every number, date and named entity in the tailored
|
|
57
|
+
CV and cover letter is checked against your source CV, and the tailored CV is
|
|
58
|
+
cross-checked against the ATS report's own list of gaps — so a package that
|
|
59
|
+
contradicts itself is caught automatically. Writes `fabrication-check.md`.
|
|
60
|
+
`--strict` exits 3 if anything is unverified, which makes it usable as a gate.
|
|
61
|
+
Until now "never fabricate" was an instruction in a prompt; now it is an
|
|
62
|
+
assertion the tool makes about its own output.
|
|
63
|
+
- **`offerprinter verify`** re-checks an already-generated package — after you
|
|
64
|
+
have hand-edited it, or in CI. Makes no API calls, so it is free and offline.
|
|
65
|
+
- **Tailoring diff.** `tailoring-diff.md` shows exactly what changed: which
|
|
66
|
+
bullets were reworded, what was added or dropped, and which vocabulary is new.
|
|
67
|
+
The verifier proves nothing was invented; the diff shows what was done.
|
|
68
|
+
- **Eval harness** (`evals/`, `scripts/run_evals.py`). Golden cases with
|
|
69
|
+
per-case assertions about which gaps must be named and which terms must never
|
|
70
|
+
be claimed, plus an LLM-as-judge pass scoring grounding, specificity, honesty,
|
|
71
|
+
usefulness and format. The deterministic half runs in CI on every PR; the
|
|
72
|
+
judge runs on demand. Prompt changes are no longer a guess.
|
|
73
|
+
|
|
74
|
+
**New commands**
|
|
75
|
+
- **`offerprinter rank`** — score a folder of adverts and rank them by fit
|
|
76
|
+
without generating a single document. Two cheap calls per job; triage twenty
|
|
77
|
+
roles for a couple of pence, then print packages only for the ones worth it.
|
|
78
|
+
- **`offerprinter followup`** — thank-you email, recruiter message, LinkedIn
|
|
79
|
+
note (hard-capped at 300 characters) or a polite nudge. Written from your CV
|
|
80
|
+
and your notes; it will not invent a conversation that did not happen.
|
|
81
|
+
- **`offerprinter practice`** — an interactive mock interview. It asks, you
|
|
82
|
+
answer, it critiques and rewrites your answer using only your real experience,
|
|
83
|
+
then summarises the patterns worth fixing.
|
|
84
|
+
- **`offerprinter mcp`** — run as an MCP server on stdio, so Claude Desktop,
|
|
85
|
+
Claude Code and other agents can call OfferPrinter natively and get structured
|
|
86
|
+
results back. Hand-rolled JSON-RPC; no new dependencies.
|
|
87
|
+
- **`offerprinter cache stats` / `cache clear`**.
|
|
88
|
+
|
|
89
|
+
**Privacy and job-board reality**
|
|
90
|
+
- **PII redaction** (`--redact`). Your name, email, phone and profile links are
|
|
91
|
+
replaced with placeholders before the provider sees anything, and restored in
|
|
92
|
+
the output. The model keeps every employer, date and metric it needs.
|
|
93
|
+
- **`--jd-clipboard`** and a **browser extension** (`extension/`). Job boards
|
|
94
|
+
are the most common reason a run fails; the advert is already rendered in your
|
|
95
|
+
tab, so read it from there. The extension has no host permissions and makes no
|
|
96
|
+
network requests.
|
|
97
|
+
- **schema.org `JobPosting` JSON-LD extraction.** Many boards embed the whole
|
|
98
|
+
advert as structured data — cleaner than anything scraped, and it supplies the
|
|
99
|
+
company and job title for free.
|
|
100
|
+
|
|
101
|
+
**Cost**
|
|
102
|
+
- **Response caching.** Keyed on provider, model, temperature and both prompts,
|
|
103
|
+
so anything that could change the answer correctly misses. Re-running after a
|
|
104
|
+
CV tweak is now mostly free.
|
|
105
|
+
|
|
106
|
+
### Fixed
|
|
107
|
+
|
|
108
|
+
- Multi-word entities could match across a line break, producing nonsense claims
|
|
109
|
+
glued together from two sentences.
|
|
110
|
+
- A level-two heading (`## Missing keywords`) was not recognised as a sentence
|
|
111
|
+
start, so its first word was treated as a named entity.
|
|
112
|
+
- A capitalised word at the very start of a bullet ("Used SQL daily") was read
|
|
113
|
+
as an entity, because the sentence-start pattern only handled bullets that
|
|
114
|
+
followed a newline, not one at the start of the text.
|
|
115
|
+
|
|
116
|
+
### Changed
|
|
117
|
+
|
|
118
|
+
- Config gains `[output] redact`, `[generation] verify`, `diff` and `cache`.
|
|
119
|
+
- The test suite grew from 73 to 143 tests, still fully offline.
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
## [0.2.0] — 2026-08-18
|
|
123
|
+
|
|
124
|
+
The "actually installable, and quite a lot faster" release.
|
|
125
|
+
|
|
126
|
+
### Added
|
|
127
|
+
|
|
128
|
+
**Distribution**
|
|
129
|
+
- Published to PyPI: `pipx install offerprinter`, `uvx offerprinter`, or
|
|
130
|
+
`pip install offerprinter`.
|
|
131
|
+
- `offerprinter` and `opr` console scripts, so it works from anywhere after
|
|
132
|
+
install rather than only from a git clone.
|
|
133
|
+
- Standalone binaries for macOS, Linux and Windows attached to each release —
|
|
134
|
+
no Python installation required.
|
|
135
|
+
- Multi-arch Docker image published to GHCR on every push and tag:
|
|
136
|
+
`ghcr.io/mohitagw15856/offerprinter:latest`.
|
|
137
|
+
- Homebrew formula in `packaging/homebrew/` for a `brew install` tap.
|
|
138
|
+
- Deployment guides for Streamlit Community Cloud, Hugging Face Spaces, Docker
|
|
139
|
+
and container platforms, in `deploy/`.
|
|
140
|
+
|
|
141
|
+
**Features**
|
|
142
|
+
- **Fit score** — every run ends with a 0-100 score, a band, and honest
|
|
143
|
+
strengths and gaps. Written to `fit-score.md` and shown in the CLI and web UI.
|
|
144
|
+
- **PDF output** — real, selectable, ATS-friendly Helvetica text, written by a
|
|
145
|
+
dependency-free PDF writer rather than a rendering engine. Enable with
|
|
146
|
+
`--formats md,docx,pdf`.
|
|
147
|
+
- **Roast mode** — `offerprinter roast --cv cv.pdf`, or `--roast` during a run.
|
|
148
|
+
Blunt, funny, opt-in critique of your CV's *writing*. Still never dishonest.
|
|
149
|
+
- **Application tracker** — every run is recorded locally in
|
|
150
|
+
`~/.offerprinter/applications.json`. New commands: `offerprinter list`,
|
|
151
|
+
`offerprinter stats`, `offerprinter status <slug> <status>`.
|
|
152
|
+
- **Achievements** — eleven small milestones, computed from your local history.
|
|
153
|
+
- **Batch mode** — `--jd-dir ./jobs` prints a package for every job description
|
|
154
|
+
in a folder.
|
|
155
|
+
- **Cost and token reporting** — every run reports calls, tokens and estimated
|
|
156
|
+
spend. `--dry-run` forecasts the cost without calling the API at all.
|
|
157
|
+
- **Ollama provider** — run entirely on your own machine with no API key and no
|
|
158
|
+
data leaving your laptop: `--provider ollama`.
|
|
159
|
+
- **Printer animation** — an ASCII dot-matrix printer feeding a sheet per
|
|
160
|
+
document. Degrades to plain status lines when not a terminal, or with
|
|
161
|
+
`--no-animation`.
|
|
162
|
+
- **Demo mode in the web UI** — with no API key configured, the app shows the
|
|
163
|
+
bundled example package instead of an error, which is what makes a public
|
|
164
|
+
hosted demo possible.
|
|
165
|
+
|
|
166
|
+
**Reliability**
|
|
167
|
+
- Automatic retries with exponential backoff and `Retry-After` support on 429s
|
|
168
|
+
and 5xx errors. A single rate limit no longer kills a five-document run.
|
|
169
|
+
- `[pricing]` config table to override list prices with your actual rates.
|
|
170
|
+
|
|
171
|
+
**Project**
|
|
172
|
+
- `CONTRIBUTING.md`, `SECURITY.md`, `CODE_OF_CONDUCT.md`, this changelog, issue
|
|
173
|
+
and pull request templates.
|
|
174
|
+
- CI now runs the test suite across Python 3.11/3.12/3.13 with macOS and Windows
|
|
175
|
+
spot-checks, plus coverage, a packaging build, and a console-script smoke test.
|
|
176
|
+
|
|
177
|
+
### Changed
|
|
178
|
+
|
|
179
|
+
- **Artifacts now generate in parallel.** They are independent of each other, so
|
|
180
|
+
a full run takes about as long as its slowest single document instead of the
|
|
181
|
+
sum of all five. Use `--sequential` for the old behaviour.
|
|
182
|
+
- The CLI moved to `offerprinter/cli.py` so it ships inside the installed
|
|
183
|
+
package. `python cli.py …` still works from a clone.
|
|
184
|
+
- The web UI (`streamlit`) is now an optional extra, keeping
|
|
185
|
+
`pipx install offerprinter` small. Install it with `offerprinter[web]`.
|
|
186
|
+
- Default output formats remain `md` and `docx`; `pdf` is opt-in via config
|
|
187
|
+
or `--formats`.
|
|
188
|
+
|
|
189
|
+
### Fixed
|
|
190
|
+
|
|
191
|
+
- The `offerprinter` console script pointed at a root-level `cli` module that
|
|
192
|
+
was never included in the wheel, so it could not resolve after an install.
|
|
193
|
+
- The README claimed CI ran `pytest`. It didn't. Now it does.
|
|
194
|
+
|
|
195
|
+
## [0.1.0] — 2026-08-18
|
|
196
|
+
|
|
197
|
+
First release — "First Print".
|
|
198
|
+
|
|
199
|
+
### Added
|
|
200
|
+
|
|
201
|
+
- Five generated artifacts from one CV and one job description: tailored CV,
|
|
202
|
+
cover letter, fit memo, ATS keyword report, interview prep pack.
|
|
203
|
+
- The no-fabrication guarantee, enforced in every prompt.
|
|
204
|
+
- Four providers behind one interface: Anthropic (default), OpenAI, Gemini,
|
|
205
|
+
Moonshot Kimi.
|
|
206
|
+
- CLI (Typer), web UI (Streamlit), and an agent skill at `docs/skill/SKILL.md`.
|
|
207
|
+
- CV input as `.pdf`, `.docx`, `.md`, `.txt` or pasted text; job description as a
|
|
208
|
+
URL or text.
|
|
209
|
+
- Markdown and Word output, plus a combined full package.
|
|
210
|
+
- UK and US English.
|
|
211
|
+
- Config via env vars, `config.toml`, or defaults.
|
|
212
|
+
- Dockerfile and docker-compose, offline test suite, ruff lint.
|
|
213
|
+
|
|
214
|
+
[Unreleased]: https://github.com/mohitagw15856/OfferPrinter/compare/v0.3.0...HEAD
|
|
215
|
+
[0.3.0]: https://github.com/mohitagw15856/OfferPrinter/compare/v0.2.0...v0.3.0
|
|
216
|
+
[0.2.0]: https://github.com/mohitagw15856/OfferPrinter/compare/v0.1.0...v0.2.0
|
|
217
|
+
[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.
|
|
3
|
+
Version: 0.4.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,8 +153,100 @@ 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
|
|
|
180
|
+
## I have not updated my CV in three years
|
|
181
|
+
|
|
182
|
+
Neither has anyone. Your LinkedIn is current though, and LinkedIn will give it
|
|
183
|
+
back to you:
|
|
184
|
+
|
|
185
|
+
```sh
|
|
186
|
+
# More > Save to PDF on your own profile
|
|
187
|
+
offerprinter --linkedin ~/Downloads/Profile.pdf --jd https://…
|
|
188
|
+
|
|
189
|
+
# or the full archive: Settings > Data privacy > Get a copy of your data
|
|
190
|
+
offerprinter --linkedin ~/Downloads/Basic_LinkedInDataExport.zip --jd https://…
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Both shapes are normalised into the Markdown the rest of the pipeline expects.
|
|
194
|
+
Nothing is scraped — you export your own data from your own account and this reads
|
|
195
|
+
the file.
|
|
196
|
+
|
|
197
|
+
The importer holds to the same rule as the generator: **it invents nothing.** A
|
|
198
|
+
role with no description gets no description; an empty section stays empty. There
|
|
199
|
+
is a test that takes every word of the output and asserts it appeared in the
|
|
200
|
+
input, because an importer that quietly "tidied up" your history would break the
|
|
201
|
+
no-fabrication guarantee before the generator ever ran.
|
|
202
|
+
|
|
203
|
+
## Can I make it look like something other than every other CV?
|
|
204
|
+
|
|
205
|
+
Six templates, all of which still parse:
|
|
206
|
+
|
|
207
|
+
```sh
|
|
208
|
+
offerprinter templates # what they are
|
|
209
|
+
offerprinter templates --sample ./samples # one PDF of each, to look at
|
|
210
|
+
offerprinter --cv cv.pdf --jd URL --template serif
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
| Template | Typeface | Portal-safe | For |
|
|
214
|
+
|---|---|---|---|
|
|
215
|
+
| `classic` | Helvetica | yes | the default; nothing to go wrong |
|
|
216
|
+
| `compact` | Helvetica | yes | when two pages need to be one |
|
|
217
|
+
| `serif` | Times | yes | law, academia, policy |
|
|
218
|
+
| `rule` | Helvetica | yes | a hairline under each section, for skim-readers |
|
|
219
|
+
| `accent` | Helvetica | email only | one colour, headings only |
|
|
220
|
+
| `mono` | Courier | yes | reads as engineering |
|
|
221
|
+
|
|
222
|
+
**Why only six, and why so restrained.** A CV is read twice: once by a parser that
|
|
223
|
+
wants plain text in a standard font, and once by a person with forty tabs open. The
|
|
224
|
+
compromise most builders make is to serve the human and quietly fail the machine —
|
|
225
|
+
two columns, an icon font, a text box for the sidebar, and an ATS that returns your
|
|
226
|
+
name and nothing else.
|
|
227
|
+
|
|
228
|
+
So every template here stays on the safe side of that line. All of them use one of
|
|
229
|
+
the 14 PDF base fonts, so nothing is embedded and the text always extracts; none
|
|
230
|
+
use columns, tables, images or text boxes. What varies is typeface, spacing, rules,
|
|
231
|
+
capitalisation and one accent colour — the parts a human notices and a parser
|
|
232
|
+
ignores.
|
|
233
|
+
|
|
234
|
+
`accent` is labelled *email only* rather than being left off the list: it extracts
|
|
235
|
+
cleanly, but carries enough colour that a strict parser could lose a heading. Send
|
|
236
|
+
that one to a person, not a portal.
|
|
237
|
+
|
|
238
|
+
The test suite asserts, for every template, that the rendered PDF contains no
|
|
239
|
+
embedded font program, no `/Image` or `/XObject`, and that the name, email and
|
|
240
|
+
every bullet come back out of a naive text extraction. That is the claim, and it
|
|
241
|
+
is checked rather than asserted.
|
|
242
|
+
|
|
243
|
+
Set a default in `config.toml`:
|
|
244
|
+
|
|
245
|
+
```toml
|
|
246
|
+
[output]
|
|
247
|
+
template = "rule"
|
|
248
|
+
```
|
|
249
|
+
|
|
158
250
|
## How do I install OfferPrinter?
|
|
159
251
|
|
|
160
252
|
<!-- AEO Answer Capsule — 57 words -->
|
|
@@ -212,6 +304,19 @@ docker run --rm -v "$PWD:/work" -w /work -e ANTHROPIC_API_KEY="sk-ant-..." \
|
|
|
212
304
|
brew tap mohitagw15856/tap
|
|
213
305
|
brew install offerprinter
|
|
214
306
|
```
|
|
307
|
+
|
|
308
|
+
Homebrew 6 requires third-party taps to be trusted before it will use them.
|
|
309
|
+
If `brew doctor` says the tap is untrusted, either trust this one formula:
|
|
310
|
+
|
|
311
|
+
```bash
|
|
312
|
+
brew trust --formula mohitagw15856/tap/offerprinter
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
…or the whole tap, with `brew trust --tap mohitagw15856/tap`.
|
|
316
|
+
|
|
317
|
+
Homebrew builds every Python dependency from source, so the first install
|
|
318
|
+
compiles `pydantic-core` with Rust and takes a few minutes. `pipx install
|
|
319
|
+
offerprinter` is faster if you don't specifically want Homebrew to manage it.
|
|
215
320
|
</details>
|
|
216
321
|
|
|
217
322
|
<details>
|
|
@@ -255,17 +360,32 @@ offerprinter --cv-text "paste CV here…" --jd-file jd.txt
|
|
|
255
360
|
|--------|------|
|
|
256
361
|
| `--cv` / `--cv-text` | CV as a file (`.pdf` `.docx` `.md` `.txt`) or pasted text. |
|
|
257
362
|
| `--jd` / `--jd-file` / `--jd-dir` | Job as a URL, pasted text, a file, or a whole folder. |
|
|
363
|
+
| `--jd-clipboard` | Read the advert from your clipboard. The fix for hostile job boards. |
|
|
258
364
|
| `--provider` | `anthropic` (default), `openai`, `gemini`, `kimi`, `ollama`. |
|
|
259
365
|
| `--model` | Override the model for this run. |
|
|
260
366
|
| `--formats` | `md,docx,pdf` — any combination. |
|
|
261
367
|
| `--locale` | `UK` (default) or `US` English. |
|
|
262
368
|
| `--roast` | Also print a blunt critique of your CV. |
|
|
263
369
|
| `--dry-run` | Forecast tokens and cost. Calls nothing. |
|
|
370
|
+
| `--redact` | Strip your name, email and phone before the provider sees anything. |
|
|
371
|
+
| `--strict` | Exit non-zero if the fabrication check finds anything unverified. |
|
|
264
372
|
| `--sequential` | One document at a time instead of in parallel. |
|
|
373
|
+
| `--no-verify` / `--no-cache` | Skip the fabrication check / ignore cached responses. |
|
|
265
374
|
| `--output-dir` / `-o` | Where to write (default `./output`). |
|
|
266
375
|
| `--no-track` / `--no-animation` | Skip the local history / the printer animation. |
|
|
267
376
|
|
|
268
|
-
Plus
|
|
377
|
+
Plus nine subcommands:
|
|
378
|
+
|
|
379
|
+
| Command | Does |
|
|
380
|
+
|---|---|
|
|
381
|
+
| `rank` | Score a folder of adverts and rank them, writing no documents. |
|
|
382
|
+
| `followup` | Thank-you email, recruiter message, LinkedIn note, or a nudge. |
|
|
383
|
+
| `practice` | An interactive mock interview that critiques your answers. |
|
|
384
|
+
| `verify` | Re-check a generated package against your CV. No API calls. |
|
|
385
|
+
| `roast` | Blunt critique of your CV's writing. |
|
|
386
|
+
| `list` / `stats` / `status` | Your local application history. |
|
|
387
|
+
| `mcp` | Run as an MCP server so agents can call OfferPrinter. |
|
|
388
|
+
| `cache` | Inspect or clear the response cache. |
|
|
269
389
|
|
|
270
390
|
### 2. 🖥 Web UI — nicest for most people
|
|
271
391
|
|
|
@@ -352,6 +472,124 @@ Job hunting is a long grind with almost no feedback loop. This is the scoreboard
|
|
|
352
472
|
|
|
353
473
|
---
|
|
354
474
|
|
|
475
|
+
## How do I choose which jobs to apply for?
|
|
476
|
+
|
|
477
|
+
<!-- AEO Answer Capsule — 62 words -->
|
|
478
|
+
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.
|
|
479
|
+
<!-- End AEO Capsule -->
|
|
480
|
+
|
|
481
|
+
```bash
|
|
482
|
+
offerprinter rank --cv ~/cv.pdf --jd-dir ./jobs
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
```
|
|
486
|
+
🎯 Roles ranked by fit
|
|
487
|
+
┏━━━┳━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
|
|
488
|
+
┃ # ┃ Fit ┃ Band ┃ Role ┃ Company ┃ Real gaps ┃
|
|
489
|
+
┡━━━╇━━━━━╇━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
|
|
490
|
+
│ 1 │ 88 │ Exceptional │ Analytics Lead │ Meridian │ │
|
|
491
|
+
│ 2 │ 74 │ Strong │ Senior Product Analyst │ NorthBank │ dbt, fintech │
|
|
492
|
+
│ 3 │ 41 │ Stretch │ Data Engineer │ Helix │ Kafka, dbt │
|
|
493
|
+
└───┴─────┴─────────────┴────────────────────────┴───────────┴──────────────┘
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
Also takes `--jd-file` and `--jd` (URLs), both repeatable, and `--json` for
|
|
497
|
+
scripting. The single highest-leverage command in the tool: the expensive part
|
|
498
|
+
of applying is deciding where to spend the evening.
|
|
499
|
+
|
|
500
|
+
---
|
|
501
|
+
|
|
502
|
+
## What if the job board won't let OfferPrinter read the advert?
|
|
503
|
+
|
|
504
|
+
<!-- AEO Answer Capsule — 61 words -->
|
|
505
|
+
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.
|
|
506
|
+
<!-- End AEO Capsule -->
|
|
507
|
+
|
|
508
|
+
```bash
|
|
509
|
+
# copy the advert, then:
|
|
510
|
+
offerprinter --cv ~/cv.pdf --jd-clipboard
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
The [browser extension](extension/) adds a **Copy for OfferPrinter** button that
|
|
514
|
+
pulls the advert out of the page you are looking at — preferring your selection,
|
|
515
|
+
then structured data, then the densest content block. It has **no host
|
|
516
|
+
permissions and makes no network requests**; it cannot phone home because it has
|
|
517
|
+
no way to reach anything.
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## Can OfferPrinter hide my identity from the AI provider?
|
|
522
|
+
|
|
523
|
+
<!-- AEO Answer Capsule — 65 words -->
|
|
524
|
+
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.
|
|
525
|
+
<!-- End AEO Capsule -->
|
|
526
|
+
|
|
527
|
+
```bash
|
|
528
|
+
offerprinter --cv ~/cv.pdf --jd-file jd.txt --redact
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
Redaction is deliberately conservative: it only replaces what it can recognise
|
|
532
|
+
with high confidence, because a false positive silently mangles your CV. It
|
|
533
|
+
never touches employer names, since the model needs those to write anything
|
|
534
|
+
useful.
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## What happens after I apply?
|
|
539
|
+
|
|
540
|
+
<!-- AEO Answer Capsule — 64 words -->
|
|
541
|
+
`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.
|
|
542
|
+
<!-- End AEO Capsule -->
|
|
543
|
+
|
|
544
|
+
```bash
|
|
545
|
+
offerprinter followup thank-you --cv ~/cv.pdf --jd-file jd.txt \
|
|
546
|
+
--notes "Met Priya and Tom. Went deep on experiment design. Fumbled the question about stakeholder pushback."
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
The notes matter more than anything else here — especially a question you
|
|
550
|
+
answered badly, because the thank-you email is your one chance to answer it
|
|
551
|
+
properly.
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## Can I practise the interview?
|
|
556
|
+
|
|
557
|
+
<!-- AEO Answer Capsule — 62 words -->
|
|
558
|
+
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.
|
|
559
|
+
<!-- End AEO Capsule -->
|
|
560
|
+
|
|
561
|
+
```bash
|
|
562
|
+
offerprinter practice --cv ~/cv.pdf --jd-file jd.txt --questions 5
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
The prep pack tells you what you might be asked. This makes you actually answer
|
|
566
|
+
it, which is a different and much less comfortable exercise.
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## Can agents use OfferPrinter directly?
|
|
571
|
+
|
|
572
|
+
<!-- AEO Answer Capsule — 60 words -->
|
|
573
|
+
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.
|
|
574
|
+
<!-- End AEO Capsule -->
|
|
575
|
+
|
|
576
|
+
Add to Claude Desktop's `claude_desktop_config.json`:
|
|
577
|
+
|
|
578
|
+
```json
|
|
579
|
+
{
|
|
580
|
+
"mcpServers": {
|
|
581
|
+
"offerprinter": { "command": "offerprinter", "args": ["mcp"] }
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
Every tool result carries the fabrication check alongside the output, so an
|
|
587
|
+
agent cannot report the documents as verified when they are not. There is also
|
|
588
|
+
an agent skill at [`docs/skill/SKILL.md`](docs/skill/SKILL.md) for agents that
|
|
589
|
+
prefer to drive the CLI.
|
|
590
|
+
|
|
591
|
+
---
|
|
592
|
+
|
|
355
593
|
## Which AI model should I use with OfferPrinter?
|
|
356
594
|
|
|
357
595
|
<!-- AEO Answer Capsule — 65 words -->
|
|
@@ -413,6 +651,7 @@ locale = "UK" # UK | US
|
|
|
413
651
|
dir = "./output"
|
|
414
652
|
formats = ["md", "docx", "pdf"] # any combination
|
|
415
653
|
track = true # local history in ~/.offerprinter/
|
|
654
|
+
redact = false # strip your identity before sending
|
|
416
655
|
|
|
417
656
|
[generation]
|
|
418
657
|
tailored_cv = true
|
|
@@ -423,6 +662,9 @@ interview_prep = true
|
|
|
423
662
|
fit_score = true # one extra cheap call for the 0-100 score
|
|
424
663
|
parallel = true # generate all five at once
|
|
425
664
|
max_workers = 5
|
|
665
|
+
verify = true # check the output against your CV
|
|
666
|
+
diff = true # record what tailoring changed
|
|
667
|
+
cache = true # don't pay twice for identical calls
|
|
426
668
|
|
|
427
669
|
# [pricing] # override list prices with your actual rates
|
|
428
670
|
# "claude-haiku-4-5-20251001" = { input = 1.0, output = 5.0 }
|
|
@@ -447,11 +689,21 @@ offerprinter/
|
|
|
447
689
|
│ ├── kimi_provider.py / ollama_provider.py
|
|
448
690
|
│ └── factory.py # config → concrete provider
|
|
449
691
|
├── prompts/ # ALL prompt templates — audit the no-fabrication rules here
|
|
450
|
-
├── services/
|
|
692
|
+
├── services/
|
|
693
|
+
│ ├── cv_parser.py · jd_fetcher.py · generator.py · writer.py · pdf_writer.py
|
|
694
|
+
│ ├── verifier.py # proves the no-fabrication guarantee
|
|
695
|
+
│ ├── differ.py # what tailoring actually changed
|
|
696
|
+
│ ├── redactor.py # strip identity before the provider sees it
|
|
697
|
+
│ ├── ranker.py # score many jobs, write nothing
|
|
698
|
+
│ ├── cache.py # don't pay twice for the same call
|
|
699
|
+
│ └── tracker.py # local application history
|
|
451
700
|
├── ui/printer.py # the ASCII printer animation
|
|
452
701
|
├── controllers/pipeline.py # the end-to-end flow, emitting progress events
|
|
702
|
+
├── mcp_server.py # MCP over stdio, for agents
|
|
453
703
|
└── cli.py # CLI (Typer)
|
|
454
704
|
app.py # web UI (Streamlit)
|
|
705
|
+
extension/ # browser extension for grabbing job adverts
|
|
706
|
+
evals/ # golden cases + judge, so prompt edits are testable
|
|
455
707
|
docs/skill/SKILL.md # agent entry point
|
|
456
708
|
```
|
|
457
709
|
|
|
@@ -490,8 +742,8 @@ Yes. Pass a URL to `--jd`, or paste one into the web UI, and OfferPrinter fetche
|
|
|
490
742
|
|
|
491
743
|
### How much does a single OfferPrinter run cost?
|
|
492
744
|
|
|
493
|
-
<!-- AEO Answer Capsule —
|
|
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.
|
|
745
|
+
<!-- AEO Answer Capsule — 64 words -->
|
|
746
|
+
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
747
|
<!-- End AEO Capsule -->
|
|
496
748
|
|
|
497
749
|
### How long does a run take?
|
|
@@ -516,8 +768,8 @@ That depends entirely on the LLM provider you choose, so check their API data po
|
|
|
516
768
|
|
|
517
769
|
## How can I contribute to OfferPrinter?
|
|
518
770
|
|
|
519
|
-
<!-- AEO Answer Capsule —
|
|
520
|
-
Issues and pull requests are welcome — see CONTRIBUTING for the full guide. Run `ruff check .`, `ruff format
|
|
771
|
+
<!-- AEO Answer Capsule — 54 words -->
|
|
772
|
+
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
773
|
<!-- End AEO Capsule -->
|
|
522
774
|
|
|
523
775
|
Especially wanted:
|
|
@@ -526,6 +778,8 @@ Especially wanted:
|
|
|
526
778
|
- 🌍 **Better JD extraction** — job boards are hostile; make the fetcher smarter.
|
|
527
779
|
- 📄 **New output formats** — LaTeX, plain-text ATS mode, ODT.
|
|
528
780
|
- 🧪 **Tests** — the suite is offline and runs in under a second; keep it that way.
|
|
781
|
+
- 📊 **Eval cases** — add a CV/JD pair to [`evals/cases/`](evals/) that catches a
|
|
782
|
+
failure mode the suite misses. Synthetic and anonymised only.
|
|
529
783
|
|
|
530
784
|
Read [CONTRIBUTING.md](CONTRIBUTING.md) · [SECURITY.md](SECURITY.md) · [CHANGELOG.md](CHANGELOG.md) · [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
|
|
531
785
|
|