Converts Markdown to professional PDF. Two engines — reportlab (lightweight) or weasyprint (full CSS). Supports style customization, a test/preview mode, and dependency management. Triggers: pdf, md to pdf, markdown to pdf, convert pdf, generate pdf.
Converts Markdown files to professional PDF using one of two rendering engines.
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.
--engine <name>. A literal mode token (styles, config, test, help) anywhere wins outright.help (documented default). Read-only — asks nothing.$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.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.
Parse $ARGUMENTS to determine mode and components.
| Component | Required | Description |
|-----------|:--------:|-------------|
| md_file | per mode | Path to .md file — literal token or extracted from prose (rule 4 above) |
| --engine | No | reportlab or weasyprint (overrides saved config) |
| custom_prompt | No | Last argument in double quotes = LLM preprocessing instructions |
Mode detection rules:
| 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 (writes only to /tmp/) |
| 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 |
Extract --engine <name> from anywhere in arguments if present. Remove it before further parsing.
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:
| 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) |
Options:
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.
Check for saved config in order:
.claude/md-to-pdf.config.json~/.claude/md-to-pdf.config.jsonIf --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.
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.
.pdf extension.--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.
STATUS, OUTPUT, PAGES, SIZE, ENGINE.custom_prompt instructions (delete sections, rewrite headings, restructure, etc.).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/Editand never reuse a fixed.tmp_<name>.mdpath -- 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.
preprocessing: true.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.
${CLAUDE_SKILL_DIR}/test/test-all-elements.md as INPUT_PATH./tmp/md-to-pdf-test-ENGINE.pdf/tmp/ output).| Parameter | Value |
|-----------|-------|
| Source | absolute path to input MD |
| Output | absolute path to output PDF |
| Pages | from PAGES= in script output |
| Size | from SIZE= in script output |
| Engine | reportlab or weasyprint |
| Preprocessing | custom_prompt summary (if used) or none |
npx skills add kochetkov-ma/brewdoc:md-to-pdf下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer