Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Applicator

File-based job application manager. An AI agent takes a job description, tailors CV and cover letter, runs the LaTeX renderer, and tracks state — all in plain files.


What This Is

Applicator is a local-first workflow for running job applications with an AI coding agent. It keeps job postings, application state, tailored sources, email summaries, meeting notes, callbacks, and generated artifacts in a predictable folder structure.

It is intentionally not a hosted CRM. The repository is the database, JSON files are the source of truth, and the dashboard is derived from files below applications/.

Prerequisites

  • OS: macOS or Linux. Windows works via WSL — the workflow scripts are bash.
  • Python 3.10+ — required by the dashboard, validators, and setup/export scripts.
  • A LaTeX distribution providing pdflatex (TeX Live, MacTeX, or MiKTeX). Needed only to render CV/cover-letter PDFs via scripts/render-tex.sh. The templates use standard packages (geometry, hyperref, babel, titlesec, marvosym, …), so a medium/full install is recommended over a minimal one.
  • Python packages (requirements.txt): rich, openpyxl, jsonschema, cairosvg. cairosvg is used only for the ./index --export-png dashboard snapshot and needs the native Cairo library (brew install cairo / apt-get install libcairo2); the rest of the tool runs without it, and PNG export can also fall back to the rsvg-convert CLI.
  • git — to clone the repository.

Fresh Clone Setup

python3 -m venv .venv
. .venv/bin/activate
pip install -r requirements.txt
python3 scripts/setup_user.py
./index

PDF rendering uses pdflatex, so a local LaTeX installation is required for scripts/render-tex.sh.

scripts/setup_user.py writes the personal profile files under user/, including structured identity/contact data in user/profile.json. It can also import an existing Markdown or text CV:

python3 scripts/setup_user.py --import-cv ~/Documents/cv.md --lang en

After importing an old CV, keep the Markdown source as the readable canonical version, then move stable sections into the LaTeX fragments under user/files/cv/{de,en}/ when you want rendered PDFs.

First Application

To create a new application from a job posting file:

./scripts/new_job.sh "_new/job-posting.html" 2026-07-21 "Example GmbH" "Backend Engineer"

Then point your agent at the created folder under applications/preparation/. The agent should extract details.json, assess match against user/, write build/cv.{lang}.tex and build/cover-letter.{lang}.tex, render PDFs, and prepare any required submission notes.

For an initiative application:

./scripts/new_job.sh --initiative 2026-07-21 "Example GmbH"

For later status updates:

./scripts/set_application_stage.sh applications/preparation/2026-07-21_Example-GmbH_Platform-Backend-Engineer applied "Submitted via company portal"

Validation

To validate the repository before publishing or after editing workflow files:

python3 scripts/validate_project.py

The repository is meant to be publishable in-place: user/ contains an anonymized starter profile, and scripts/setup_user.py turns that starter profile into a personal one after cloning. Structured identity, contact fields, application languages, and the rendered artifact filename slug live in user/profile.json and are validated against schemas/user-profile.schema.json; user/details.md is free-form agent context. A fictional example application is included so the dashboard, indexes, generated LaTeX sources, submission email, and rendered PDFs are visible immediately.

Remove the example application with:

./scripts/remove_example_application.sh

For a one-time publish check, add any private terms you want blocked:

./scripts/remove_example_application.sh
python3 scripts/validate_project.py --no-applications \
  --privacy-term "Your Name" \
  --privacy-term "your-private-email@example.com"

You can also put local-only blocked terms in .privacy-terms; that file is gitignored.

Use --no-applications only after removing the demo or when you want to enforce that no application folders are present.


File Structure

root/
├── _new/                         # Inbox for newly captured job materials
├── applications/
│   ├── preparation/              # Prospecting/open/ready applications
│   ├── running/                  # Applied/interview/offer pipeline
│   ├── closed/                   # Accepted/rejected/withdrawn/ghosted applications
│   ├── deferred/                 # Parked same-company alternatives
│   └── archive/                  # Hidden historical/reference applications
│       └── YYYY-MM-DD_Company_Job-Title/
│           ├── artifacts/        # Final PDFs written by render-tex.sh
│           ├── build/            # Agent-written LaTeX sources
│           ├── emails/           # Curated email summaries, linked to raw/*.eml
│           ├── meetings/
│           ├── raw/              # Web archives, screenshots, original postings
│           ├── details.json      # Single source of truth for this application
│           ├── description.md    # Raw job description (input for agent)
│           └── notes.md          # Free-form notes
├── user/
│   ├── files/
│   │   ├── cv/
│   │   └── cover-letter/
│   ├── documents/              # Markdown summaries of supporting documents
│   ├── feedback/               # Short reusable feedback summaries
│   ├── profile.json            # Structured identity/contact/headline/slug
│   ├── details.md              # Personal profile for agent context
│   ├── skills.md               # Targeted skills beyond the standard CV
│   ├── skill-gaps.md           # Confirmed and pending negative skill inventory
│   └── looking-for.md          # Preferences, dealbreakers, priorities
├── schemas/
│   ├── details.schema.json     # JSON Schema for details.json (validation + autocomplete)
│   ├── user-profile.schema.json # JSON Schema for user/profile.json
│   ├── emails.schema.json      # Canonical emails/index.json structure
│   ├── meetings.schema.json    # Canonical meetings/index.json structure
│   └── details.example.json    # Filled example
├── scripts/
│   ├── new_job.sh              # Bootstrap a new application folder
│   ├── setup_user.py           # Interactive profile setup after cloning
│   ├── validate_project.py     # JSON/schema/workflow/privacy validation
│   ├── adopt_new_material.sh   # Move extra _new files into an application raw/
│   ├── set_application_stage.sh # Update status.stage and move folder bucket
│   ├── defer_application.sh     # Park a secondary role behind a primary application
│   ├── reject_application.sh    # Reject primary and promote best deferred alternative
│   ├── archive_application.sh   # Archive an application and hide it from dashboard rows
│   ├── organize_applications.sh # Sort existing folders by status.stage
│   └── render-tex.sh           # Compile LaTeX to PDF
├── bin/                        # Render working directory — gitignored
│   └── 2026-05-20_Acme_PM/     # Per-job render folder, logs kept after run
│       ├── CV.de.log
│       ├── CV.de.aux
│       └── ...
└── README.md

Agent Workflow

New job materials land in _new/ first. Once a material belongs to a specific application, it is moved into that application's raw/ directory and removed from _new/.

Application folders live under applications/preparation, applications/running, applications/closed, applications/deferred, or applications/archive. New jobs are created in preparation. Use scripts/set_application_stage.sh for status changes so the folder moves automatically: applied, screening, interview, assessment, offer, and negotiation go to running; accepted, rejected, withdrawn, and ghosted go to closed; prospecting, reaching_out, open, and ready stay in preparation; deferred goes to deferred. Use scripts/defer_application.sh for same-company alternatives that should wait for a primary application outcome. Use scripts/archive_application.sh for applications that should be kept for reference but hidden from the dashboard rows.

For extra materials that arrive after the application folder exists:

./scripts/adopt_new_material.sh applications/preparation/YYYY-MM-DD_Company_Job-Title/ "_new/job-material.webarchive"
./scripts/set_application_stage.sh applications/preparation/YYYY-MM-DD_Company_Job-Title applied "Submitted via company portal"
./scripts/organize_applications.sh --dry-run

Email handling: original .eml files belong in raw/ and stay unchanged. Human-readable email summaries belong in emails/, with emails/index.json listing date, direction, subject, contact, summary file, and the corresponding raw/*.eml filename. meetings/ is only for actual calls/interviews/briefings, not email invitations or scheduling messages.

After scripts/new_job.sh has created an application folder, the agent fills the job state and produces the deliverables:

  1. Read raw/ or description.md
  2. Extract structured data into details.json
  3. Assess match against user/profile.json, user/details.md, user/skills.md, user/skill-gaps.md, user/looking-for.md, and user/requirements.json
  4. Write tailored LaTeX files to build/cv.{lang}.tex and build/cover-letter.{lang}.tex
  5. Run ./scripts/render-tex.sh <application-dir>
  6. Verify that the rendered CV PDF is exactly one well-filled page; shorten or restore relevant details and re-render if needed
  7. Report extraction, tailoring decisions, render result, and unresolved uncertainty

The renderer is the only path for PDFs. It writes final files to artifacts/ and keeps logs in bin/{job-name}/. Dashboard and Excel export read details.json recursively below applications/. Archived applications are counted in the dashboard summary but hidden from the dashboard rows. Deferred applications are also hidden from dashboard rows, counted separately, and can be promoted by scripts/reject_application.sh when the linked primary application is rejected. Open callbacks in running applications appear in the dashboard action panel when their due date is today or in the past. The dashboard and Excel export show only meetings with status: completed in each application's meetings/index.json. Entries with status: scheduled or status: cancelled remain available as history but do not increase the count. Every conversation is indexed once; raw notes and reconstructions are supporting files, not additional meeting entries.

Email indexes use file for the Markdown summary and raw for the application-relative original below raw/. The formal schemas are schemas/emails.schema.json and schemas/meetings.schema.json.

Normalize legacy indexes and validate all application indexes with:

./scripts/migrations/migrate_indexes.py
./scripts/validate_indexes.py
./scripts/validate_project.py

German LaTeX output is written as UTF-8 prose with real umlauts and ß (möchten, für, über, Grüßen). ASCII transliterations such as moechten, fuer, or Gruessen are reserved for technical literals like filenames, URLs, commands, and code identifiers. Generated CV PDFs must always stay on one well-filled page. The agent should tailor by prioritizing the strongest job-specific evidence, relevant work, projects, thesis topics, and measurable results instead of leaving large blank areas or carrying every possible detail forward. When the CV overflows, remove only as much as strictly necessary: compact wording and prioritization before cutting relevant content.

user/skills.md captures skills that are true but not always standard-CV material. When a posting asks for one of those skills, the agent should cover it in the most appropriate place: CV if central, cover letter if secondary, match notes if only relevant for scoring, or omit if unrelated. user/skill-gaps.md captures confirmed missing skills and pending skill questions. When a posting asks for something not present in either inventory, the agent asks the user, updates the positive or negative inventory, and then uses that information for scoring and tailoring. Job titles are normalized for applicant-facing text when the cleanup is obvious. The raw posting title is preserved in extensions.job_title_raw for traceability.

Dashboard

./index
./index --run
./index --pre --run
./index --exp
./index --export applications_export.xlsx
./index --png
./index --png dashboard.png --width 140

Use --preparation, --running, --closed, --deferred, or --archive to show only those application buckets. Filters can be combined and also apply to Excel and PNG exports. Their short forms are --pre, --run, --clo, --def, and --arc. The remaining shorthand options are --exp, --png, and --width. --export writes an Excel workbook to applications_export.xlsx in the project root by default, or to the path you pass. --export-png renders the Rich TUI to a PNG snapshot. Without a path it writes dashboard_YYYY-MM-DD.png in the project root.


details.json Schema

Every application folder contains exactly one details.json. It is the single source of truth for the tracker/dashboard.

The formal JSON Schema lives at schemas/details.schema.json. The fields below are the canonical definition.

Top-level structure

{
  "$schema": "../../schemas/details.schema.json",
  "meta":         { ... },
  "status":       { ... },
  "job":          { ... },
  "contact":      { ... },
  "compensation": { ... },
  "match":        { ... },
  "documents":    { ... },
  "callbacks":    [ ... ],
  "extensions":   { ... }
}

meta — File metadata

Field Type Description
version string Schema version this file was written against, e.g. "1.0.0"
created date ISO 8601 — when the application folder was created
updated date ISO 8601 — last manual or automated update

status — Application stage

Field Type Description
stage enum Current stage (see values below)
history array Ordered log of every stage transition

Stage values: openreadyappliedscreeninginterviewassessmentoffernegotiationaccepted | rejected | withdrawn | ghosted | deferred | archived

Each history entry:

{ "date": "YYYY-MM-DD", "stage": "screening", "notes": "HR called, 30min slot booked" }

job — Position details

Field Type Description
title string Official job title
company string Company name
url string Link to the job posting
source string Where found: linkedin, xing, company_site, referral, headhunter, …
posted date | null When the posting went live
deadline date | null Application deadline
location.address string Street address of the office
location.city string
location.country string ISO 3166-1 alpha-2, e.g. "DE"
location.remote enum full, hybrid, onsite
location.remote_days_per_week int | null For hybrid: how many days remote
commute.distance_km number | null Door-to-door distance
commute.time_min_transit int | null Minutes by public transport
commute.time_min_car int | null Minutes by car
commute.notes string e.g. "S-Bahn direkt, kein Umsteigen"

contact — Ansprechpartner

Field Type Description
name string Full name
role string e.g. "Recruiter", "Hiring Manager", "Team Lead"
email string | null
phone string | null
linkedin string | null Profile URL
notes string Anything worth remembering about this person

compensation — Gehalt & Benefits

compensation.offered — Was die Stelle bietet

Field Type Description
base_min number | null Untergrenze des angebotenen Gehalts
base_max number | null Obergrenze des angebotenen Gehalts
currency string ISO 4217, default "EUR"
period enum "annual" or "monthly"
bonus number | null Jährlicher Bonus (absolut)
bonus_notes string z.B. "up to 15% of base, discretionary"
equity string | null Freitext, z.B. "0.1% over 4y cliff 1y"
benefits array of string z.B. ["BVG-Ticket", "30 Tage Urlaub", "ESPP"]

compensation.requested — Was du forderst

Field Type Description
base_min number | null Deine Untergrenze
base_max number | null Dein Zielgehalt (Obergrenze der Forderung)
currency string
period enum
notes string Kontext, z.B. "inkl. Umzugskostenpauschale verhandeln"

compensation.totals — Gesamtbild

Field Type Description
total_comp_estimated number | null Geschätztes Total Comp (base + bonus + equity-value)
realistic_salary number | null Dein realistischer Erwartungswert nach Verhandlung
notes string Freitext für Besonderheiten

match — Fit zwischen dir und der Stelle

Field Type Description
score int | null 0–100, deine Einschätzung des Matches
strengths array of string Wo du klar passt
gaps array of string Was fehlt oder schwach ist
notes string Freitext

documents — Generierte Artefakte

Field Type Description
cv_version string z.B. Git-Hash oder "v3"
cover_letter_version string
other array of string Weitere Anhänge (Portfolio, Zeugnisse, …)

callbacks — Follow-up-Reminder

Callbacks sind strukturierte Erinnerungen für erwartete Rückmeldungen, Interviewtermine und Follow-ups. Eine Bewerbung kann mehrere offene Callbacks haben, z.B. eine Rückmeldung in einer Woche und ein technisches Interview in zwei bis drei Wochen.

Field Type Description
id string Stabiler Identifier pro Bewerbung
kind enum follow_up, expected_feedback, interview, technical_interview, assessment, prepare, other
status enum open, resolved, cancelled
created date Wann der Callback angelegt wurde
due date Ab wann das Dashboard eine Aktion anzeigen soll
expected_from date | null Optionaler Beginn eines erwarteten Zeitfensters
expected_to date | null Optionales Ende eines erwarteten Zeitfensters
action string Konkreter Call to Action
source string Woher die Frist stammt, z.B. Gespräch oder E-Mail
notes string Kontext
resolved date | null Abschlussdatum, sobald der Callback geschlossen ist
resolution_note string Pflicht bei resolved oder cancelled, z.B. "Nachfrage an Jakob geschickt"
priority enum low, normal, high

Nur offene Callbacks in applications/running/ werden als fällige Dashboard-Aktionen gewertet. Geschlossene Callbacks bleiben als Verlauf erhalten und müssen immer festhalten, was passiert ist: z.B. "Feedback erhalten: Technical Interview terminiert", "Nachfrage an HR geschickt" oder "Durch Absage erledigt".


extensions — Erweiterungsfeld

Freies JSON-Objekt für alles, was noch nicht im Schema ist. Kein festes Format.

"extensions": {
  "referral_from": "Max Mustermann",
  "visa_sponsorship": true,
  "languages_required": ["DE", "EN"]
}

Sobald ein extensions-Feld regelmäßig genutzt wird, wird es ins Hauptschema übernommen und die Schema-version erhöht.

About

Local-first, file-based job-application manager: an AI coding agent extracts job data, tailors a LaTeX CV and cover letter, renders PDFs, and tracks every application as plain JSON/Markdown files.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages