Md To Pdf
Convert Markdown to PDF via reportlab or weasyprint engines.
by kochetkov-ma · MIT
The skill
Convert Markdown to PDF via reportlab or weasyprint engines. Triggers - pdf, md to pdf, markdown to pdf, generate pdf.
Markdown to PDF
Convert a local Markdown file with scripts/md_to_pdf.py. Check dependencies with scripts/check_deps.sh, choose a bundled style from styles/, and write only to the requested output path. Store project preferences in .codex/md-to-pdf.config.json only after confirmation. Render and inspect the result before reporting completion.
Complete native workflow
Follow every phase below. When a phase delegates work, use Codex collaboration with only task_name and message; treat each "Codex delegation brief" block as role and message content, not executable syntax. Required approval: main presents a concrete, reviewable proposal in chat and waits for an actual user reply before dependent action. Existing authorization for the same scope remains valid; do not ask again. Optional clarification: use request_user_input_async only if exposed, or request_user_input only if available in the current runtime/mode, for optional choices and never approval. Otherwise ask in main chat. Delegated agents return unresolved questions to main. Silence, elapsed time and tool errors are not approval. Resolve <skill-directory>, <plugin-root>, <project-root>, and <arguments> before running commands.
<!-- brewcode-meta: version=6.10.0 content_version=6.5.3 generated_by=brewdoc:md-to-pdf -->
MD to PDF
Converts Markdown to PDF with the reportlab or weasyprint engine.
Prompt contract
<arguments> is one free-form RU/EN prompt; the file path, the --engine <name> flag, the styles/test
tokens and the quoted LLM-preprocessing prompt may sit anywhere in it.
- Mode: a literal mode token (
styles,config,test,help) wins outright; else the most distinct whole-word keyword hits (table below). Empty ->help(documented default, read-only, asks nothing). - Scope: the prompt may be a full sentence, not just tokens. Take
md_filefrom any path-shaped token or a filename explicitly named in prose (e.g. "convert my notes.md to pdf" ->md_file = notes.md, resolved against cwd). Never guess a file and never silently fall through to HELP. - Ask: a sentence that implies CONVERT with no resolvable file -> ONE
main-chat user gatefor the file path; outcome-changing ambiguity (missing engine deps, no saved engine preference) -> ONEmain-chat user gate.
Print once, before Step 1 (labels literal, values in the conversation language):
PLAN — brewdoc:md-to-pdf
INPUT: <arguments verbatim, or "(empty)">
MODE: <resolved> — <explicit | matched keyword: X | default | prose-resolved file: path>
SCOPE: <md_file | engine | style question, as applicable>
DO: <2-5 imperative bullets>
RESULT: <PDF path, or the help/styles/test output>
Step 0: Parse Arguments
| Component | Required | Description |
|---|---|---|
md_file |
per mode | Path to .md file — literal token or extracted from prose (Prompt contract rule 2) |
--engine |
No | reportlab or weasyprint, anywhere; overrides saved config; strip before parsing |
custom_prompt |
No | Last argument in double quotes = LLM preprocessing instructions |
| Mode | Condition | EN keywords | RU keywords | Mutates? |
|---|---|---|---|---|
| HELP | Empty or help |
(empty), help | помощь | no |
| STYLES | styles or config |
styles, config, configure, page size, color scheme | стили, настрой стиль, конфиг | yes |
| TEST | test |
test, sample, demo, bundled test file | тест, пример, демо | no (private temp dir, removed on exit) |
| CONVERT+PROMPT | Path to .md file + quoted string at end |
convert with instructions, pdf and rewrite/strip section | сконвертируй с изменениями | yes |
| CONVERT | Path to .md file (no quoted string) |
convert, pdf, generate pdf, turn into pdf, .md file named in prose |
конвертируй, сделай pdf, преврати в pdf | yes |
HELP and STYLES skip to Step 3.
Step 1: Engine Selection
Config = project .codex/md-to-pdf.config.json, else global ~/.codex/md-to-pdf.config.json. Engine = --engine,
else config engine, else TEST takes reportlab and writes nothing, and CONVERT modes ask via main-chat user gate
with this table (default reportlab) and save with the block below, NEW_JSON = {"engine": "<choice>"}.
| Feature | reportlab | weasyprint |
|---|---|---|
| Install | pip only | pip + brew |
| Quality | Good | Excellent |
| Speed | Fast | Moderate |
| Images | Basic | Full |
| CSS Styling | No | Yes |
| Code highlight | No | Yes (Pygments) |
Config shape:
{
"engine": "reportlab",
"pygments_theme": "github",
"version": "{PLUGIN_VERSION}",
"content_version": "{CONTENT_VERSION}",
"generated_by": "brewdoc:md-to-pdf",
"last_updated": "{LAST_UPDATED}"
}
The block merges NEW_JSON's top-level keys into the project file, so engine and style saves keep each other,
and resolves the four provenance keys on every write, never hardcoded.
EXECUTE using shell (replace NEW_JSON; keep every other byte):
ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
NEW="$(mktemp)"
cat > "$NEW" <<'BREWDOC_CFG_EOF'
NEW_JSON
BREWDOC_CFG_EOF
mkdir -p "$ROOT/.claude"
CFG="$ROOT/.codex/md-to-pdf.config.json" NEW="$NEW" PJ="<skill-directory>/../../.codex-plugin/plugin.json" SKILL_MD="<skill-directory>/SKILL.md" TODAY="$(date +%F)" python3 - <<'PY'
import json, os, re
cfg_p = os.environ["CFG"]
pv = json.load(open(os.environ["PJ"])).get("version")
if not pv:
raise SystemExit("no version in " + os.environ["PJ"])
cv = None
for line in open(os.environ["SKILL_MD"]):
if "brewcode-meta:" in line:
m = re.search(r"content_version=(\d+\.\d+\.\d+)", line)
if m:
cv = m.group(1)
break
if not cv:
raise SystemExit("no content_version in " + os.environ["SKILL_MD"])
data = json.load(open(cfg_p)) if os.path.exists(cfg_p) else {}
data.update(json.load(open(os.environ["NEW"])))
data["version"] = pv
data["content_version"] = cv
data["generated_by"] = "brewdoc:md-to-pdf"
data["last_updated"] = os.environ["TODAY"]
with open(cfg_p, "w") as fh:
json.dump(data, fh, indent=2)
fh.write("\n")
print("OK version=%s content_version=%s engine=%s" % (pv, cv, data.get("engine", "(unset)")))
PY
rc=$?; rm -f "$NEW"
[ "$rc" -eq 0 ] && echo "✅ config saved" || { echo "❌ config save FAILED"; exit 1; }
STOP if ❌ -- fix before continuing.
Step 2: Dependency Check
EXECUTE using shell (replace ENGINE_NAME with the Step 1 engine):
bash "<skill-directory>/scripts/check_deps.sh" check ENGINE_NAME 2>&1; echo "EXIT_CODE=$?"
Output contains MISSING_PIP or MISSING_SYSTEM -> main-chat user gate with the comparison table and these options:
- "Install ENGINE_NAME dependencies"
- "Switch to OTHER_ENGINE" (if the other engine is available)
- "Cancel"
If user chooses install, EXECUTE using shell:
bash "<skill-directory>/scripts/check_deps.sh" install ENGINE_NAME 2>&1 && echo "---INSTALL_OK---" || echo "---INSTALL_FAILED---"
STOP if INSTALL_FAILED -- report error and exit. Cancel -> STOP.
Step 3: Mode Execution
HELP Mode
Print:
MD to PDF Converter
Usage:
$brewdoc:md-to-pdf <file.md> Convert with saved engine/style
$brewdoc:md-to-pdf <file.md> --engine weasyprint Convert with specific engine
$brewdoc:md-to-pdf <file.md> "remove section X" Preprocess MD then convert
$brewdoc:md-to-pdf styles Configure page/color/font
$brewdoc:md-to-pdf test Convert bundled test file
$brewdoc:md-to-pdf help Show this help
Engines:
reportlab -- Pure Python, fast, no system deps (check_deps.sh install reportlab)
weasyprint -- HTML/CSS pipeline, best quality (pip + brew deps)
EXIT after printing.
CONVERT Mode
- Read
md_file; not found -> STOP with an error. Output: same directory and name,.pdfextension.
EXECUTE using shell (substitute INPUT_PATH, OUTPUT_PATH, ENGINE; add --config CONFIG_PATH when a config from Step 1 exists; for weasyprint add --pygments-theme THEME when it sets pygments_theme):
python3 "<skill-directory>/scripts/md_to_pdf.py" "INPUT_PATH" "OUTPUT_PATH" --engine ENGINE 2>&1 && echo "---CONVERT_OK---" || echo "---CONVERT_FAILED---"
STOP if CONVERT_FAILED -- read error output, attempt fix, retry once. If still failing -- report error.
- Parse the result lines
STATUS,OUTPUT,PAGES,SIZE,ENGINE.
CONVERT+PROMPT Mode
- Read
md_file; apply thecustom_prompttransformations (delete sections, rewrite headings, restructure). - Write, convert and clean up in ONE Bash call.
EXECUTE using shell (replace ORIGINAL_DIR, TRANSFORMED_MARKDOWN, OUTPUT_PATH, ENGINE; keep every other byte):
set -euo pipefail
SRC_DIR="ORIGINAL_DIR"
TMP="$(mktemp "$SRC_DIR/.tmp_XXXXXX")"
trap 'case "${TMP##*/}" in .tmp_??????) rm -f "$TMP" ;; esac' EXIT
cat > "$TMP" <<'BREWDOC_MD_EOF'
TRANSFORMED_MARKDOWN
BREWDOC_MD_EOF
python3 "<skill-directory>/scripts/md_to_pdf.py" "$TMP" "OUTPUT_PATH" --engine ENGINE 2>&1 && echo "---CONVERT_OK---" || echo "---CONVERT_FAILED---"
OUTPUT_PATH is the ORIGINAL name with a .pdf extension; --config / --pygments-theme as in CONVERT. Never
write the transformed markdown with Write/Edit or to a fixed .tmp_<name>.md path: a predictable name
overwrites user files and collides with a concurrent conversion of the same source.
STOP if CONVERT_FAILED -- as in CONVERT.
- Parse the result lines, then Step 4 with
preprocessing: true.
STYLES Mode
Ask the four questions in one main-chat user gate call:
| # | Question | Options |
|---|---|---|
| 1 | Page size | A4 (default), Letter, Legal |
| 2 | Color scheme | Default blue (primary #1a3a5c), Dark (primary #2d3748), Custom (ask for hex values) |
| 3 | Code theme (weasyprint only) | github (default), monokai, friendly, solarized-dark, solarized-light |
| 4 | Footer format | Page {page} of {total} (default), {page}/{total}, Disabled |
Save with the Step 1 block: NEW_JSON = styles/default.json structure with the changed values, plus answer 3 as
top-level pygments_theme (passed as --pygments-theme; md_to_pdf.py never reads code.theme and reads only
page, colors, typography, code, footer). Report the saved settings table and EXIT.
TEST Mode
The CONVERT command on the bundled file, writing into a private 0700 mktemp -d dir removed on exit.
EXECUTE using shell (replace ENGINE; --config / --pygments-theme as in CONVERT; keep every other byte):
umask 077
TEST_DIR="$(mktemp -d "${TMPDIR:-/tmp}/md-to-pdf-test.XXXXXX")" && chmod 700 "$TEST_DIR" || { echo "---CONVERT_FAILED---"; exit 1; }
trap 'case "${TEST_DIR##*/}" in md-to-pdf-test.??????) rm -rf -- "$TEST_DIR" ;; esac' EXIT
python3 "<skill-directory>/scripts/md_to_pdf.py" "<skill-directory>/test/test-all-elements.md" "$TEST_DIR/md-to-pdf-test-ENGINE.pdf" --engine ENGINE 2>&1 && echo "---CONVERT_OK---" || echo "---CONVERT_FAILED---"
STOP if CONVERT_FAILED -- as in CONVERT.
Then Step 4; Output = the OUTPUT= path, marked (removed on exit).
Step 4: Report Results
| Parameter | Value |
|---|---|
| Source | absolute path to input MD |
| Output | absolute path to output PDF |
| Pages | PAGES= line of the script output |
| Size | SIZE= line |
| Engine | reportlab or weasyprint |
| Preprocessing | custom_prompt summary (if used) or none |
Use this skill
Connect your agent to the Skill Store MCP server and ask for it by ref: kochetkov-ma/md-to-pdf-22424523313.
Details
- Publisher
- kochetkov-ma
- Category
- Software Development › Developer docs & tooling
- License
- MIT
- Updated
- 2026-10-17
References
More from kochetkov-ma
- Agents Creates, improves, syncs Codex subagents.
- Convention Setup Installs project conventions and reversible loading guidance; extracts representative implementat…
- Rules Maintains project .codex/rules and the AGENTS.md rule index.
- Superreview Setup Generates a project-tailored deep-review skill: domain-expert routing + scope discipline (blast r…