>_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)
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.
Add a new application
- Create
jobs/<JOB>/and drop the posting in asjob_desc.txt. - Run
s run. It AI-drafts the missingletter.txt(Principal/Staff 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).