Skip to content
mdzip-projectPublic

About

CLI to create and extract .mdz files

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

mdz command-line interface (CLI)

A cross-platform command-line interface for creating, extracting, validating, and inspecting .mdz files.

The .mdz format is a portable, self-contained document format that packages one or more Markdown content files together with their associated assets into a single ZIP archive. See the MDZ specification for full details.


Installation

Although the CLI is built with C#, binaries are distributed as prebuilt release assets. .NET is not required for normal CLI use.

One-line install

Windows (WinGet):

winget install MDZip.Cli

Windows (PowerShell, will put mdz.cmd in %LOCALAPPDATA%\Microsoft\WindowsApps):

irm "https://raw.githubusercontent.com/mdzip-project/mdzip-cli/refs/heads/main/scripts/install.ps1?nocache=$(Get-Date -UFormat %s)" | iex

If you need to pin a specific release version during install:

$env:MDZ_VERSION='vX.Y.Z'
irm "https://raw.githubusercontent.com/mdzip-project/mdzip-cli/refs/heads/main/scripts/install.ps1?nocache=$(Get-Date -UFormat %s)" | iex

Linux/macOS system-wide install (recommended if you can use sudo):

curl -fsSL https://raw.githubusercontent.com/mdzip-project/mdzip-cli/refs/heads/main/scripts/install.sh | sudo sh

Linux/macOS (no sudo required, but you might need to fix PATH):

curl -fsSL https://raw.githubusercontent.com/mdzip-project/mdzip-cli/refs/heads/main/scripts/install.sh | sh

If mdz is not found after non-sudo install, add ~/.local/bin to your PATH:

# current shell
export PATH="$HOME/.local/bin:$PATH"

# persist for bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc && source ~/.bashrc

# persist for zsh
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc && source ~/.zshrc

After install, run:

mdz --help

Usage

mdz <command> [options]
mdz create <source> <output> [options]
mdz add <archive> <entry-path> <file>
mdz remove <archive> <entry-path>
mdz extract <archive> [options]
mdz validate <archive>
mdz ls <archive> [options]
mdz inspect <archive>

Note: <archive> is the .mdz file path; .mdz extension is optional.

Root options

Option Description
-v, --version Show version information
-?, -h, --help Show help and usage information

Commands

Command Description
create Create an .mdz archive from a source directory
add Add or replace a file entry in an existing .mdz archive
remove Remove a file entry from an existing .mdz archive
extract Extract the contents of an .mdz archive
validate Validate an .mdz archive against the specification
ls List the contents of an .mdz archive
inspect Inspect metadata and manifest information of an .mdz archive

mdz create <source> <output> [options]

Creates an .mdz archive from all files in a source directory. If no unambiguous entry point is found and you are in an interactive terminal, mdz prompts to generate a default index.md.

mdz create ./my-doc-folder my-doc.mdz --title "My Document" --author "Jane Smith" --entry-point index.md
mdz create --source ./my-doc-folder --output my-doc.mdz --force
mdz create ./my-doc-folder my-doc.mdz --create-index
mdz create ./my-doc-folder my-doc.mdz --map-files --entry-point index.md
mdz create ./my-doc-folder my-doc.mdz --mode project --entry-point index.md
mdz create ./my-doc-folder my-doc.mdz --filter "**/*.md" --filter "**/*.png" --filter "**/*.jpg" --filter "**/*.jpeg" --filter "**/*.webp" --filter "**/*.svg"
Option Description
--source, -s Required source directory (can also be provided positionally as <source>)
--output, -o Required output archive path (can also be provided positionally as <output>). If no extension is supplied, .mdz is added automatically
--filter, -fi Glob filter (archive-relative, repeatable). If omitted, defaults to markdown and common image files
--force, -f Overwrite output file if it already exists
--create-index, -ci Auto-generate index.md when no unambiguous entry point can be resolved
--map-files, -mf * Write/update manifest.json files[] mapping (path, originalPath, title). Invalid source paths are sanitized when needed
--title, -t * Sets manifest.title
--entry-point, -e * Sets manifest.entryPoint. Must name a .md or .markdown file in the archive, with exact case
--language, -l * Sets manifest.language (BCP 47, for example en, fr-CA). Defaults to en when another * option is passed
--author, -a * Sets manifest.author.name
--description, -d * Sets manifest.description
--mode * Sets manifest.mode to document or project
--doc-version * Sets manifest.version (for example 1.0.0)

Notes:

  • Every archive mdz create writes has a manifest.json with spec.version, title, entryPoint and mode (spec 1.2.0 §14.1). Without * options, the title is the source folder name and the entry point is discovered.
  • * = Manifest metadata option. If none is passed and --filter packages a manifest.json from the source, that manifest is used as-is (unknown fields kept) and only missing required fields are added. Otherwise the source manifest.json is replaced by the generated one.
  • Entry point discovery follows spec §5.5: index.md, else the one root Markdown file. Root README, AGENTS, LICENSE, CHANGELOG and CONTRIBUTING files are set aside when another root Markdown file exists.
  • manifest.mode defaults to document when omitted; use --mode project for multi-document project archives.
  • manifest.producer is optional in the spec; this CLI writes it by default.
  • If manifest-writing options are used without --title, title defaults to the source folder name.
  • If invalid archive path characters are found and --map-files is not passed, interactive mode prompts to enable file mapping.

Example manifest generated by mdz create:

{
  "spec": {
    "name": "mdzip-spec",
    "version": "1.2.0"
  },
  "title": "My Document",
  "entryPoint": "index.md",
  "mode": "document",
  "language": "en",
  "producer": {
    "application": {
      "name": "mdz-cli",
      "version": "<current mdz-cli version>",
      "url": "https://github.com/mdzip-project/mdzip-cli"
    },
    "core": {
      "name": "MDZip.Core",
      "version": "<current MDZip.Core version>",
      "url": "https://github.com/mdzip-project/mdzip-core"
    }
  },
  "author": {
    "name": "Jane Smith"
  }
}

mdz add <archive> <entry-path> <file>

Adds a local file into an existing archive at the given archive-relative path. If the entry path already exists, it is replaced.

add, remove and manifest set keep unknown manifest fields and every untouched entry byte-for-byte, and add a manifest.json to an archive that has none. A change that would make the archive open at a different entry point (for example removing index.md when other root Markdown files remain) is refused with ERR_ENTRYPOINT_CHANGED; set --entry-point with mdz manifest set first.

mdz add my-doc.mdz assets/images/cover.png ./cover.png
mdz add my-doc.mdz chapter-02.md ./chapter-02.md

mdz remove <archive> <entry-path>

Removes a file entry from an existing archive.

mdz remove my-doc.mdz assets/images/old-cover.png

mdz extract <archive> [options]

Extracts an .mdz archive to a destination directory.

mdz extract my-doc.mdz --output ./extracted
Option Description
--allow-invalid Extract even if archive validation fails
--output, -o Destination directory. Defaults to a directory named after the archive in the current folder

mdz validate <archive>

Validates an .mdz archive against the specification. Exits with code 0 if valid, 1 if invalid.

Errors carry the spec's error codes, for example ERR_ENTRYPOINT_INVALID (a malformed entryPoint), ERR_ENTRYPOINT_MISSING (no such entry; a case-only match is suggested), ERR_DUPLICATE_PATH (entries equal after NFC normalization) and ERR_ZIP_INVALID (compression other than stored or DEFLATE). Symbolic link entries are reported as warnings and are never followed or extracted as links. A missing manifest.json is a note, not a warning: the archive still conforms.

mdz validate my-doc.mdz

mdz ls <archive> [options]

Lists the contents of an .mdz archive.

mdz ls my-doc.mdz
mdz ls my-doc.mdz --long
Option Description
--long, -l Show detailed information (size, compressed size, last modified)

mdz inspect <archive>

Displays metadata and manifest information from a .mdz archive.

mdz inspect my-doc.mdz

inspect surfaces manifest.spec.version (falling back to legacy mdz if present), manifest.mode, and other key manifest metadata.


Archive structure

A .mdz file follows the MDZ specification:

document.mdz
|- index.md               # Recommended entry point
|- manifest.json          # Metadata (mdz always writes one)
|- chapter-01.md
`- assets/
   `- images/
      `- cover.png

Paths inside an archive are case-sensitive and compared after Unicode NFC normalization (spec §5.4.1): mdz cat doc.mdz Index.md won't open index.md, but it will suggest it.

Development

Implementation note: this project is written in C# and targets .NET 8.

Browser Builder (experimental)

A standalone browser page is available at mdzip.org/packager.html. It accepts a dropped or selected folder, builds an .mdz, previews the resolved entry-point markdown, and provides a download link for the generated archive.

Building from source requires .NET 8 SDK. For Linux/macOS setup help, see Install .NET on Linux/macOS.

# Build
dotnet build

# Test
dotnet test

# Run directly
dotnet run --project src/mdz.Cli/mdz.Cli.csproj -- --help

# Publish a single self-contained executable (example: Windows x64)
dotnet restore .\mdz-cli.slnx --configfile .\NuGet.Config
dotnet publish src/mdz.Cli/mdz.Cli.csproj -c Release -r win-x64 --self-contained true --no-restore -p:DebugType=None -p:DebugSymbols=false -o out/win-x64

# Or use the helper script (restores + verifies the mdz.exe/mdz output)
powershell -ExecutionPolicy Bypass -File .\scripts\publish-single.ps1 -Rid win-x64 -ConfigFile .\NuGet.Config

WinGet release automation is documented in docs/winget.md.

About

CLI to create and extract .mdz files

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages