>_ZPWR-JOBS
A private job-application management pipeline built on stryke. One repo holds every application: the resume, each company's posting, the tailored cover-letter prose, and the rendered docx + pdf deliverables. Drop a posting and run s run — it AI-drafts the letter (stryke's ai builtin), renders both formats via stryke-office, and opens the result. Letters can also be hand-written; the render path is pure native Rust — no LibreOffice, no soffice, no python.
What it manages
| Identity | config.toml at the repo root — username (the output-filename token) plus the letterhead: name, two contact lines, and the accent color. |
| Resume | resume.txt at the repo root — the single source of candidate facts and the visual style every letter matches (serif throughout, cyan #26ADE4 section bars). |
| Job postings | one per application at jobs/<JOB>/job_desc.txt — the raw posting, kept for reference and as the tailoring source. |
| Cover letters | tailored prose at jobs/<JOB>/letter.txt; rendered to jobs/<JOB>/<Company>_JacobMenke.docx and .pdf. |
Layout
zpwr-jobs/
stryke.toml # stryke project manifest ([package], [bin], [scripts])
config.toml # username + letterhead (identity)
resume.txt # candidate facts + style reference
main.stk # `s run` entry — scan jobs, AI-draft, render, open
cover_letter_pipeline.stk # the renderer (stryke + stryke-office)
draft_letter.stk # AI letter drafter (the `ai` builtin)
lib/CoverLetter.stk # pure text + parse helpers (unit-tested)
t/ # unit tests — `s test` (stryke only, runs in CI)
resume.html # base resume source -> pdf
resume-tech.html # + per-role tech stacks -> pdf
resume-fulltime.html # full-time roles only -> pdf
resume-noselfemployed.html # minus the self-employed role-> pdf
resume-nocounts.html # minus every count -> pdf
resume-nocounts-tech.html # no-counts + tech stacks -> pdf
resume-consultant.html # consulting / architect -> pdf
resume-ai.html # AI / LLM engineer -> pdf
exec-summary.html # one-page executive brief -> pdf
frontend-summary.html # one-page frontend brief -> pdf
clients.html # selected projects & work (CV)-> pdf
render-pdf.js # headless-Chrome CDP renderer for every variant
render-resume.sh # wrapper -> `node render-pdf.js resume`
render-cv.sh # wrapper -> `node render-pdf.js cv`
docs/ # this HUD documentation
jobs/
<JOB>/
job_desc.txt # the raw posting (input)
letter.txt # tailored letter (input, or AI-drafted)
<Company>_<user>.docx # rendered deliverable (output)
<Company>_<user>.pdf # rendered deliverable (output)
Architecture
The renderer is one stryke script: four file inputs collapse into a single block model, which stryke-office emits as both formats; a one-page guard gates every write. The render path is pure native Rust — no soffice, no python, no network. Drafting letter.txt with the ai builtin is a separate, optional upstream step (draft_letter.stk); main.stk orchestrates draft + render for s run (see Run it).
cover_letter_pipeline.stk
jobs/<JOB>/
Run it
The whole workflow: drop a posting in as jobs/<JOB>/job_desc.txt, then run s run. Bare s run scans every job and brings it up to date — letter missing → AI-draft then render; pdf missing or older than the letter → render; otherwise skip — then opens what it rendered (or, when nothing needed work, the most recently modified letter by mtime). It is idempotent.
# 1) create jobs/<JOB>/job_desc.txt 2) run: s run # process all jobs; open results (newest by mtime) s run main.stk <JOB> # one job; opens it even if already up to date # individual steps (stryke.toml [scripts] aliases; trailing args forwarded): s run gen <JOB> # render an existing letter.txt only (no AI) s run draft <JOB> # AI-draft letter.txt only (no render) s run apply <JOB> # AI-draft AND render one job s run gen reuters -f docx -p a4 # flags pass straight through s test # run the unit tests in t/
The renderer requires stryke-office installed (s pkg install -g github.com/MenkeTechnologies/stryke-office). AI drafting uses stryke's ai builtin (Anthropic) and needs ANTHROPIC_API_KEY; apply redrafts shorter (up to 3×) if a draft overflows one page. The s test suite needs only stryke (no stryke-office, no rendering), so it runs in headless CI.
Settings & CLI
Behavior is set in config.toml (parsed by stryke's native toml_decode — keep comments on their own lines, since a trailing # … is kept as part of the value) and overridable per run on the CLI via stryke's getopts builtin (CLI wins). Flags may go before or after <JOB> (short or long); long names match config keys (dashes → underscores). To place a flag first, prefix the args with -- (e.g. … -- -f docx reuters) so stryke passes them through instead of reading the leading dash as one of its own switches. open_after is a negatable boolean: --open-after / -o / --no-open-after.
[generator] verbosity -v | quiet | normal | verbose |
[generator] formats -f | which outputs to emit, e.g. docx,pdf or docx |
[generator] page -p | letter | a4 |
[generator] open_after -o | open the result when done (macOS open) |
[letter] max_paragraphs -P | cap body paragraphs (content length; 0 = no cap) |
[letter] max_words -w | cap total body words (0 = no cap; over-limit aborts) |
[letter] body_size -b | body font size in points |
[letter] head_size -H | letterhead/date/recipient/salutation/closing/name size (pt) |
[letter] line_height -L | pdf line-height multiplier (× font size); lower to fit a long letter on one page |
[letter] margin -m | page margin in inches (pdf) |
[letter] subject_prefix -s | text before the Subject in the bar (default Re: ) |
[letter] closing -c | sign-off line above the name (default Sincerely,) |
[letter] salutation -l | default greeting when letter.txt omits it |
[identity] accent -A / ink -k | bar fill color / body + letterhead text color |
[identity] body_font -B / pdf_font -d | docx serif font / pdf base-14 serif font |
stryke cover_letter_pipeline.stk reuters --formats docx --page a4 \ --max-paragraphs 4 --body-size 12 --verbosity verbose --open-after stryke cover_letter_pipeline.stk reuters -f docx -p a4 -P 4 -b 12 -v verbose -o stryke cover_letter_pipeline.stk -- -f docx -P 4 reuters # flags before JOB
letter.txt format
Key: value headers, then a blank line, then body paragraphs separated by blank lines. Each paragraph becomes one justified block in the letter.
Date: {{date}}
Recipient1: Hiring Team
Recipient2: Mariana Minerals — Ann Arbor, MI
Subject: Senior Full Stack Software Engineer
Company: Mariana
I am applying for the {{role}} role at Mariana Minerals …
Second body paragraph …
Any field — header value or body paragraph — may use mustache {{placeholders}}, filled by stryke's stdlib template() from a shared map: every header (lowercased), role (alias for subject), and username/name/address/phone/email/website/github/contact1/contact2/accent from config.toml (contact1/contact2 are the assembled letterhead lines).
| Date | letter date line; {{date}} or omitted → today's date (Month D, YYYY) |
| Recipient1 / Recipient2 | recipient block (line 1 rendered bold) |
| Subject | cyan subject bar text, rendered as Re: <Subject> |
| Company | filename token — non-alphanumerics stripped → <Company>_JacobMenke.{docx,pdf} |
| Salutation | greeting line; defaults to Dear Hiring Team, |
How it renders
The script builds a structured block model once and emits two formats from it via stryke-office:
| docx | Office::doc_write — rich runs (Cambria serif throughout), section bars as a single-cell shaded table (bg => "#26ADE4", white bold run), justified body, US-Letter page. |
Office::pdf_build — absolute layout: cyan rect bars with overlaid white text, base-14 Times body, greedy word-wrap, ASCII-folded typographic characters (bullets → |, em dash → -). |
Both come straight from native Rust inside stryke-office — there is no soffice conversion step and no python.
The resume & CV PDFs — a separate path
Cover letters go through stryke-office. The resume and the CV do not: they are authored as HTML and printed by headless Chrome over the Chrome DevTools Protocol. The reason is narrow — Chrome's --print-to-pdf cannot render a page-number footer, so render-pdf.js drives Page.printToPDF directly for displayHeaderFooter + footerTemplate. One script renders every variant.
node render-pdf.js <doc> [--open] ./render-resume.sh [--open] # wrapper -> node render-pdf.js resume ./render-cv.sh [--open] # wrapper -> node render-pdf.js cv
The renderer defines these documents, keyed by the first argument:
resume | resume.html — the base resume, single-column and ATS-clean |
resume-tech | resume-tech.html — the resume plus a per-role tech-stack line |
resume-ft | resume-fulltime.html — full-time roles only |
resume-nose | resume-noselfemployed.html — the base resume minus the self-employed role |
resume-nc | resume-nocounts.html — the base resume minus every product / language / book / invention count |
resume-nctech | resume-nocounts-tech.html — the no-counts resume plus a per-role tech-stack line |
resume-consult | resume-consultant.html — consulting / systems-architect angle: engagement model, modernization practice, client-named roles |
resume-ai | resume-ai.html — AI / LLM engineer angle: RAG and chatbot delivery, the ai language primitive, MCP work |
exec | exec-summary.html — one-page executive summary |
frontend | frontend-summary.html — one-page frontend summary (web-UI roles + the authored zgui-core library) |
cv | clients.html — selected projects & work |
Each entry carries its own output filename, footer label and PDF DOCINFO metadata (Title / Author / Subject / Keywords). The five variants that share the base resume's keyword surface inherit its Keywords rather than restating them. Metadata is stamped in a second pass through Ghostscript; if gs fails, the un-stamped PDF is kept and a warning printed rather than losing the render.
Geometry is fixed in the script — US Letter, 0.5″ side and top margins, 0.6″ bottom, printBackground on, preferCSSPageSize off. After writing, it reports the page count via pdfinfo and, for the documents that declare an atsCheck string, asserts that the contact line still survives pdftotext extraction — so a layout change cannot silently make the resume unreadable to an applicant-tracking system.
Chrome is launched --headless=new on port 9411 and killed when the render completes. Its binary path is hardcoded to the macOS Chrome bundle, so this path is macOS-only as written. The prerequisites are Chrome, Ghostscript (gs) and poppler (pdfinfo / pdftotext) — none of which the cover-letter pipeline touches.
Add a new application
- Create
jobs/<JOB>/and drop the posting in asjob_desc.txt. - Run
s run. It AI-drafts the missingletter.txt(senior-engineer voice, real resume facts only), renders thedocx+pdf, and opens the pdf. Done. - Review the draft. To revise, edit
letter.txtands runagain to re-render — or hand-writeletter.txtbefore the firsts runto skip AI drafting entirely.
Built on
- strykelang — the stryke language and runtime.
- stryke-office — native-Rust office document I/O (docx + pdf rendering).