// ZPWR-JOBS — JOB-APPLICATION MANAGEMENT PIPELINE // RESUMES + COVER LETTERS + POSTINGS

stryke project · s runmain.stk (AI-draft + render) · renders via use Office · native (no LibreOffice/soffice/python)

stryke-office
// Color scheme

>_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

Identityconfig.toml at the repo root — username (the output-filename token) plus the letterhead: name, two contact lines, and the accent color.
Resumeresume.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 postingsone per application at jobs/<JOB>/job_desc.txt — the raw posting, kept for reference and as the tailoring source.
Cover letterstailored 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).

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 -vquiet | normal | verbose
[generator] formats -fwhich outputs to emit, e.g. docx,pdf or docx
[generator] page -pletter | a4
[generator] open_after -oopen the result when done (macOS open)
[letter] max_paragraphs -Pcap body paragraphs (content length; 0 = no cap)
[letter] max_words -wcap total body words (0 = no cap; over-limit aborts)
[letter] body_size -bbody font size in points
[letter] head_size -Hletterhead/date/recipient/salutation/closing/name size (pt)
[letter] line_height -Lpdf line-height multiplier (× font size); lower to fit a long letter on one page
[letter] margin -mpage margin in inches (pdf)
[letter] subject_prefix -stext before the Subject in the bar (default Re: )
[letter] closing -csign-off line above the name (default Sincerely,)
[letter] salutation -ldefault greeting when letter.txt omits it
[identity] accent -A / ink -kbar fill color / body + letterhead text color
[identity] body_font -B / pdf_font -ddocx 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).

Dateletter date line; {{date}} or omitted → today's date (Month D, YYYY)
Recipient1 / Recipient2recipient block (line 1 rendered bold)
Subjectcyan subject bar text, rendered as Re: <Subject>
Companyfilename token — non-alphanumerics stripped → <Company>_JacobMenke.{docx,pdf}
Salutationgreeting 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:

docxOffice::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.
pdfOffice::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

  1. Create jobs/<JOB>/ and drop the posting in as job_desc.txt.
  2. Run s run. It AI-drafts the missing letter.txt (Principal/Staff voice, real resume facts only), renders the docx + pdf, and opens the pdf. Done.
  3. Review the draft. To revise, edit letter.txt and s run again to re-render — or hand-write letter.txt before the first s run to skip AI drafting entirely.

Built on

  • strykelang — the stryke language and runtime.
  • stryke-office — native-Rust office document I/O (docx + pdf rendering).