resume

Log | Files | Refs | README

README.md (7492B)


      1 # Resume and Cover Letter Build System
      2 
      3 Resumes and cover letters are written in Markdown and rendered to PDF using pandoc and weasyprint.
      4 
      5 ## Required Packages
      6 
      7 ```bash
      8 sudo pacman -S pandoc python-weasyprint
      9 ```
     10 
     11 ## Directory Structure
     12 
     13 ```
     14 job_search/
     15 ├── job_tracker.py                       # Application tracker CLI
     16 ├── job_search.db                        # SQLite database
     17 ├── job_postings/                        # Raw job posting text files
     18 └── resume/
     19     ├── Chris_Roberts_Resume.md          # Master resume — never edit this
     20     ├── resume.css                       # Stylesheet for resume PDFs
     21     ├── cover_letters/
     22     │   └── cover_letter.css             # Stylesheet for cover letter PDFs
     23     ├── interview_prep/                  # Interview prep documents
     24     └── <role_dir>/                      # One directory per application, named for the role
     25         ├── Chris_Roberts_Resume.md
     26         ├── Chris_Roberts_Resume.pdf
     27         ├── Chris_Roberts_Cover_Letter.md
     28         └── Chris_Roberts_Cover_Letter.pdf
     29 ```
     30 
     31 Every role directory (e.g. `cei_help_desk/`, `springdale_ditt/`) holds its resume and cover letter under the same generic filenames — the directory name is what identifies the role, not the filename. This keeps paths predictable and lets scripts/globs find "the resume" or "the cover letter" for any role without knowing its name in advance.
     32 
     33 ## Generating a Resume PDF
     34 
     35 Run from inside the role directory:
     36 
     37 ```bash
     38 pandoc Chris_Roberts_Resume.md -o Chris_Roberts_Resume.pdf --pdf-engine=weasyprint --css=../resume.css
     39 ```
     40 
     41 ## Generating a Cover Letter PDF
     42 
     43 Run from inside the role directory:
     44 
     45 ```bash
     46 pandoc Chris_Roberts_Cover_Letter.md -o Chris_Roberts_Cover_Letter.pdf --pdf-engine=weasyprint --css=../cover_letters/cover_letter.css
     47 ```
     48 
     49 ## Workflow
     50 
     51 1. Create a new directory under `resume/` named for the role (e.g. `resume/company_role_name/`)
     52 2. Copy `Chris_Roberts_Resume.md` into it and tailor the copy — never modify the master
     53 3. Write `Chris_Roberts_Cover_Letter.md` in the same directory
     54 4. Generate both PDFs using the commands above (from inside the role directory)
     55 5. Confirm the cover letter PDF used `cover_letter.css`, not `resume.css` — check with `pdffonts`: it should embed Noto-Serif/Georgia, not Liberation-Sans/Arial
     56 
     57 ## Job Tracker
     58 
     59 Applications are tracked in a SQLite database at `job_search/job_search.db`, managed with `job_tracker.py`.
     60 
     61 Add an alias to `~/.zshrc` for convenience:
     62 
     63 ```bash
     64 alias jt="python /home/cjr/claude/job_search/job_tracker.py"
     65 ```
     66 
     67 Common commands (run from anywhere once the alias is set):
     68 
     69 ```bash
     70 jt list                                          # all jobs, sorted by status
     71 jt show 2                                        # full detail for job id 2
     72 jt add "Company Name" "Job Title"                # add a new job (interactive prompts)
     73 jt add "Company Name" "Job Title" --location "Remote" --remote y \
     74    --posting_file "job_postings/x.txt" --resume_file "resume/x/Chris_Roberts_Resume.pdf" \
     75    --cover_letter_file "resume/x/Chris_Roberts_Cover_Letter.pdf" --status tracking \
     76    --fit_rating moderate --fit_notes "..." --notes "..."
     77                                                   # add a new job non-interactively (any --field
     78                                                   # from ADD_FLAGS in job_tracker.py; add
     79                                                   # `--field value` at all and prompts are skipped)
     80 jt update 2 status interviewing                  # update status
     81 jt add-interview 2 2026-07-01 in_person "Jane Smith, HR" "Went well, discussed..."
     82 jt update-interview 3 outcome advanced           # update an interview outcome
     83 jt add-note 2 "Sent thank-you email"             # append a timestamped note
     84 jt add-contact 2 "Jane Smith" "HR Manager"       # record someone you met
     85 ```
     86 
     87 Valid statuses: `tracking`, `applied`, `phone_screen`, `interviewing`, `offer`, `rejected`, `withdrawn`, `hired`
     88 
     89 ## Job Posting Inbox (automatic intake)
     90 
     91 A systemd user service watches `job_postings/inbox/` and automatically screens anything dropped
     92 there: `~/.config/systemd/user/job-inbox.path` fires `job-inbox.service`
     93 (`job_search/scripts/process_inbox.py`) whenever the directory goes from empty to non-empty.
     94 
     95 **Drop convention:** write the new posting file somewhere else first, then `mv` it into
     96 `job_postings/inbox/` — a same-filesystem `mv` is atomic, so the watcher never sees a half-written
     97 file mid-download or mid-save. Don't save directly into `inbox/`.
     98 
     99 What the daemon does, automatically, per file:
    100 1. Sanity checks (non-empty, reasonable size, valid UTF-8 text)
    101 2. A cheap classification pass (small model, no tool access, tight budget cap) confirming it's
    102    actually a job posting and pulling out company/title/location/salary/etc.
    103 3. A dedup check against `job_search.db` (same company + title already tracked)
    104 
    105 What it does NOT do: generate a resume/cover letter or write a `job_search.db` row. That step
    106 writes files and runs shell commands, so it's deliberately left for a normal interactive Claude
    107 Code session — no unattended permission bypass. On success, the daemon moves the posting into
    108 `job_postings/ready/<file>` with a `<file>.meta.json` sidecar of the extracted fields, and fires a
    109 desktop notification (`notify-send`) telling you to ask Claude Code to process it. From there it's
    110 the same manual flow as every other posting: fit assessment, tailored resume/cover letter, PDFs,
    111 `jt add`.
    112 
    113 Everything else lands in a folder next to `inbox/` for you to look at, never silently:
    114 `job_postings/rejected/` (not a job posting, or a duplicate — reason logged alongside),
    115 `job_postings/failed/` (a real posting, but a transient API error — retry it),
    116 `job_postings/throttled/` (more than 5 files landed at once — reviewed manually before being
    117 re-dropped). These are deliberately siblings of `inbox/`, not children of it — a subdirectory
    118 inside `inbox/` would make it permanently "not empty" and the daemon would never stop retriggering.
    119 
    120 Enable/check status:
    121 ```bash
    122 systemctl --user enable --now job-inbox.path     # turn the watcher on (persists across reboots
    123                                                   # while logged in; no linger, so it does NOT
    124                                                   # run while logged out)
    125 systemctl --user disable --now job-inbox.path    # turn it off
    126 systemctl --user status job-inbox.path           # confirm it's active
    127 journalctl --user -u job-inbox.service -f        # watch it process a drop live
    128 tail -f job_search/logs/inbox.log                # flat log, easier to grep than the journal
    129 ```
    130 
    131 Run it by hand (e.g. to reprocess something without waiting for a trigger):
    132 ```bash
    133 python3 scripts/process_inbox.py
    134 ```
    135 
    136 To retry something that landed in `failed/` (transient API error) or release files from
    137 `throttled/` (burst overflow) back into the pipeline, just `mv` them back into `inbox/`:
    138 ```bash
    139 mv job_postings/failed/<file> job_postings/inbox/
    140 mv job_postings/throttled/<file> job_postings/inbox/
    141 ```
    142 
    143 ## Notes
    144 
    145 - The `--pdf-engine=weasyprint` flag is required; pandoc's default LaTeX engine does not use CSS
    146 - weasyprint will emit warnings about unsupported CSS properties — these are harmless
    147 - Use two trailing spaces at the end of a line to force a line break within a paragraph (standard markdown hard break)
    148 - Cover letter CSS is set to 10.5pt Georgia, 1in margins to fit content on one page