md-to-pdf

v2026.09.24

Convert Markdown to PDF via reportlab or weasyprint engines. Triggers - pdf, md to pdf, markdown to pdf, generate pdf.

GitHub
安装命令
npx skhub add kochetkov-ma/md-to-pdf
Markdown
SKILL.md
<!-- brewcode-meta: version=6.2.0 content_version=6.0.0 generated_by=brewdoc:md-to-pdf -->

MD to PDF

Converts Markdown files to professional PDF using one of two rendering engines.

Prompt contract

Position 1 of $ARGUMENTS is a free-form prompt (RU/EN) — the file path, --engine flag, styles/test tokens and the quoted LLM-preprocessing prompt are all optional and may follow in any order. Nobody types keys: resolve mode + file FROM the prompt.

  1. Strip --engine <name>. A literal mode token (styles, config, test, help) anywhere wins outright.
  2. Else score modes by distinct whole-word keyword hits (table below). Highest unique score wins.
  3. Empty arguments -> help (documented default). Read-only — asks nothing.
  4. Prose resolution (mandatory): $ARGUMENTS may be a full sentence, not just tokens. Extract md_file from 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). If the sentence implies CONVERT but no file is resolvable -> ONE AskUserQuestion for the file path. Never guess a file and never silently fall through to HELP.
  5. Outcome-changing ambiguity (missing engine deps, no saved engine preference) -> ONE AskUserQuestion.

Then print this block ONCE, before Step 1:

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>

Labels are literal; values follow the conversation language.

Step 0: Parse Arguments

Parse $ARGUMENTS to determine mode and components.

ComponentRequiredDescription
md_fileper modePath to .md file — literal token or extracted from prose (rule 4 above)
--engineNoreportlab or weasyprint (overrides saved config)
custom_promptNoLast argument in double quotes = LLM preprocessing instructions

Mode detection rules:

ModeConditionEN keywordsRU keywordsMutates?
HELPEmpty or help(empty), helpпомощьno
STYLESstyles or configstyles, config, configure, page size, color schemeстили, настрой стиль, конфигyes
TESTtesttest, sample, demo, bundled test fileтест, пример, демоno (writes only to /tmp/)
CONVERT+PROMPTPath to .md file + quoted string at endconvert with instructions, pdf and rewrite/strip sectionсконвертируй с изменениямиyes
CONVERTPath to .md file (no quoted string)convert, pdf, generate pdf, turn into pdf, .md file named in proseконвертируй, сделай pdf, преврати в pdfyes

Extract --engine <name> from anywhere in arguments if present. Remove it before further parsing.

Step 1: Dependency Check

Determine the target engine (from --engine flag, saved config, or default reportlab).

EXECUTE using Bash tool:

bash "${CLAUDE_SKILL_DIR}/scripts/check_deps.sh" check ENGINE_NAME 2>&1; echo "EXIT_CODE=$?"

Replace ENGINE_NAME with the target engine.

If output contains MISSING_PIP or MISSING_SYSTEM:

Use AskUserQuestion presenting the engine comparison table:

Featurereportlabweasyprint
Installpip onlypip + brew
QualityGoodExcellent
SpeedFastModerate
ImagesBasicFull
CSS StylingNoYes
Code highlightNoYes (Pygments)

Options:

  • "Install ENGINE_NAME dependencies"
  • "Switch to OTHER_ENGINE" (if the other engine is available)
  • "Cancel"

If user chooses install, EXECUTE using Bash tool:

bash "${CLAUDE_SKILL_DIR}/scripts/check_deps.sh" install ENGINE_NAME 2>&1 && echo "---INSTALL_OK---" || echo "---INSTALL_FAILED---"

STOP if INSTALL_FAILED -- report error and exit.

If user cancels -- STOP.

Step 2: Engine Selection (first run only)

Check for saved config in order:

  1. Project: .claude/md-to-pdf.config.json
  2. Global: ~/.claude/md-to-pdf.config.json

If --engine flag was provided -- use it (skip config lookup).

If no saved preference and no --engine flag -- use AskUserQuestion with the engine comparison table from Step 1. Save the choice to project config .claude/md-to-pdf.config.json:

{
  "engine": "reportlab",
  "pygments_theme": "github",
  "version": "{PLUGIN_VERSION}",
  "content_version": "{CONTENT_VERSION}",
  "generated_by": "brewdoc:md-to-pdf",
  "last_updated": "{LAST_UPDATED}"
}

The four provenance keys are mandatory on every write of this file and are RESOLVED, never hardcoded.

EXECUTE using Bash tool (replace ENGINE_VALUE and THEME_VALUE with the chosen values):

ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")}"
PJ="${CLAUDE_SKILL_DIR}/../../.claude-plugin/plugin.json"
PV=$(python3 -c "import json,sys;print(json.load(open(sys.argv[1])).get('version',''))" "$PJ" 2>/dev/null || true)
[ -n "$PV" ] || { echo "❌ cannot read version from $PJ -- reinstall brewdoc"; exit 1; }
SKILL_MD="${CLAUDE_SKILL_DIR}/SKILL.md"
CV=$(python3 -c "
import re,sys
for line in open(sys.argv[1]):
    if 'brewcode-meta:' in line:
        m = re.search(r'content_version=(\d+\.\d+\.\d+)', line)
        if m:
            print(m.group(1))
        break
" "$SKILL_MD" 2>/dev/null || true)
[ -n "$CV" ] || { echo "❌ cannot read content_version from $SKILL_MD -- reinstall brewdoc"; exit 1; }
mkdir -p "$ROOT/.claude"
printf '{\n  "engine": "ENGINE_VALUE",\n  "pygments_theme": "THEME_VALUE",\n  "version": "%s",\n  "content_version": "%s",\n  "generated_by": "brewdoc:md-to-pdf",\n  "last_updated": "%s"\n}\n' "$PV" "$CV" "$(date +%F)" > "$ROOT/.claude/md-to-pdf.config.json" \
  && python3 -c "import json,sys;json.load(open(sys.argv[1]))" "$ROOT/.claude/md-to-pdf.config.json" \
  && echo "✅ config written (version $PV, content_version $CV)" || { echo "❌ config invalid JSON"; exit 1; }

STOP if ❌ -- fix before continuing.

Step 3: Mode Execution

HELP Mode

Print formatted usage:

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

  1. Read the input MD file with Read tool. If not found -- STOP with error.
  2. Determine output path: same directory, same name, .pdf extension.
  3. Build the config path argument (if project or global config exists, add --config CONFIG_PATH).

EXECUTE using Bash tool:

python3 "${CLAUDE_SKILL_DIR}/scripts/md_to_pdf.py" "INPUT_PATH" "OUTPUT_PATH" --engine ENGINE 2>&1 && echo "---CONVERT_OK---" || echo "---CONVERT_FAILED---"

Replace INPUT_PATH, OUTPUT_PATH, ENGINE with actual values. Add --config CONFIG_PATH if a style config JSON exists. Add --pygments-theme THEME for weasyprint if configured.

STOP if CONVERT_FAILED -- read error output, attempt fix, retry once. If still failing -- report error.

  1. Parse structured output lines: STATUS, OUTPUT, PAGES, SIZE, ENGINE.

CONVERT+PROMPT Mode

  1. Read the input MD file with Read tool.
  2. Apply LLM transformations per the custom_prompt instructions (delete sections, rewrite headings, restructure, etc.).
  3. Write the transformed markdown, convert it and clean up in ONE Bash invocation. The temp name comes from mktemp -- never composed from the source name -- and cleanup is trapped and constrained to a .tmp_* basename, so a mis-substitution cannot name the source file.

EXECUTE using Bash tool (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 "${CLAUDE_SKILL_DIR}/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. Add --config / --pygments-theme exactly as in CONVERT mode.

Never write the transformed markdown with Write/Edit and never reuse 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 -- read error output, attempt fix, retry once. If still failing -- report error.

  1. Parse the structured result lines, then proceed to Step 4 with preprocessing: true.

STYLES Mode

Run interactive configuration via AskUserQuestion dialogs:

Question 1 -- Page size: Options: A4 (default), Letter, Legal

Question 2 -- Color scheme: Options: Default blue (primary #1a3a5c), Dark (primary #2d3748), Custom (ask for hex values)

Question 3 -- Code theme (weasyprint only): Options: github (default), monokai, friendly, solarized-dark, solarized-light

Question 4 -- Footer format: Options: Page {page} of {total} (default), {page}/{total}, Disabled

Build JSON config matching styles/default.json structure, overriding changed values, and Write it to a temp file (e.g. /tmp/md-to-pdf-styles.json). The merge below then lands it in .claude/md-to-pdf.config.json with the four mandatory provenance keys, carrying over any engine / pygments_theme the file already held -- this writer replaces the whole file, and dropping those would silently reset the saved engine choice.

EXECUTE using Bash tool (replace STYLE_JSON_PATH with the temp file you wrote):

ROOT="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || echo "$PWD")}"
CFG="$ROOT/.claude/md-to-pdf.config.json"
NEW="STYLE_JSON_PATH"
PJ="${CLAUDE_SKILL_DIR}/../../.claude-plugin/plugin.json"
SKILL_MD="${CLAUDE_SKILL_DIR}/SKILL.md"
mkdir -p "$ROOT/.claude"
CFG="$CFG" NEW="$NEW" PJ="$PJ" SKILL_MD="$SKILL_MD" TODAY="$(date +%F)" python3 - <<'PY'
import json, os, re
cfg_p, new_p = os.environ["CFG"], os.environ["NEW"]
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"])
old = json.load(open(cfg_p)) if os.path.exists(cfg_p) else {}
data = json.load(open(new_p))
for k in ("engine", "pygments_theme"):
    if k in old and k not in data:
        data[k] = old[k]
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
[ $? -eq 0 ] && echo "✅ styles saved" || { echo "❌ styles save FAILED"; exit 1; }
rm -f "$NEW"

STOP if ❌ -- fix before continuing.

The four keys sit at the top level beside the style sections; md_to_pdf.py's load_config deep-merges the file over styles/default.json and then reads only the sections it owns (page, colors, typography, code, footer), so extra top-level keys are inert.

Report saved settings table and EXIT.

TEST Mode

  1. Use bundled test file at ${CLAUDE_SKILL_DIR}/test/test-all-elements.md as INPUT_PATH.
  2. Determine output path: /tmp/md-to-pdf-test-ENGINE.pdf
  3. Run converter (same command as CONVERT mode, using test file as input, /tmp/ output).
  4. Proceed to Step 4.

Step 4: Report Results

ParameterValue
Sourceabsolute path to input MD
Outputabsolute path to output PDF
Pagesfrom PAGES= in script output
Sizefrom SIZE= in script output
Enginereportlab or weasyprint
Preprocessingcustom_prompt summary (if used) or none
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

2026年9月24日

分类

未分类

许可证

NOASSERTION

源路径

brewdoc/skills/md-to-pdf

默认分支

main

最新提交

7f5b5d8

Tree SHA

5bfd4fc