Skip to main content

mkd

Contents:

What is mkd?

mkd is a local-first, Markdown-oriented CLI that gathers an essential family of Markdown chores under a single handy executable that doesn't:

  • hold any opinions about your project's file layout
  • force heavyweight tools (like Pandoc, Poppler, and RapidOCR) on you prematurely

What can it do?

  • Transcribe images and PDFs into page-sectioned Markdown, with a content-addressed local cache for large jobs
  • Convert between DOCX/HTML/Markdown via Pandoc, with a tweakable reference DOCX letting you easily specify your desired house style
  • Split/join Markdown files, backed by manifests to survive edit cycles without losing order
  • Reflow (e.g. to one sentence per line) for diff-clean prose review
  • Word counts (including tags allowing multiple custom section-based counts at once)
  • Proofing via deterministic OCR cleanup (no LLM needed), with fixes auto-applied if safe and surfaced for review otherwise

Why can I trust it?

mkd's trust model is local-first and keeps trust boundaries explicit and simple: its commands are guaranteed to only read from and write to filepaths that are specified explicitly (or stdio).

mkd also supports automatically using output paths derived simply and deterministically from input filenames/paths (e.g., message.htmlmessage.md).

What mkd will never do

  • fetch remote URLs
  • check remote links
  • log into services
  • talk to a server
  • crawl repos
  • watch directories

Side effects

Command(s) Side effect Auto-cleaned? Enabled by default? Notes
transcribe pdf, transcribe images Creating and writing caches (for durability and efficiency) Placed at ${XDG_CACHE_HOME:-~/.cache}/mkd by default. transcribe image intentionally has no cache flags.
Most commands Silently overwrite explicit/derived output paths n/a Exceptions: split, join, and reference (must pass --force)
from-docx, to-docx, transcribe pdf Temporary files used as part of processing (placed in the filesystem temp directory, e.g. /tmp) from-html and reference do not use processing temp files.

That's it!

Is it for me?

  • Who it is for: an engineer or writer who wants these operations made reliable, durable and scriptable.
  • What it is not: a document server, network client, or policy engine.

Getting started

Prerequisites

Ensure the following are available (including in the $PATH, if applicable) before proceeding:

  • Python >= 3.11
  • uv

Subcommand-specific

The following dependencies are only required by certain mkd subcommands:

Dependency Subcommands requiring it
Pandoc count, from-docx, from-html, to-docx, reference
Poppler transcribe pdf

mkd doctor checks these external tools. A required minimum is a compatibility floor; a recommended minimum is the maintained support baseline and does not mean older versions cannot work.

[!TIP] On macOS, both can be installed easily via Homebrew:

brew install pandoc poppler

Installation

Use the checkout directly while developing:

uv sync
uv run mkd --help

To install mkd machine-wide (based on a local checkout of the repo), run this in your checkout's root dir:

uv tool install -e . --force
mkd --help

[!TIP]

  • Examples below use the bare mkd.
  • From an unmanaged checkout, prefix them with uv run.
  • For project-pinned invocation (via wrapper scripts), see docs/operations.md.

Core commands

[!IMPORTANT] Run mkd doctor first to confirm the environment before working on live documents (especially after installing or upgrading mkd or an external tool).

  • Check the environment, then preflight and count a draft:

    mkd doctor --smoke                      # probe pandoc, Poppler, and RapidOCR
    mkd check paper.md --fail-on warning    # fail the command on warnings, not just errors
    mkd count paper.md
    
  • Format prose for line-oriented review:

    mkd sentences draft.md formatted.md     # write to a new file
    mkd sentences draft.md --in-place       # rewrite the input (explicit opt-in)
    
  • Transcribe source material into page-sectioned Markdown:

    mkd transcribe pdf reading.pdf --output reading.md
    mkd transcribe pdf reading.pdf --output reading.md --confidence-report reading-confidence.json
    mkd transcribe pdf reading.pdf --page-range 12 18 --mode auto --output reading-p12-p18.md
    mkd transcribe images pages/ --output pages.md
    
  • Proof OCR output, then split it by page and join it back after editing:

    mkd proof transcript.md proofed.md --report proof-report.json
    mkd split proofed.md parts --by page --manifest
    mkd join --manifest parts/_mkd-split.json --output edited.md
    
  • Convert across document formats:

    mkd from-docx authoritative.docx draft.md --keep-comments
    mkd from-html export.html export.md --selector '.WordSection1'
    mkd to-docx draft.md final.docx --reference-doc reference.docx
    

Testing

  • Run the full test suite:

    uv run pytest
    
    • Tests target module boundaries first, then CLI wiring.
    • tests/test_cli.py drives the app through Typer's CliRunner.
    • The test suite runs fully offline, matching the runtime contract.
    • Pull requests run the locked suite across every supported Python minor.

    See docs/testing.md for suite structure, CI design rationale, mocks, and smoke checks.

  • Lint:

    # checks:
    # - line length 100
    # - rule sets E, F, I, N, W
    uv run ruff check .
    

Documentation map

Document Contains
docs/commands.md Full CLI reference covering options, outputs, & failure semantics
docs/workflows.md End-to-end recipes for transcription, proofing, checks, conversion, & split/join
docs/operations.md Install modes, environment checks, & stdout/file behavior
docs/architecture.md Module boundaries, external-tool edges, cache model, & invariants
docs/cache.md Cache location, invalidation, refresh, & cleanup
docs/troubleshooting.md Common failures & how to narrow them
docs/extension-guide.md & docs/development.md Adding commands & following repo conventions

Other features

Transcription cache

To make large jobs cheap to rerun, transcribe pdf and transcribe images cache successful pages by default.

CLI flags

  • Default runs reuse valid successful entries and retry misses or prior failures.
  • --refresh ignores cached successes and rewrites them.
  • --no-cache disables reads and writes for a single run.
  • --cache-dir PATH uses PATH as the app cache root for a single run.
  • --confidence-report PATH writes OCR confidence as separate JSON without changing transcript Markdown.

Confidence reports preserve ordered target, line, and fragment evidence for review; they do not annotate or otherwise alter transcript Markdown.

Cache location

By default, the cache lives at ${XDG_CACHE_HOME:-~/.cache}/mkd.

mkd cache path
mkd cache prune --older-than 30d --dry-run
mkd cache clean --force

Lifecycle

  • Cache entries key on:

    • the source bytes
    • the mkd version
    • the processing options
    • the signatures of the OCR and Poppler tools involved
  • This is so that changes in the user's choice of tools or options invalidate stale entries automatically.

[!TIP]

  • Cache data can contain extracted document text and diagnostic source paths.
  • Use --no-cache for a sensitive one-off run.
  • Full layout and invalidation rules can be found at docs/cache.md.

Word-count markers

  • By default, mkd count counts the whole rendered document.

  • To count only selected regions, wrap them:

    <!-- word-count-begin -->
    This counts.
    <!-- word-count-end -->
    
    • Short forms (<!-- wcb --> and <!-- wce -->) are also accepted.
    • Active markers are recognized only outside fenced code blocks.
    • Marker-looking comments inside fenced code blocks stay inert examples and mkd check reports them as visible informational findings that do not fail --fail-on warning.

[!TIP] Run mkd check before trusting marked counts in automation.

Design and safety

The core is deliberately small and boring: every capability is a thin CLI shell over a Typer-free module that can be tested in isolation.

  • Local-first and path-based: commands take explicit input/output paths, write local files or stdout, and perform no network I/O.
  • Thin CLI over tested modules: src/mkd/cli.py owns only Typer wiring, option validation, and exit codes; domain logic lives in modules that return plain values, dataclasses, or typed exceptions.
  • Heavyweight tools at narrow edges: Pandoc, Poppler, and RapidOCR are resolved with shutil.which, invoked through a bounded shared subprocess boundary, and translated into mkd objects or typed errors immediately, so foreign formats never leak inward; operations.md documents the supported timeout tuning contract.
  • Failures are contract, not surprise: expected problems raise typed MkdError subclasses, which the CLI renders as mkd <command>: <message> rather than as a stack trace.

Guaranteed invariants

mkd's only persistent side effects are documented and local: explicit output files, explicit in-place rewrites, the app cache at ${XDG_CACHE_HOME:-~/.cache}/mkd unless disabled or overridden, and temporary files under the system temp directory during conversion or OCR.

  • No network, ever: no command fetches a remote URL, and check validates structure and DOCX hazards statically, without invoking Pandoc.
  • Deterministic overwrite semantics: inputs are rewritten only via explicit --in-place; split, join, and reference refuse an existing output without --force; all other commands overwrite their output paths without prompting; proof, from-html, and join additionally refuse output paths that collide with an input (full per-command contract: Stdout and output files).
  • Atomic user outputs: file outputs are completed in a private sibling temporary path, given their final permission bits, and file-fsynced before replacement; existing output symlinks update their regular-file targets without replacing the links.
  • Explicit durability limits: split output is atomic per file, not transactional across its directory; newly created ancestor directories are not fsynced; and a rejected best-effort parent-directory fsync can leave a successfully visible file with weaker crash durability.
  • Clean stdout: content-producing commands keep stdout to content and send diagnostics to stderr or explicit report files, so mkd composes in pipelines.
  • Stable transcript headings: every transcript section is headed ### Page <label>; this is a cross-module contract, so proof preserves it, page-based split recognizes it, and the cache keys on the label that produced it.

mkd doctor returns a stable scheme: 0 (healthy), 1 (a failed check, or a warning under --strict), and 2 (a usage or orchestration error). Other commands exit non-zero on a handled failure.

For the full module map, external-tool contracts, and the complete invariant list, see docs/architecture.md.

External dependencies

  • mkd ships thin and defers document-format interpretation to established tools.

  • mkd doctor reports which are present, their versions, and whether they satisfy any required compatibility floor or recommended support baseline (and, with --smoke, whether they actually run);

    • Missing tools surface as concise, actionable CLI errors.
    • Required minimums reject incompatible versions; recommended baselines warn without claiming older versions cannot work.
Tool Powers Notes
Pandoc word counts, DOCX and HTML conversion, reference DOCX parses Markdown to JSON; owns format interpretation
Poppler (pdfinfo, pdftotext, pdftoppm) PDF page counts, text extraction, rasterization text-first, with raster fallback for OCR
RapidOCR OCR of images and PDF fallback pages installed into the environment automatically by uv

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mkd-0.5.0.tar.gz (136.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mkd-0.5.0-py3-none-any.whl (93.6 kB view details)

Uploaded Python 3

File details

Details for the file mkd-0.5.0.tar.gz.

File metadata

  • Download URL: mkd-0.5.0.tar.gz
  • Upload date:
  • Size: 136.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.2 {"installer":{"name":"uv","version":"0.12.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for mkd-0.5.0.tar.gz
Algorithm Hash digest
SHA256 1a2b90dffa8dd76e2364954fd40575a1115702dbad40e0b0ff9267b445a2dfcf
MD5 2e23a3d07da3e32a89ba110b043242d3
BLAKE2b-256 4787025fb9da4a305425c68a299c4fac6e301d280898671f7c4e4dc5fd07eb22

See more details on using hashes here.

File details

Details for the file mkd-0.5.0-py3-none-any.whl.

File metadata

  • Download URL: mkd-0.5.0-py3-none-any.whl
  • Upload date:
  • Size: 93.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.12.2 {"installer":{"name":"uv","version":"0.12.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for mkd-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ce114f0a84dee627a9163d9cfb42c2f092819e0f8b805698f10ccc36533ead7a
MD5 858868f5c51c3a49a69ab9439dcdaf41
BLAKE2b-256 14b0d13954ef924a617274f808e0a268db897798fd172e1658ed012163d28b33

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.5.0 This release

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page