Convert documents to and from Markdown. The source from which polished documents flow.
Markwell gives Claude Code and AI agents the ability to transform documents — Word files, spreadsheets, presentations, and transcripts — using Markdown as the universal hub format. Install it once, and your agent gains a complete document toolkit.
AI agents work in plain text. Documents live in binary formats like .docx, .xlsx, and .pptx. Markwell bridges this gap:
- Ingest any supported document into Markdown so an agent can read and reason about it
- Export Markdown back into polished, styled documents ready for sharing
- Theme exports with professional styling — no manual formatting needed
.docx ─┐ ┌─ .docx
.xlsx ─┤ ├─ .xlsx
.pptx ─┤ ┌──────────┐ ├─ .pptx / .html / .pdf
.vtt ─┤──▶│ Markdown │──▶ ├─ .vtt / .srt
.srt ─┤ └──────────┘ └─ (themed output)
.html ─┤ Hub Format
.rtf ─┤
.json ─┘
npm install -g markwellRequires Node.js 20 or later. All dependencies are included — no additional system software needed.
Install the Markwell skill so Claude Code knows how to use it:
markwell install-skills # project-level (.claude/commands/)
markwell install-skills --global # global (~/.claude/commands/)This copies a skill file that teaches Claude Code the available commands, formats, and options.
Convert any supported file to Markdown:
markwell convert report.docx # → report.md
markwell convert financials.xlsx # → financials/ (one CSV per sheet)
markwell convert deck.pptx # → deck.md
markwell convert meeting.vtt # → meeting.md
markwell convert page.html # → page.md
markwell convert data.json # → data.mdConvert Markdown into styled output using format aliases:
markwell convert draft.md --to docx # → draft.docx
markwell convert draft.md --to word # → draft.docx (alias)
markwell convert data.csv --to xlsx # → data.xlsx
markwell convert data.csv --to excel # → data.xlsx (alias)
markwell convert slides.md --to pptx # → slides.pptx
markwell convert slides.md --to powerpoint # → slides.pptx (alias)
markwell convert slides.md --to html # → slides.html
markwell convert slides.md --to pdf # → slides.pdf
markwell convert transcript.md --to vtt # → transcript.vtt
markwell convert transcript.md --to srt # → transcript.srt
markwell convert draft.md --to docx,pdf # → draft.docx + draft.pdfUse glob patterns to convert multiple files at once:
markwell convert "docs/*.docx" # all Word files
markwell convert "**/*.html" -o output/ # recursive, custom output dir
markwell convert "reports/*.md" --to docx --force # overwrite without prompting| Category | Extensions |
|---|---|
| Document | .docx, .doc |
| Spreadsheet | .xlsx, .xls, .xlsm |
| Presentation | .pptx, .ppt |
| Transcript | .vtt, .srt |
| Web | .html, .htm |
| Rich Text | .rtf |
| Data | .json, .jsonl, .jsonc |
| Drawing | .excalidraw |
| Slides (MD) | .md (Marp format) |
| Category | Extensions | Aliases |
|---|---|---|
| Document | .docx, .html, .pdf* |
word, doc, docx |
| Spreadsheet | .xlsx |
excel, xlsx, xls, sheets |
| Presentation | .html, .pptx, .pdf |
powerpoint, slides, pptx, ppt |
| Transcript | .vtt, .srt |
vtt, srt, transcription |
The primary command. Converts files to or from Markdown.
Options:
--to <format> Export format(s): docx, pptx, pdf, xlsx, vtt, srt, etc.
-o, --output <path> Output file or directory
--theme <name|path> Theme name or path to .markwell.yaml
--force Overwrite existing files without prompting
--dry-run Show what would happen without writing
--verbose Show detailed processing info
When --to is omitted, Markwell ingests the file into Markdown. When --to is provided, it exports Markdown to the specified format. You can use format aliases like powerpoint, word, or excel, and comma-separated values for multi-format output (e.g., --to docx,pdf).
The --dry-run flag is useful for previewing what an agent would do before committing to file writes.
Show all registered ingest and export converters with their supported extensions and formats.
Show details about a specific converter (e.g., markwell converters info docx).
List all available themes — both built-in and any .markwell.yaml found in the directory tree.
Display a fully resolved theme with all settings — colors, typography, spacing, and format-specific options.
Create a starter .markwell.yaml in the current directory. Use --force to overwrite an existing file.
Copy the Claude Code skill file to .claude/commands/markwell.md. With --global, installs to ~/.claude/commands/ instead.
Themes control the visual styling of exported documents. Four built-in themes are included:
| Theme | Description |
|---|---|
default |
Clean, minimal styling — Calibri 11pt, standard margins |
professional |
Corporate/consulting — darker palette, wider margins |
modern |
Contemporary — bolder accent colors, Segoe UI font |
minimal |
Stripped down — content-focused, fewer decorative elements |
# Apply a built-in theme
markwell convert report.md --to docx --theme professional
# Apply a custom theme file
markwell convert report.md --to docx --theme ./brand.markwell.yamlIf you place a .markwell.yaml file in your project, Markwell finds it automatically by walking up the directory tree from the input file. No --theme flag needed.
# Create a project theme
markwell themes init
# Edit .markwell.yaml to customize, then convert — theme applies automatically
markwell convert report.md --to docxA .markwell.yaml file extends a base theme and overrides specific settings:
extends: professional
colors:
primary: "1A4F8B"
accent: "E8491D"
typography:
fontFamily: Georgia
document:
header: "{title}"
footer: "Page {page} of {pages}"
margins:
left: 1800
right: 1800
spreadsheet:
headerBackground: "$primary"
headerBold: true
presentation:
footer: "Confidential"
paginate: trueColor variables like $primary and $accent reference values from the colors section and are substituted automatically.
| Section | Controls |
|---|---|
colors |
Primary, accent, text, background, muted (hex values) |
typography |
Font family, body size, heading sizes, code font |
spacing |
Paragraph spacing, heading spacing (before/after) |
document |
Margins, headers, footers (with {title}, {page}, {pages}, {date} placeholders) |
spreadsheet |
Header row background, text color, bold |
presentation |
Theme name, pagination, background color, header/footer, custom CSS |
transcript |
Speaker label inclusion |
Themes can chain: your theme extends professional, which extends default. Each layer deep-merges — objects merge recursively, primitives replace. Circular inheritance is detected and rejected.
git clone <repo-url>
cd markwell
npm install
npm run build # compile TypeScript + copy themes/skills to dist
npm test # run all 184 tests
npm run lint # ESLint
npm run typecheck # TypeScript strict mode checkThe build step runs tsdown to bundle the CLI entry point, then copies src/themes/ and src/skills/ to dist/ (these are runtime assets that must be accessible from the compiled output).
src/
├── cli/
│ ├── index.ts # CLI entry point (commander setup)
│ ├── setup-registry.ts # Converter registration
│ └── commands/
│ ├── convert.ts # convert command (ingest + export logic)
│ ├── converters.ts # converters list/info commands
│ ├── themes.ts # themes list/preview/init commands
│ └── install-skills.ts # install-skills command
├── core/
│ ├── types.ts # Shared interfaces
│ ├── registry.ts # ConverterRegistry class
│ ├── theme-schema.ts # Theme types and defaults
│ └── theme-loader.ts # Theme resolution, inheritance, variable substitution
├── ingest/ # One file per ingest converter
├── export/ # One file per export converter
│ └── utils/
│ └── markdown-parser.ts # Shared unified/remark AST parser
├── themes/ # Built-in theme YAML files
└── skills/ # Claude Code skill template
tests/
├── fixtures/ # Sample files for testing
└── integration/ # CLI e2e, round-trip, registry, startup tests
Markwell uses a registry pattern with two-stage resolution for ingest and category-based resolution for export.
An ingest converter transforms a source file into Markdown:
interface IngestConverter {
name: string; // e.g., "docx"
extensions: string[]; // e.g., [".docx", ".doc"]
canProcess(input: CanProcessInput): Promise<boolean>;
ingest(input: IngestInput): Promise<IngestOutput>;
}Resolution works in two stages:
- Extension filter — narrow to converters whose
extensionsarray matches the input file - Content inspection — call
canProcess()on each match (in registration order) with the first 1KB of file content
This allows smart detection — for example, .json files are checked for Excalidraw schema before falling back to the generic JSON converter.
Output is either a single Markdown string (markdown) or multiple files (files — used by xlsx for one CSV per sheet), plus optional binary assets and metadata.
An export converter transforms Markdown (or CSV) into a styled output format:
interface ExportConverter {
name: string; // e.g., "document"
category: ExportCategory; // "document" | "spreadsheet" | "presentation" | "transcript"
formats: ExportFormat[]; // Supported output formats
export(input: ExportInput): Promise<ExportOutput>;
}Resolution matches by category, then validates the requested format against the converter's formats array.
The ExportInput includes the resolved theme, so converters apply styling without needing to know about theme loading.
- Create
src/ingest/myformat.ts(orsrc/export/myformat.ts) - Implement the
IngestConverterorExportConverterinterface - Register it in
src/cli/setup-registry.ts— order matters for ingest (specific before generic) - Add tests alongside (e.g.,
src/ingest/myformat.test.ts) - Add test fixtures to
tests/fixtures/
Heavy dependencies (mammoth, exceljs, docx, marp-core) are loaded via dynamic import() inside converter functions. This keeps CLI startup fast — the 62KB bundle loads instantly, and heavy libraries are only pulled in when a specific converter is used.
Export converters receive a fully resolved ResolvedTheme object. Here's how each export uses it:
The document exporter walks the Markdown AST (via unified/remark-parse/remark-gfm) and maps nodes to docx package elements:
// Theme values used:
theme.typography.fontFamily // → font for all text runs
theme.typography.bodySize // → body text size (half-points)
theme.typography.headingSizes // → { 1: 48, 2: 40, ... } per heading level
theme.typography.codeFont // → font for code blocks/inline code
theme.colors.primary // → heading text color
theme.colors.text // → body text color
theme.colors.accent // → table header background, link color
theme.spacing.paragraphAfter // → paragraph spacing (twips)
theme.spacing.headingBefore // → space before headings
theme.spacing.headingAfter // → space after headings
theme.document.margins // → { top, bottom, left, right } in twips
theme.document.header // → header text (supports {title}, {page}, {pages}, {date})
theme.document.footer // → footer textParses CSV/TSV input and creates ExcelJS workbooks:
theme.spreadsheet.headerBackground // → header row fill color (supports $variable)
theme.spreadsheet.headerTextColor // → header row font color
theme.spreadsheet.headerBold // → bold header rowInjects theme settings as Marp frontmatter directives and custom CSS:
theme.presentation.theme // → Marp theme name
theme.presentation.paginate // → slide numbering
theme.presentation.backgroundColor // → slide background
theme.presentation.header // → slide header
theme.presentation.footer // → slide footer
theme.presentation.style // → custom CSS injected into output
theme.colors.primary // → heading color via CSS
theme.typography.fontFamily // → font-family via CSSParses the shared transcript Markdown format and reconstructs subtitle files:
theme.transcript.speakerLabels // → include speaker labels in VTT outputTests live alongside source files (*.test.ts) and in tests/integration/:
npm test # run all tests
npm run test:watch # watch mode
npx vitest run src/ingest/ # run only ingest tests
npx vitest run tests/integration/ # run only integration testsNote: First-run tests for converters that lazy-load heavy dependencies may need extended timeouts (15-30s) due to cold-start module loading in dev containers.
- Fork the repository
- Create a feature branch
- Make your changes with tests
- Ensure
npm run lint,npm run typecheck, andnpm testall pass - Submit a pull request
- CI runs on every push to
mainand on PRs: lint, typecheck, test (with coverage), and build across Node.js 18 and 20 - Release triggers on
v*tags and publishes to npm with provenance
MIT