doc-engine-cli
Zero-config Markdown → PDF documentation engine
Transform any README.md into a premium, print-ready PDF report — no configuration, no templates, no LaTeX.
pipx install doc-engine-cli
Built and maintained by one person, in the open. Sponsorship keeps it that way.
Overview
doc-engine-cli is a developer-first CLI tool that converts Markdown files into professionally styled PDF documents using Typst as its rendering backend. It is designed for teams and individual developers who need high-quality documentation artifacts without the complexity of LaTeX or manual typesetting.
The tool auto-detects your README.md, extracts metadata from Git, and produces an IEEE-inspired technical document — complete with cover page, table of contents, and premium typography — in a single command.
doc-engine build
That's it. Zero configuration required.
Features
| Feature | Description |
|---|---|
| Zero-Config | Auto-detects README.md, Git author, and document title. No setup files needed. |
| Seven Templates | Academic, article, book, minimal, modern, report, and technical layouts, each with a configurable accent color. Point --template at your own .typ file to go further. |
| Front Matter | An optional --- metadata block sets the title, subtitle, author, template, and accent right inside the file. |
| Watch Mode | --watch rebuilds the PDF every time you save the source. |
| Diagrams | ```mermaid and ```svg blocks are rendered as real diagrams, in pure Python — no Node, no headless browser. |
| Alerts | > [!NOTE] and friends render as coloured callouts, the way GitHub shows them. |
| Math | LaTeX math, inline with $…$ and display with $$…$$, translated into native Typst math. |
| Multi-File | A doc-engine.md manifest builds one PDF from many files, diagrams, figures, and a bibliography. |
| Page Sizes | A4 by default, plus A3–A6, ISO/JIS B5, and US letter, legal, and tabloid. |
| Project Config | A .doc-engine.toml keeps a team's defaults out of every document. |
| Rich Markdown | Embeds local images, renders GitHub task lists as real checkboxes, and turns [^1] footnotes into native Typst footnotes. |
| Error Checking | Reports source problems with line and column before compiling. A --dry-run mode runs the check on its own. |
| Non-Destructive | Never overwrites an existing PDF — writes report (1).pdf, report (2).pdf, … unless you pass --force. |
| Premium Typography | Font stacks that end in a font Typst ships, so a document looks the same in a bare container as on a laptop. |
| Pure Python | No external binaries required (no Pandoc, no LaTeX). Ships as a single pip install. |
| Cross-Platform | Works on Windows, macOS, and Linux with Python 3.10+. |
Academic Writing
Cite with the usual [@citation-key] syntax and drop a refs.bib,
references.bib, or bibliography.bib next to your document. It is picked up
automatically and rendered as an IEEE-styled references section:
As shown in [@smith2020], results vary.
Point --bib at a different file, or name one in front matter, to override the
search. Combine it with the academic, article, or report layout and LaTeX
math for a paper that needs no LaTeX toolchain.
Quick Start
Installation
pipx install doc-engine-cli
(If you don't have pipx, you can install it via pip install pipx)
Generate Your First PDF
Navigate to any project directory containing a README.md and run:
doc-engine build
The tool will:
- Auto-detect
README.mdin the current directory - Extract the document title from the first
# heading - Read your Git
user.namefor the author field - Generate a
README_doc.pdfwith cover page, ToC, and formatted content
Explicit Options
doc-engine build path/to/file.md -o output.pdf -t "Custom Title" -a "Author Name"
Usage
Switching templates, recoloring the accent, and checking a file for errors.
Commands
doc-engine build [INPUT_FILE] Convert a Markdown file into a PDF
doc-engine info Show version, repository, and what is supported
doc-engine info --json The same, as JSON, for tools that drive the CLI
doc-engine --version Print the version and exit
doc-engine --help Show all commands and flags
build flags
| Flag | Default | Description |
|---|---|---|
INPUT_FILE |
auto-detect README.md |
Path to the Markdown file to convert. |
-o, --output |
<input>_doc.pdf |
Output PDF path. |
-t, --title |
first # heading |
Document title override. |
-s, --subtitle |
none | Subtitle shown under the title on the cover. |
-a, --author |
git config user.name |
Author name override. |
--date |
today | Date shown on the cover. |
--template |
academic |
A built-in layout (academic, article, book, minimal, modern, report, technical) or a path to your own .typ file. |
--accent |
template default | Accent color as a hex value (#2563eb) or a name (blue, teal, rose, ...). |
--paper |
a4 |
Page size: a3–a6, iso-b5, jis-b5, us-letter, us-legal, us-tabloid. |
--bib |
auto-detect refs.bib |
Path to a custom .bib file for the bibliography. |
--pdf-standard |
off | Write an archival PDF/A file: a-2b or a-3b. |
--code-theme |
Typst default | Syntax highlighting theme: github, solarized, monochrome, or a path to a .tmTheme. |
--tall-images |
fit |
What to do with a picture taller than a page: fit scales it onto one page, split cuts it across several. |
--fetch-images |
off | Download images linked by URL instead of rendering their alt text. |
--no-branding |
off | Hide the doc-engine attribution from the PDF. |
--dry-run |
off | Check the Markdown for errors and exit without writing a PDF. |
-w, --watch |
off | Rebuild automatically whenever the source file changes. |
-f, --force |
off | Overwrite the output file instead of writing a numbered copy. |
--open |
off | Open the PDF after it is generated. |
Any flag can also be set in the front matter (see below); a flag on the command line always wins.
Examples
Basic — zero-config mode:
cd my-project
doc-engine build
# → Generates README_doc.pdf
Specify input and output:
doc-engine build CONTRIBUTING.md -o contributing_guide.pdf
Override metadata:
doc-engine build -t "API Reference v2.0" -a "Engineering Team"
Pick a template and accent color:
doc-engine build --template modern --accent teal
doc-engine build --template technical --accent "#7c3aed"
Check for errors before building:
doc-engine build --dry-run
Drop the engine attribution from the PDF:
doc-engine build --no-branding
Generate and open immediately:
doc-engine build --open
Rebuild on every save:
doc-engine build --watch
Use as Python module:
python -m doc_engine build README.md
Front Matter
Any Markdown file can open with a --- block to carry its own settings, so the
document renders the same way for everyone — no flags to remember:
---
title: Payments API
subtitle: Integration Guide
author: Platform Team
template: technical
accent: teal
---
# Payments API
...
Supported keys: title, subtitle, author, date, template, accent,
paper, and bib. A flag passed on the command line overrides the matching front-matter key,
which in turn overrides the auto-detected value.
Watch Mode
Pass --watch to keep doc-engine running and rebuild the PDF whenever you save
the source. It's the fastest way to tweak a template or accent and see the result:
Every file the manifest names is watched, and a save rebuilds the whole document.
doc-engine build --watch --template modern --accent teal
The output path is chosen once when watch starts, then rewritten in place on each
change. Press Ctrl+C to stop.
Diagrams
Fenced blocks tagged mermaid or svg become pictures instead of code:
```mermaid
flowchart LR
Client --> API --> Ledger
```
Mermaid is rendered through an embedded JavaScript engine, so there is no Node
install and no headless browser — it stays a plain pip install. A svg block
is passed straight through, since Typst draws SVG natively.
If a diagram has a syntax error, the build stops and reports Mermaid's own message rather than producing a broken document.
Math
LaTeX math is translated into native Typst math, inline with $…$ and as a
display block with $$…$$:
The quadratic formula is $x = \frac{-b \pm \sqrt{b^2-4ac}}{2a}$.
$$
P(A \mid B) = \frac{P(B \mid A)\,P(A)}{P(B)}
$$
Greek letters, relations, fractions, roots, sub- and superscripts, font
commands, matrices, and cases are covered. Anything unrecognized passes
through with its backslash removed, which lands on the right Typst symbol most
of the time.
A $ that is not math stays untouched, so prices and shell variables survive:
$10, $HOME, and export $PATH all render as written.
Alerts
A blockquote that opens with a marker becomes a coloured callout, matching what GitHub shows on the page:
> [!NOTE]
> Useful information worth knowing.
> [!WARNING]
> Something that needs attention.
NOTE, TIP, IMPORTANT, WARNING, and CAUTION are all recognized. A
blockquote without a marker stays an ordinary quote.
Code Themes
Code blocks are highlighted with Typst's own colours by default. --code-theme
swaps that for something else:
doc-engine build --code-theme github
doc-engine build --code-theme monochrome # for printing in black and white
| Theme | Look |
|---|---|
github |
GitHub's light palette |
solarized |
Solarized light |
monochrome |
Greys only — keeps code legible on a black-and-white printer |
Any TextMate .tmTheme file works too, so a theme from your editor can be
pointed at directly:
doc-engine build --code-theme ~/themes/my-editor.tmTheme
Cross-References
Inside a manifest build, a link from one included file to another becomes a jump within the PDF rather than a link to a file the reader does not have:
For the full picture see [the data model](model.md).
That resolves to the place where model.md was merged in. Links to anything
outside the build — a URL, a file that is not part of the manifest — are left
exactly as they are.
Multi-File Documents
A project that has outgrown a single file lists its parts in doc-engine.md,
using ordinary Markdown links so the manifest still reads as a table of
contents on GitHub:
---
title: Payments Platform
subtitle: Engineering Handbook
template: report
---
- [Overview](doc/overview.md)
- [Architecture](diagrams/architecture.mmd)
- [Schema](img/schema.svg)
- [References](bib/references.bib)
Then just build:
doc-engine build
One manifest, one command: sections, a diagram, a figure, and a bibliography.
Each entry is handled by what it is:
| Entry | What happens |
|---|---|
.md |
Appended as a section, headings intact |
.mmd, .mermaid |
Rendered as a diagram at that point |
.png, .svg, .jpg, … |
Placed as a captioned figure |
.bib |
Registered as the bibliography for the document |
Paths resolve against the manifest's folder, and every included file resolves
its own images relative to itself — so a file builds the same way alone as it
does inside the manifest. --watch follows every file the manifest names.
Project Configuration
Team defaults belong in a .doc-engine.toml next to the project, not repeated
in every document:
[doc-engine]
template = "report"
accent = "teal"
paper = "us-letter"
A [tool.doc-engine] table in pyproject.toml works the same way. Precedence
runs command-line flag, then front matter, then this file.
Every key it understands:
| Key | Values |
|---|---|
template |
A built-in layout name, or a path to a .typ file |
paper |
a3–a6, iso-b5, jis-b5, us-letter, us-legal, us-tabloid |
accent |
A hex value or a colour name |
author |
Author name, used instead of the Git user |
bib |
Path to a .bib file |
branding |
false hides the doc-engine attribution |
code_theme |
github, solarized, monochrome, or a path to a .tmTheme |
pdf_standard |
a-2b or a-3b |
tall_images |
fit or split |
fetch_images |
true downloads images linked by URL |
Anything else in the table is ignored, so a typo cannot quietly change how a document is built.
Large Images
A picture that does not fit the text block is scaled down until it does, so nothing is ever clipped. For a tall diagram — a top-down flowchart, a long schema — scaling it onto one page can leave it unreadable, so it can be cut across pages at full size instead:
doc-engine build --tall-images split
Archival PDFs
For documents that have to stay readable for decades:
doc-engine build --pdf-standard a-2b
a-3b is also accepted, which additionally allows embedded attachments.
Templates
doc-engine ships with seven layouts. Switch with --template <name>, and recolor any of them with --accent.
| Template | Look |
|---|---|
academic |
Serif IEEE-style report with cover page, table of contents, and running headers. The default. |
modern |
Clean sans-serif layout with generous spacing and a left-aligned cover. |
minimal |
No cover or table of contents — a compact title block, then straight into the content. |
technical |
Bold layout with a filled accent banner and section markers. Good for engineering docs. |
book |
Classic centered title page with chapter-style section breaks. |
article |
A LaTeX paper: New Computer Modern, numbered sections, title block on page one. |
report |
Roomy and easy on the eyes — 12pt on generous leading, wide margins, lots of air. |
doc-engine build --template book
doc-engine build --template modern --accent rose
Accent colors take a hex value (#0ea5e9) or one of these names: blue, sky, indigo, violet, purple, red, rose, orange, amber, green, emerald, teal, slate, black.
Bring your own template
--template also accepts a path to a .typ file, so you can ship a house style
without forking the project:
doc-engine build --template ./corporate.typ
The quickest way to start is to copy one of the files in
doc_engine/templates/ and edit it. A template exposes a
single setup_doc entry point, and the compiler passes it the document metadata:
#let setup_doc(
title: "",
subtitle: "",
author: "Anonymous",
date: datetime.today().display(),
bibliography_file: none,
accent: none,
branding: true,
version: "",
body,
) = { ... }
Checking for Errors
Before compiling, doc-engine scans the Markdown for problems and reports them with the exact line and column, so you can jump straight to the fix:
README.md:42:8: error: link URL must not be empty
README.md:51:1: warning: image source is empty
Errors stop the build; warnings don't. Use --dry-run to run the check on its own without producing a PDF — handy in CI:
doc-engine build --dry-run
Architecture
┌─────────────┐
│ README.md │
└──────┬──────┘
│
┌──────▼──────┐
│ CLI Layer │ click + rich
│ (cli.py) │ arg parsing, git detection
└──────┬──────┘
│
┌────────────┼────────────┐
│ │
┌──────▼──────┐ ┌───────▼──────┐
│ Converter │ │ Compiler │
│(converter.py)│ │(compiler.py) │
│ │ │ │
│ Markdown AST │ │ Typst → PDF │
│ → Typst │ │ via typst-py│
└──────┬──────┘ └───────┬──────┘
│ │
│ ┌──────────────┐ │
└────► templates/ ◄─────┘
│ *.typ │
└──────┬──────┘
│
┌──────▼──────┐
│ output.pdf │
└─────────────┘
Pipeline
| Stage | Module | Responsibility |
|---|---|---|
| 1. Input Resolution | cli.py |
Locate Markdown file, detect Git metadata |
| 2. Source Checking | linter.py |
Report empty links and unclosed fences with line/column |
| 3. Markdown Parsing | converter.py |
Parse Markdown AST via mistune, emit Typst markup |
| 4. Template Injection | compiler.py |
Merge converted content with the selected template |
| 5. PDF Compilation | compiler.py |
Compile via typst Python bindings |
How It Works
Markdown → Typst Conversion
The converter module parses Markdown using mistune and generates equivalent Typst markup:
| Markdown | Typst Output |
|---|---|
# Heading |
= Heading |
**bold** |
*bold* |
*italic* |
_italic_ |
`code` |
`code` |
[text](url) |
#link("url")[text] |
- item |
- item |
1. item |
+ item |
- [x] task |
rendered checkbox |
text[^1] |
#footnote[...] |
 |
#image("local.png") |
> blockquote |
#block(...) |
--- |
#line(...) |
Special characters (#, $, @, *, _, etc.) are automatically escaped to prevent Typst interpretation.
PDF Templates
Each template lives in doc_engine/templates/ and exposes the same setup_doc entry point, so the compiler can swap between them with --template. The default academic template provides:
- Cover page with title, author, and date
- Table of contents with depth-3 navigation
- Running headers with document title and author
- Page footer with page numbers and engine attribution
- Code blocks with rounded corners and subtle borders
- Heading hierarchy with accent-colored H2 sections
The other templates (modern, minimal, technical, book) keep the same content but change the fonts, layout, and cover. The accent color is injected at compile time, so --accent recolors any of them.
Project Structure
doc-engine-cli/
├── doc_engine/
│ ├── __init__.py # Package version
│ ├── __main__.py # python -m doc_engine entrypoint
│ ├── cli.py # Click-based CLI + Git detection
│ ├── help.py # Rich help screens
│ ├── config.py # .doc-engine.toml project settings
│ ├── settings.py # flag / front matter / project precedence
│ ├── frontmatter.py # Leading --- metadata block
│ ├── manifest.py # doc-engine.md multi-file builds
│ ├── converter.py # Markdown → Typst transpiler
│ ├── latex.py # LaTeX math → Typst math
│ ├── diagrams.py # Mermaid and SVG blocks
│ ├── images.py # Cutting pictures taller than a page
│ ├── remote.py # Downloading linked images
│ ├── compiler.py # Typst → PDF compilation engine
│ ├── linter.py # Source checks (line/column reporting)
│ └── templates/
│ ├── academic.typ # Default IEEE-style report
│ ├── article.typ # LaTeX paper, numbered sections
│ ├── report.typ # Roomy and legible
│ ├── modern.typ # Clean sans-serif layout
│ ├── minimal.typ # Compact, no cover page
│ ├── technical.typ # Accent banner + section markers
│ └── book.typ # Centered title page, chapter breaks
│ └── themes/
│ ├── github.tmTheme # Syntax highlighting themes
│ ├── solarized.tmTheme
│ └── monochrome.tmTheme
├── tests/ # 181 tests across every module
├── pyproject.toml # Package configuration + dependencies
├── LICENSE # MIT License
├── .gitignore
└── README.md
Dependencies
| Package | Purpose | License |
|---|---|---|
click |
CLI framework | BSD-3 |
rich |
Terminal formatting and progress indicators | MIT |
mistune |
Markdown parser (pure Python) | BSD-3 |
typst |
Typst compiler bindings | Apache-2.0 |
mermaidx |
Mermaid rendering without Node | MIT |
pillow |
Cutting pictures taller than a page | MIT-CMU |
All dependencies are pure Python — no external binaries (Pandoc, LaTeX, etc.) are required.
Development
Setup
git clone https://github.com/leonardosalasd/doc-engine-cli.git
cd doc-engine-cli
pip install -e ".[dev]"
Run Tests
python -m pytest tests/ -v
Project Commands
# Generate PDF from this project's README
python -m doc_engine build
# Run with verbose error output
python -m doc_engine build README.md -o docs_output.pdf
Docker
A container image is published to GitHub Container Registry on every release. Mount your project into /workspace and run build as usual:
docker run --rm -v "$PWD:/workspace" ghcr.io/leonardosalasd/doc-engine-cli build
The entrypoint is doc-engine, so you can pass any command or flag:
docker run --rm -v "$PWD:/workspace" ghcr.io/leonardosalasd/doc-engine-cli build --template modern --accent teal
Supported Markdown Elements
- Headings (H1–H6)
- Bold, italic, strikethrough
- Inline code and fenced code blocks (with language hints)
- Links
- Ordered and unordered lists
- Nested lists
- Blockquotes
- Tables
- Horizontal rules
- Line breaks (
<br>) - Task lists (
- [x]/- [ ]) - Footnotes (
[^1]) - Local images, and remote ones with
--fetch-images - Math blocks (LaTeX
$…$and$$…$$) - Mermaid and SVG diagram blocks
- GitHub alerts (
> [!NOTE],[!TIP],[!IMPORTANT],[!WARNING],[!CAUTION])
Roadmap
- Template selection via
--templateflag - Configurable accent color via
--accent - Source error checking with line/column and
--dry-run - User-supplied template files (point
--templateat a path) - YAML front-matter support for metadata override
- Local image embedding
- Watch mode for continuous rebuilds
- Math expressions (LaTeX-style
$...$) - Multi-file documentation merge
- Mermaid diagram rendering
- Image downloading and embedding for remote URLs
- PDF/A compliance for archival
- Page size selection
- Project-level configuration file
- Syntax highlighting themes for code blocks
- Cross-references between documents
Sponsor
doc-engine-cli is written and maintained by one person, in the open, and it is
free under the MIT licence for everyone — no paid tier, no telemetry, no
upsell. If it saves you the afternoon you would have spent fighting LaTeX, you
can put something behind it:
Sponsorship pays for the time that goes into the parts nobody sees: testing on Windows, chasing a rendering bug across five Python versions, answering issues, and keeping the release quality where it is.
It is entirely optional. Starring the repository, reporting a bug you hit, or telling someone the tool exists all help too, and cost nothing.
Contributing
Contributions are welcome — bug reports, documentation fixes, new templates, and features. The contributing guide covers setup, testing across Python versions, code style, and how to add a template.
| Contributing guide | Setup, tests, style, pull requests |
| Code of conduct | Expected behavior in community spaces |
| Security policy | Reporting a vulnerability privately |
| Support | Where to ask questions and report problems |
Questions belong in Discussions; bugs belong in Issues.
License
This project is licensed under the MIT License.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file doc_engine_cli-2.0.0.tar.gz.
File metadata
- Download URL: doc_engine_cli-2.0.0.tar.gz
- Upload date:
- Size: 67.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8769ce2c1ebf858403bf2353e56361d20631545aecbe12c0b68850c54c487bb1
|
|
| MD5 |
a3afecfdf722835458847aec8dc1e52e
|
|
| BLAKE2b-256 |
009cb0e0771d9fab43932b7a66fd80d85c59a6d1b72bae1af508a6d97911dac1
|
File details
Details for the file doc_engine_cli-2.0.0-py3-none-any.whl.
File metadata
- Download URL: doc_engine_cli-2.0.0-py3-none-any.whl
- Upload date:
- Size: 57.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
42b142dd4f39107c81ae8c53e595b62e28e29e228e8c7923ef88d4da09e926a0
|
|
| MD5 |
1139e98be61650cf3e6495f3fafd2a92
|
|
| BLAKE2b-256 |
dbea2f18e8d1114d55d80d6c833a1a9304cb644a219472a963f25df6af57ce13
|