#!/usr/bin/env bash # make.sh — minimax-pdf unified CLI # Usage: bash make.sh [options] # # Commands: # check Verify all dependencies # fix Auto-install missing dependencies # run --title T --type TYPE Full pipeline → output.pdf # --out FILE Output path (default: output.pdf) # --author A --date D # --subtitle S # --abstract A Optional abstract text for cover # --cover-image URL Optional cover image URL/path # --content FILE Path to content.json (optional) # demo Build a full-featured demo to demo.pdf # # Document types: # report proposal resume portfolio academic general # minimal stripe diagonal frame editorial # magazine darkroom terminal poster # # Content block types: # h1 h2 h3 body bullet numbered callout table # image figure code math chart flowchart bibliography # divider caption pagebreak spacer # # Exit codes: 0 success, 1 usage error, 2 dep missing, 3 runtime error set -euo pipefail SCRIPTS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" NODE="node" # ── Auto-detect Python (cross-platform) ──────────────────────────────────────── detect_python() { local candidate # 1. PATH 中的 python3 / python(排除 Windows Store 空壳) for candidate in python3 python; do if command -v "$candidate" &>/dev/null \ && "$candidate" -c "import sys; assert sys.version_info >= (3,9)" 2>/dev/null; then echo "$candidate"; return fi done # 2. Windows 常见安装路径(官方安装器 / Chocolatey / Scoop / Anaconda / Miniconda) local win_candidates=() # 官方安装器 — 当前用户 for d in "/c/Users/$USER/AppData/Local/Programs/Python"/Python3*/; do [[ -x "${d}python.exe" ]] && win_candidates+=("${d}python.exe") done # 官方安装器 — 全局 for d in "/c/Python3"*/ "/c/Program Files/Python3"*/; do [[ -x "${d}python.exe" ]] && win_candidates+=("${d}python.exe") done # Chocolatey [[ -x "/c/ProgramData/chocolatey/bin/python.exe" ]] && \ win_candidates+=("/c/ProgramData/chocolatey/bin/python.exe") # Scoop [[ -x "/c/Users/$USER/scoop/apps/python/current/python.exe" ]] && \ win_candidates+=("/c/Users/$USER/scoop/apps/python/current/python.exe") # Anaconda / Miniconda for d in "/c/Users/$USER/anaconda3" "/c/Users/$USER/miniconda3" \ "/c/ProgramData/anaconda3" "/c/ProgramData/miniconda3"; do [[ -x "$d/python.exe" ]] && win_candidates+=("$d/python.exe") done for candidate in "${win_candidates[@]}"; do if "$candidate" -c "import sys; assert sys.version_info >= (3,9)" 2>/dev/null; then echo "$candidate"; return fi done # 3. macOS Homebrew for candidate in /opt/homebrew/bin/python3 /usr/local/bin/python3; do if [[ -x "$candidate" ]] \ && "$candidate" -c "import sys; assert sys.version_info >= (3,9)" 2>/dev/null; then echo "$candidate"; return fi done # 未找到 — 返回空字符串,由调用方处理 echo "" } PY="$(detect_python)" # ── Interactive prompt helper ────────────────────────────────────────────────── # 询问用户选择:自动安装 or 手动安装 # Usage: ask_install "Python" → sets REPLY to "auto" or "manual" or "skip" ask_install() { local dep_name="$1" echo "" bold " $dep_name is not installed. What would you like to do?" echo " [1] Auto-install (let this script install it for you)" echo " [2] Manual install (show me the instructions, I'll do it myself)" echo " [3] Skip (continue without it)" echo "" read -rp " Choose [1/2/3]: " choice case "$choice" in 1) REPLY="auto" ;; 2) REPLY="manual" ;; *) REPLY="skip" ;; esac } # ── Auto-install helpers ─────────────────────────────────────────────────────── install_python_auto() { bold " Attempting to install Python..." if command -v winget &>/dev/null; then echo " Using: winget install Python.Python.3.12" winget install Python.Python.3.12 --accept-package-agreements --accept-source-agreements 2>&1 | tail -5 elif command -v choco &>/dev/null; then echo " Using: choco install python3" choco install python3 -y 2>&1 | tail -5 elif command -v scoop &>/dev/null; then echo " Using: scoop install python" scoop install python 2>&1 | tail -5 elif command -v brew &>/dev/null; then echo " Using: brew install python3" brew install python3 2>&1 | tail -5 elif command -v apt-get &>/dev/null; then echo " Using: sudo apt-get install python3 python3-pip" sudo apt-get install -y python3 python3-pip 2>&1 | tail -5 elif command -v dnf &>/dev/null; then echo " Using: sudo dnf install python3 python3-pip" sudo dnf install -y python3 python3-pip 2>&1 | tail -5 else red " No supported package manager found. Please install manually." show_python_manual_instructions return 1 fi echo "" green " Python installed. Please restart your terminal and re-run this script." echo " (New PATH entries require a terminal restart to take effect)" return 0 } show_python_manual_instructions() { echo "" bold " Install Python 3.9+ manually:" echo " Windows: winget install Python.Python.3.12" echo " or https://www.python.org/downloads/" echo " (IMPORTANT: check 'Add Python to PATH' during install)" echo " macOS: brew install python3" echo " Linux: sudo apt install python3 python3-pip (Debian/Ubuntu)" echo " sudo dnf install python3 python3-pip (Fedora)" echo "" } install_node_auto() { bold " Attempting to install Node.js..." if command -v winget &>/dev/null; then echo " Using: winget install OpenJS.NodeJS.LTS" winget install OpenJS.NodeJS.LTS --accept-package-agreements --accept-source-agreements 2>&1 | tail -5 elif command -v choco &>/dev/null; then echo " Using: choco install nodejs-lts" choco install nodejs-lts -y 2>&1 | tail -5 elif command -v scoop &>/dev/null; then echo " Using: scoop install nodejs-lts" scoop install nodejs-lts 2>&1 | tail -5 elif command -v brew &>/dev/null; then echo " Using: brew install node" brew install node 2>&1 | tail -5 elif command -v apt-get &>/dev/null; then echo " Using: sudo apt-get install nodejs npm" sudo apt-get install -y nodejs npm 2>&1 | tail -5 else red " No supported package manager found. Please install manually." show_node_manual_instructions return 1 fi echo "" green " Node.js installed. Please restart your terminal and re-run this script." return 0 } show_node_manual_instructions() { echo "" bold " Install Node.js 18+ manually:" echo " Windows: winget install OpenJS.NodeJS.LTS" echo " or https://nodejs.org/en/download/" echo " macOS: brew install node" echo " Linux: sudo apt install nodejs npm (Debian/Ubuntu)" echo " or https://nodejs.org/en/download/" echo "" } # ── Colour helpers ───────────────────────────────────────────────────────────── red() { printf '\033[0;31m%s\033[0m\n' "$*"; } green() { printf '\033[0;32m%s\033[0m\n' "$*"; } yellow() { printf '\033[0;33m%s\033[0m\n' "$*"; } bold() { printf '\033[1m%s\033[0m\n' "$*"; } # ── check ────────────────────────────────────────────────────────────────────── cmd_check() { local ok=true bold "Checking dependencies..." # Python if [[ -n "$PY" ]] && $PY -c "import sys" 2>/dev/null; then green " ✓ python ($PY) $($PY --version 2>&1 | awk '{print $2}')" else red " ✗ Python 3.9+ not found" ask_install "Python" case "$REPLY" in auto) install_python_auto; exit $? ;; manual) show_python_manual_instructions; exit 2 ;; *) yellow " Skipped Python — PDF generation will not work without it." ;; esac ok=false fi # reportlab if [[ -n "$PY" ]] && $PY -c "import reportlab" 2>/dev/null; then green " ✓ reportlab" else yellow " ⚠ reportlab not installed (run: make.sh fix)" ok=false fi # pypdf if [[ -n "$PY" ]] && $PY -c "import pypdf" 2>/dev/null; then green " ✓ pypdf" else yellow " ⚠ pypdf not installed (run: make.sh fix)" ok=false fi # Node.js if command -v node &>/dev/null; then green " ✓ node $(node --version)" else red " ✗ Node.js not found" ask_install "Node.js" case "$REPLY" in auto) install_node_auto; exit $? ;; manual) show_node_manual_instructions; exit 2 ;; *) yellow " Skipped Node.js — cover rendering will not work without it." ;; esac ok=false fi # Playwright + Browser (prefer local Chrome/Edge) local browser_found=false if node -e " const pw = (() => { try { return require('playwright'); } catch(_) { const {execSync} = require('child_process'); try { return require(execSync('npm root -g').toString().trim()+'/playwright'); } catch(_) { return null; } }})(); if (!pw) process.exit(1); (async () => { for (const ch of ['chrome','msedge','chromium']) { try { const b = await pw.chromium.launch({channel:ch}); await b.close(); console.log(ch); process.exit(0); } catch(_) {} } try { const b = await pw.chromium.launch(); await b.close(); console.log('bundled-chromium'); process.exit(0); } catch(_) {} process.exit(1); })(); " 2>/dev/null; then green " ✓ playwright + browser detected" browser_found=true fi if ! $browser_found; then yellow " ⚠ playwright or browser not found (run: make.sh fix)" ok=false fi # matplotlib (optional) if [[ -n "$PY" ]] && $PY -c "import matplotlib" 2>/dev/null; then green " ✓ matplotlib (math, chart, flowchart blocks enabled)" else yellow " ⚠ matplotlib not installed — math/chart/flowchart blocks degrade to text (run: make.sh fix)" fi if $ok; then green "\nAll dependencies satisfied." exit 0 else yellow "\nSome dependencies missing. Run: bash make.sh fix" exit 2 fi } # ── fix ──────────────────────────────────────────────────────────────────────── cmd_fix() { bold "Installing missing dependencies..." local rc=0 # Python if [[ -n "$PY" ]] && $PY -c "import sys" 2>/dev/null; then green " ✓ Python found ($PY)" else red " ✗ Python 3.9+ not found" ask_install "Python" case "$REPLY" in auto) install_python_auto || rc=2 if [[ $rc -ne 0 ]]; then yellow " Auto-install may have failed. Please restart terminal and re-run." exit $rc fi # Re-detect after install PY="$(detect_python)" if [[ -z "$PY" ]]; then yellow " Python still not detected. Please restart terminal and re-run: bash make.sh fix" exit 2 fi ;; manual) show_python_manual_instructions echo " After installing Python, re-run: bash make.sh fix" exit 2 ;; *) yellow " Skipped Python — cannot install Python packages." rc=2 ;; esac fi # Python packages if [[ -n "$PY" ]] && $PY -c "import sys" 2>/dev/null; then $PY -m pip install --break-system-packages -q reportlab pypdf matplotlib 2>/dev/null \ || $PY -m pip install -q reportlab pypdf matplotlib 2>/dev/null \ || { yellow " pip install failed — try: $PY -m pip install reportlab pypdf matplotlib"; rc=3; } green " ✓ Python packages installed (reportlab, pypdf, matplotlib)" fi # Node.js if ! command -v node &>/dev/null; then red " ✗ Node.js not found" ask_install "Node.js" case "$REPLY" in auto) install_node_auto || rc=2 if [[ $rc -ne 0 ]]; then yellow " Auto-install may have failed. Please restart terminal and re-run." exit $rc fi ;; manual) show_node_manual_instructions echo " After installing Node.js, re-run: bash make.sh fix" exit 2 ;; *) yellow " Skipped Node.js — cover rendering will not work." rc=2 ;; esac fi # Playwright + Browser: prefer local Chrome/Edge, only download Chromium as last resort if command -v node &>/dev/null && command -v npm &>/dev/null; then # Ensure playwright is installed if ! node -e "require('playwright')" 2>/dev/null && \ ! node -e "require(require('child_process').execSync('npm root -g').toString().trim()+'/playwright')" 2>/dev/null; then npm install -g playwright --silent 2>/dev/null || { yellow " playwright npm install failed"; rc=3; } fi # Check if local Chrome or Edge is available — skip Chromium download if so local has_local_browser=false for ch in chrome msedge; do if node -e " const pw = (() => { try { return require('playwright'); } catch(_) { try { return require(require('child_process').execSync('npm root -g').toString().trim()+'/playwright'); } catch(_) { return null; } }})(); if (!pw) process.exit(1); pw.chromium.launch({channel:'$ch'}).then(b => b.close().then(() => process.exit(0))).catch(() => process.exit(1)); " 2>/dev/null; then green " ✓ Local browser detected ($ch) — skipping Chromium download" has_local_browser=true break fi done if ! $has_local_browser; then yellow " No local Chrome/Edge found — downloading Chromium..." npx playwright install chromium --silent 2>/dev/null && \ green " ✓ Playwright Chromium installed" || \ { yellow " Chromium install failed — install Chrome or Edge manually"; rc=3; } fi else yellow " npm not found — cannot install Playwright automatically" rc=2 fi if [[ $rc -eq 0 ]]; then green "\nAll dependencies installed. Run: bash make.sh check" fi exit $rc } # ── run ──────────────────────────────────────────────────────────────────────── cmd_run() { # Pre-flight: ensure critical deps exist before doing any work if [[ -z "$PY" ]] || ! $PY -c "import sys" 2>/dev/null; then red "Error: Python 3.9+ is required but not found." echo "Run: bash make.sh check (to diagnose and install)" exit 2 fi if ! command -v node &>/dev/null; then red "Error: Node.js is required but not found." echo "Run: bash make.sh check (to diagnose and install)" exit 2 fi local title="Untitled Document" local type="general" local author="" local date="" local subtitle="" local abstract="" local cover_image="" local accent="" local cover_bg="" local content_file="" local out="output.pdf" local workdir workdir="$(mktemp -d)" # Parse options while [[ $# -gt 0 ]]; do case "$1" in --title) title="$2"; shift 2 ;; --type) type="$2"; shift 2 ;; --author) author="$2"; shift 2 ;; --date) date="$2"; shift 2 ;; --subtitle) subtitle="$2"; shift 2 ;; --abstract) abstract="$2"; shift 2 ;; --cover-image) cover_image="$2"; shift 2 ;; --accent) accent="$2"; shift 2 ;; --cover-bg) cover_bg="$2"; shift 2 ;; --content) content_file="$2"; shift 2 ;; --out) out="$2"; shift 2 ;; *) echo "Unknown option: $1"; exit 1 ;; esac done bold "Building: $title" echo " Type : $type" echo " Output : $out" # Step 1: tokens echo "" bold "Step 1/4 Generating design tokens..." local accent_args=() [[ -n "$accent" ]] && accent_args+=(--accent "$accent") [[ -n "$cover_bg" ]] && accent_args+=(--cover-bg "$cover_bg") $PY "$SCRIPTS/palette.py" \ --title "$title" --type "$type" \ --author "$author" --date "$date" \ --out "$workdir/tokens.json" \ "${accent_args[@]+"${accent_args[@]}"}" # Inject optional cover fields into tokens.json if [[ -n "$abstract" || -n "$cover_image" ]]; then PDF_ABSTRACT="$abstract" PDF_COVER_IMAGE="$cover_image" PDF_TOKENS="$workdir/tokens.json" \ $PY - <<'PYEOF' import json, os with open(os.environ["PDF_TOKENS"]) as f: t = json.load(f) abstract = os.environ.get("PDF_ABSTRACT", "") cover_image = os.environ.get("PDF_COVER_IMAGE", "") if abstract: t["abstract"] = abstract if cover_image: t["cover_image"] = cover_image with open(os.environ["PDF_TOKENS"], "w") as f: json.dump(t, f, indent=2) PYEOF fi cat "$workdir/tokens.json" | $PY -c " import json,sys t=json.load(sys.stdin) print(f' Mood : {t[\"mood\"]}') print(f' Pattern : {t[\"cover_pattern\"]}') print(f' Fonts : {t[\"font_display\"]} / {t[\"font_body\"]}')" # Step 2: cover HTML + render echo "" bold "Step 2/4 Rendering cover..." local subtitle_args=() [[ -n "$subtitle" ]] && subtitle_args=(--subtitle "$subtitle") $PY "$SCRIPTS/cover.py" \ --tokens "$workdir/tokens.json" \ --out "$workdir/cover.html" \ "${subtitle_args[@]+"${subtitle_args[@]}"}" $NODE "$SCRIPTS/render_cover.js" \ --input "$workdir/cover.html" \ --out "$workdir/cover.pdf" green " ✓ Cover rendered" # Step 3: body echo "" bold "Step 3/4 Rendering body pages..." if [[ -z "$content_file" ]]; then # Generate a minimal placeholder body cat > "$workdir/content.json" <<'JSON' [ {"type":"h1", "text":"Document Body"}, {"type":"body", "text":"Replace this with your content.json file using --content path/to/content.json"}, {"type":"body", "text":"See the content.json schema in the skill README for the full list of supported block types: h1, h2, h3, body, bullet, callout, table, pagebreak, spacer."} ] JSON content_file="$workdir/content.json" yellow " No content file provided — using placeholder body." fi # Validate content.json before rendering if ! $PY -c " import json, sys try: with open(sys.argv[1], encoding='utf-8') as f: data = json.load(f) blocks = data if isinstance(data, list) else data.get('blocks', data.get('content', [])) if not isinstance(blocks, list) or len(blocks) == 0: print('Error: content.json must contain a non-empty array of blocks', file=sys.stderr) sys.exit(1) print(f' content.json validated: {len(blocks)} blocks') except json.JSONDecodeError as e: print(f'Error: content.json is not valid JSON — {e}', file=sys.stderr) print(f'Hint: For long documents, write Markdown and use: make.sh reformat --input doc.md ...', file=sys.stderr) sys.exit(1) " "$content_file"; then red " ✗ content.json validation failed" exit 3 fi $PY "$SCRIPTS/render_body.py" \ --tokens "$workdir/tokens.json" \ --content "$content_file" \ --out "$workdir/body.pdf" green " ✓ Body rendered" # Step 4: merge echo "" bold "Step 4/4 Merging and QA..." $PY "$SCRIPTS/merge.py" \ --cover "$workdir/cover.pdf" \ --body "$workdir/body.pdf" \ --out "$out" \ --title "$title" # Cleanup rm -rf "$workdir" } # ── fill ────────────────────────────────────────────────────────────────────── cmd_fill() { local input="" out="" values="" data_file="" inspect_only=false while [[ $# -gt 0 ]]; do case "$1" in --input) input="$2"; shift 2 ;; --out) out="$2"; shift 2 ;; --values) values="$2"; shift 2 ;; --data) data_file="$2"; shift 2 ;; --inspect) inspect_only=true; shift ;; *) echo "Unknown option: $1"; exit 1 ;; esac done if [[ -z "$input" ]]; then echo "Usage: make.sh fill --input form.pdf [--out filled.pdf] [--values '{...}'] [--data values.json] [--inspect]" exit 1 fi if $inspect_only || [[ -z "$out" && -z "$values" && -z "$data_file" ]]; then bold "Inspecting form fields in: $input" $PY "$SCRIPTS/fill_inspect.py" --input "$input" return fi bold "Filling form: $input → $out" local val_args="" if [[ -n "$values" ]]; then val_args="--values $values"; fi if [[ -n "$data_file" ]]; then val_args="--data $data_file"; fi $PY "$SCRIPTS/fill_write.py" --input "$input" --out "$out" $val_args } # ── reformat ─────────────────────────────────────────────────────────────────── cmd_reformat() { local input="" title="Reformatted Document" type="general" local author="" date="" out="output.pdf" subtitle="" local tmpdir tmpdir="$(mktemp -d)" while [[ $# -gt 0 ]]; do case "$1" in --input) input="$2"; shift 2 ;; --title) title="$2"; shift 2 ;; --type) type="$2"; shift 2 ;; --author) author="$2"; shift 2 ;; --date) date="$2"; shift 2 ;; --subtitle) subtitle="$2"; shift 2 ;; --out) out="$2"; shift 2 ;; *) echo "Unknown option: $1"; exit 1 ;; esac done if [[ -z "$input" ]]; then echo "Usage: make.sh reformat --input source.md --title T --type TYPE --out output.pdf" exit 1 fi bold "Parsing: $input" $PY "$SCRIPTS/reformat_parse.py" --input "$input" --out "$tmpdir/content.json" green " ✓ Parsed to content.json" bold "Applying design and building PDF..." local sub_args=() [[ -n "$subtitle" ]] && sub_args=(--subtitle "$subtitle") cmd_run \ --title "$title" --type "$type" \ --author "$author" --date "$date" \ --content "$tmpdir/content.json" \ --out "$out" \ "${sub_args[@]+"${sub_args[@]}"}" rm -rf "$tmpdir" } # ── demo ────────────────────────────────────────────────────────────────────── cmd_demo() { local tmpdir tmpdir="$(mktemp -d)" cat > "$tmpdir/content.json" <<'JSON' [ {"type":"h1", "text":"Executive Summary"}, {"type":"body", "text":"This document was generated by minimax-pdf — a skill for creating visually polished PDFs. Every design decision is rooted in the document type and content, not a generic template."}, {"type":"callout", "text":"Key insight: design tokens flow from palette.py through every renderer, keeping cover and body visually consistent."}, {"type":"h1", "text":"How It Works"}, {"type":"h2", "text":"The Token Pipeline"}, {"type":"body", "text":"The palette.py script infers a color palette and typography pair from the document type. These tokens are written to tokens.json and consumed by every downstream script."}, {"type":"numbered","text":"palette.py generates color tokens, font selection, and the cover pattern"}, {"type":"numbered","text":"cover.py renders the cover HTML using the selected pattern"}, {"type":"numbered","text":"render_cover.js uses Playwright to convert the HTML cover to PDF"}, {"type":"numbered","text":"render_body.py builds inner pages from content.json using ReportLab"}, {"type":"numbered","text":"merge.py combines cover + body and runs final QA checks"}, {"type":"h2", "text":"Cover Patterns"}, {"type":"table", "headers": ["Pattern", "Document type", "Visual character"], "rows": [ ["fullbleed", "report, general", "Deep background · dot-grid texture"], ["split", "proposal", "Left dark panel · right dot-grid"], ["typographic", "resume, academic", "Oversized display type · first-word accent"], ["atmospheric", "portfolio", "Dark bg · radial glow · dot-grid"], ["magazine", "magazine", "Cream bg · centered · hero image"], ["darkroom", "darkroom", "Navy bg · centered · grayscale image"], ["terminal", "terminal", "Near-black · grid lines · monospace"], ["poster", "poster", "White · thick sidebar · oversized title"] ] }, {"type":"h1", "text":"Data Visualisation"}, {"type":"h2", "text":"Performance Metrics (Chart)"}, {"type":"body", "text":"Charts are rendered natively using matplotlib with a color palette derived from the document accent. No external chart services or image files required."}, {"type":"chart", "chart_type": "bar", "title": "Quarterly Performance", "labels": ["Q1", "Q2", "Q3", "Q4"], "datasets": [ {"label": "Revenue", "values": [120, 145, 132, 178]}, {"label": "Expenses", "values": [95, 108, 99, 122]} ], "y_label": "USD (thousands)", "caption": "Quarterly revenue vs. expenses" }, {"type":"h2", "text":"Market Share (Pie Chart)"}, {"type":"chart", "chart_type": "pie", "labels": ["Product A", "Product B", "Product C", "Other"], "datasets": [{"values": [42, 28, 18, 12]}], "caption": "Annual market share by product line" }, {"type":"pagebreak"}, {"type":"h1", "text":"Mathematics"}, {"type":"body", "text":"Display math is rendered via matplotlib mathtext — no LaTeX binary installation required. Inline references use standard [N] notation in body text."}, {"type":"math", "text":"E = mc^2", "label":"(1)"}, {"type":"math", "text":"\\int_0^\\infty e^{-x^2}\\,dx = \\frac{\\sqrt{\\pi}}{2}", "label":"(2)"}, {"type":"math", "text":"\\sum_{n=1}^{\\infty} \\frac{1}{n^2} = \\frac{\\pi^2}{6}", "caption":"Basel problem (Euler, 1734)"}, {"type":"h1", "text":"Process Flow"}, {"type":"body", "text":"Flowcharts are drawn directly using matplotlib patches — no Graphviz or external tools needed. Supported node shapes: rect, diamond, oval, parallelogram."}, {"type":"flowchart", "nodes": [ {"id":"start", "label":"Start", "shape":"oval"}, {"id":"input", "label":"Receive Input", "shape":"parallelogram"}, {"id":"valid", "label":"Valid?", "shape":"diamond"}, {"id":"proc", "label":"Process Data", "shape":"rect"}, {"id":"err", "label":"Return Error", "shape":"rect"}, {"id":"out", "label":"Return Result", "shape":"parallelogram"}, {"id":"end", "label":"End", "shape":"oval"} ], "edges": [ {"from":"start", "to":"input"}, {"from":"input", "to":"valid"}, {"from":"valid", "to":"proc", "label":"Yes"}, {"from":"valid", "to":"err", "label":"No"}, {"from":"proc", "to":"out"}, {"from":"err", "to":"end"}, {"from":"out", "to":"end"} ], "caption": "Data validation and processing flow" }, {"type":"h1", "text":"Code Example"}, {"type":"code", "language":"python", "text":"# Design token pipeline\ntokens = palette.build_tokens(\n title=\"Annual Report\",\n doc_type=\"report\",\n author=\"J. Smith\",\n date=\"March 2026\",\n)\nhtml = cover.render(tokens)\npdf = render_cover(html)"}, {"type":"h1", "text":"Design Principles"}, {"type":"body", "text":"The aesthetic system is documented in design/design.md. The core rule: every design decision must be rooted in the document content and purpose. A color chosen because it fits the content will always outperform a color chosen because it seems safe."}, {"type":"h2", "text":"Restraint over decoration"}, {"type":"body", "text":"The page is done when there is nothing left to remove. Accent color appears on section rules only — not on headings, not on bullets. No card components, no drop shadows."}, {"type":"callout", "text":"A PDF passes the quality bar when a designer would not be embarrassed to hand it to a client."}, {"type":"pagebreak"}, {"type":"bibliography", "title": "References", "items": [ {"id":"1","text":"Bringhurst, R. (2004). The Elements of Typographic Style (3rd ed.). Hartley & Marks."}, {"id":"2","text":"Cairo, A. (2016). The Truthful Art: Data, Charts, and Maps for Communication. New Riders."}, {"id":"3","text":"Hochuli, J. & Kinross, R. (1996). Designing Books: Practice and Theory. Hyphen Press."} ] } ] JSON cmd_run \ --title "minimax-pdf demo" \ --type "report" \ --author "minimax-pdf skill" \ --date "$(date '+%B %Y')" \ --subtitle "A demonstration of the token-based design pipeline" \ --content "$tmpdir/content.json" \ --out "demo.pdf" rm -rf "$tmpdir" } # ── dispatch ─────────────────────────────────────────────────────────────────── main() { if [[ $# -lt 1 ]]; then bold "minimax-pdf — make.sh" echo "" echo "Usage: bash make.sh [options]" echo "" echo "Commands:" echo " check Verify all dependencies" echo " fix Auto-install missing deps" echo " run --title T --type TYPE CREATE: full pipeline → PDF" echo " [--author A] [--date D] [--subtitle S]" echo " [--abstract A] [--cover-image URL]" echo " [--accent #HEX] [--cover-bg #HEX]" echo " [--content content.json] [--out output.pdf]" echo " fill --input f.pdf FILL: inspect or fill form fields" echo " reformat --input doc.md REFORMAT: parse doc → apply design → PDF" echo " demo Build a full-featured demo PDF" exit 0 fi case "$1" in check) cmd_check ;; fix) cmd_fix ;; run) shift; cmd_run "$@" ;; fill) shift; cmd_fill "$@" ;; reformat) shift; cmd_reformat "$@" ;; demo) cmd_demo ;; *) echo "Unknown command: $1"; exit 1 ;; esac } main "$@"