Evals / documents / Convert between formats
Markdown to Word
Convert report.md to a Word document called report.docx.
The task
The reference answer shows the job can be done with deft. It is not an agent run.
deft md to-docx report.md -o report.docx
Input

# Quarterly review Revenue grew in **every** region. See the table and the chart. | Region | Q1 | Q2 | |:--|--:|--:| | North | 120 | 140 | | South | 95 | 130 |  ```python total = sum(values) ```
Reference output

Rendered by macOS Quick Look, first page
Text inside
Quarterly review Revenue grew in every region. See the table and the chart. Region Q1 Q2 North 120 140 South 95 130 total = sum(values)
How a result is judged (2 checks)
- format: file = "report.docx", format = "docx"
- python: code = "import zipfile; z = zipfile.ZipFile('report.docx'); assert any(n.startswith('word/media/') for n in z.namelist()); assert b'Heading1' in z.read('word/document.xml')"
Runs
CodexWithout deftPassed2026-11-07
- Time
- 57s
- New tokens
- 11.5k
- Total input
- 83.6k
- Output tokens
- 2.7k
- Commands
- 5
- Failed
- 0
Trace
I’ll inspect the Markdown and available conversion tools, then create `report.docx` in the current directory.
pwd && rg --files -g 'report.md' -g 'AGENTS.md' -g 'report.docx'output
report.md
sed -n '1,240p' report.md && command -v pandoc || true && command -v libreoffice || true && python3 -c 'import docx; print(\"python-docx available\")' 2>/dev/null || trueoutput
# Quarterly review Revenue grew in **every** region. See the table and the chart. | Region | Q1 | Q2 | |:--|--:|--:| | North | 120 | 140 | | South | 95 | 130 |  ```python total = sum(values) ```
rg --filesoutput
chart.png report.md
python3 - <<'PY' import struct p='chart.png' b=open(p,'rb').read() print(struct.unpack('>II',b[16:24])) PYoutput
(128, 128)
`report.md` contains a heading, bold emphasis, a two-row table, an image reference, and a Python code block. I’m preserving those elements in the Word file.
python3 - <<'PY' from zipfile import ZipFile, ZIP_DEFLATED from pathlib import Path from xml.sax.saxutils import escape out = Path('report.docx') img = Path('chart.png').read_bytes() def run(text, bold=False, font=None, size=None): props=[] if bold: props.append('<w:b/>') if font: props.append(f'<w:rFonts w:ascii=\"{font}\" w:hAnsi=\"{font}\"/>') if size: props.append(f'<w:sz w:val=\"{size}\"/>') rpr=f'<w:rPr>{\"\".join(props)}</w:rPr>' if props else '' return f'<w:r>{rpr}<w:t xml:space=\"preserve\">{escape(text)}</w:t></w:r>' def para(content='', style=None): ppr=f'<w:pPr><w:pStyle w:val=\"{style}\"/></w:pPr>' if style else '' return f'<w:p>{ppr}{content}</w:p>' paragraphs=[] paragraphs.append(para(run('Quarterly review'), 'Heading1')) paragraphs.append(para(run('Revenue grew in ') + run('every', True) + run(' region. See the table and the chart.'))) # Accessible table with grid and cell borders. rows=[['Region','Q1','Q2'],['North','120','140'],['South','95','130']] trs=[] for ri,row in enumerate(rows): cells=[] for ci,val in enumerate(row): align = '<w:jc w:val=\"right\"/>' if ci else '<w:jc w:val=\"left\"/>' celltext=run(val, bold=(ri==0)) cells.append(f'<w:tc><w:tcPr><w:tcW w:w=\"2200\" w:type=\"dxa\"/><w:vAlign w:val=\"center\"/></w:tcPr><w:p><w:pPr>{align}</w:pPr>{celltext}</w:p></w:tc>') trs.append('<w:tr>'+''.join(cells)+'</w:tr>') table='<w:tbl><w:tblPr><w:tblW w:w=\"0\" w:type=\"auto\"/><w:tblBorders><w:top w:val=\"single\" w:sz=\"6\" w:color=\"808080\"/><w:left w:val=\"single\" w:sz=\"6\" w:color=\"808080\"/><w:bottom w:val=\"single\" w:sz=\"6\" w:color=\"808080\"/><w:right w:val=\"single\" w:sz=\"6\" w:color=\"808080\"/><w:insideH w:val=\"single\" w:sz=\"4\" w:color=\"B0B0B0\"/><w:inside … 4571 more charactersoutput
Created report.docx (33265 bytes)
Created [report.docx](report.docx) with the heading, table, chart image, and code block.
Output

Rendered by macOS Quick Look, first page
Text inside
Quarterly review Revenue grew in every region. See the table and the chart. Region Q1 Q2 North 120 140 South 95 130 total = sum(values)
CodexWith deft, skill installedPassedRead the skillUsed deft2026-11-07
- Time
- 13s
- New tokens
- 4.6k
- Total input
- 56.4k
- Output tokens
- 0.4k
- Commands
- 3
- Failed
- 0
Trace
I’ll read the file conversion instructions, then convert `report.md` in the current directory to `report.docx`.
cat .agents/skills/deft/SKILL.md && rg --files -g 'report.md' -g 'AGENTS.md' -g 'report.docx'output
--- name: deft description: "Use deft instead of writing a script for file jobs: PDF merge/split/compress/watermark, image resize/convert/compress, video and audio trim/convert/compress, spreadsheets, Markdown to PDF/HTML, and YouTube downloads and subtitles." --- # deft One local command for file jobs. Prefer it over writing Python or shell scripts. Syntax: `deft <group> <command> [inputs] [-o output] [--options]` Groups: - `pdf`: Merge, split, compress, encrypt, forms, OCR, compare, convert - `image`: Resize, crop, convert, compress, filters, montage, OCR - `video`: Trim, crop, speed, convert, compress, GIF, frames (ffmpeg) - `audio`: Convert, trim, merge, fade, speed, tag, normalize (ffmpeg) - `doc`: Word/ODT/RTF: to text, Markdown, HTML, PDF; find and replace - `slides`: PowerPoint: extract text, Markdown outline, PDF handout - `data`: JSON/YAML/TOML/XML: convert, query with jq syntax, merge, diff - `sheet`: Excel/CSV/ODS tables: filter, sort, join, pivot, convert - `md`: Markdown to PDF, HTML, Word, EPUB; table of contents - `html`: HTML to Markdown, text, Word, PDF; screenshots - `epub`: E-books: info, to text, Markdown and PDF - `email`: EML files: headers, text, attachments - `contact`: VCF address books: to and from CSV - `calendar`: ICS calendars: to and from CSV - `gps`: GPX and KML tracks: convert, distance and time - `youtube`: Info, download, audio, subtitles, thumbnail (yt-dlp) - `file`: Any file: hash, duplicates, batch rename, base64, encoding - `qr`: Cre … 738 more characters
deft md --helpoutput
deft md - commands (input -> output) from-docx doc -> md Convert to Markdown, keeping headings, lists and tables from-html html -> md Convert to Markdown; scripts and styles are dropped from-pdf pdf -> md Text as Markdown paragraphs [beta] to-docx md -> docx Write a Word document with headings, lists, tables, images to-epub md... -> epub Make an e-book; each top-level heading starts a chapter to-html md -> html Render to a styled HTML page, or --fragment to-pdf md -> pdf Typeset Markdown as PDF to-pptx md -> pptx Make a deck: each heading or --- starts a slide [beta] toc md -> md Insert a table of contents from the headings More: deft md <command> --help
deft md to-docx report.md -o report.docxoutput
ok md.to-docx report.md -> report.docx 26 words 0.01s
Converted `report.md` to [report.docx](report.docx).
Output

Rendered by macOS Quick Look, first page
Text inside
Quarterly review Revenue grew in every region. See the table and the chart. Region Q1 Q2 North 120 140 South 95 130 total = sum(values)
CodexWith deft, skill as AGENTS.mdPassed2026-11-05
- Time
- 64s
- New tokens
- 18.9k
- Total input
- 161.7k
- Output tokens
- 3.0k
- Commands
- 9
- Failed
- 2
Trace
I’ll check the available `deft` Markdown conversion options, then create `report.docx` in the current directory.
deft mdoutput
deft md - commands (input -> output) to-html md -> html Render to a styled HTML page, or --fragment to-pdf md -> pdf Typeset a PDF with headings, tables, code, images More: deft md <command> --help
deft find "Markdown to Word docx"output
sheet to-md sheet -> md Markdown table (first row is the header) pdf from-md md -> pdf Typeset a PDF with headings, tables, code, images md to-html md -> html Render to a styled HTML page, or --fragment
rg --files -g 'report.md' -g '*.md' | head -30; command -v pandoc; command -v libreoffice; command -v textutil; command -v python3output
AGENTS.md report.md /usr/bin/textutil /opt/homebrew/bin/python3
textutil -helpoutput
textutil: [command_option] [other_options] file... Command options are (-help is the default): -help show this message and exit -info display information about each file -convert fmt convert each input file to format (txt, rtf, rtfd, html, doc, docx, odt, wordml, or webarchive) -cat fmt concatenate input files into one output file There are some additional optional arguments: -extension ext alternate extension for all output files -output path alternate file name for first output file -stdin read from stdin instead of files -stdout send first output file to stdout -encoding IANA_name|NSStringEncoding encoding used for plain text or html output files (default encoding is UTF-8) -inputencoding IANA_name|NSStringEncoding encoding used to interpret plain text input files (by default encoding will be detected from BOM) -format fmt force input files to be interpreted in this format -font font specify font used for converting plain to rich text -fontsize size specify font size for converting plain to rich text -- specifies that all further arguments are file names -noload do not load subsidiary resources for html files -nostore do not write out subsidiary resources for html files -baseurl url base URL for subsidiary resources in html files -timeout t time in seconds to wait for html resources to load -tex … 989 more charactersdeft md to-html --help; ls -l report.mdoutput
deft md to-html - Render to a styled HTML page, or --fragment Same as: deft convert Usage: deft md to-html <md>... [-o <html|dir>] [options] In: md (each handled separately) Out: html (default <name>.<ext>) Options: --fragment Body only, no <html> page --title <text> Page title (default: first heading) Common: -o --force --dry-run --json -j (deft help common) Examples: deft md to-html notes.md -o notes.html deft md to-html notes.md --fragment -o - -rw-r--r--@ 1 user staff 218 Nov 4 19:31 report.md
deft md to-html report.md -o report.html && textutil -convert docx report.html -output report.docx && rm report.html && ls -l report.docx (exit 1)output
ok md.to-html report.md -> report.html 1KB 0.00s Error reading report.html. Couldn’t communicate with a helper application. ls: report.docx: No such file or directory
cat report.md; python3 -c 'import docx; print(\"python-docx available\")' 2>/dev/null; python3 -c 'import markdown; print(\"markdown available\")' 2>/dev/null; command -v pandoc; command -v lowriter (exit 1)output
# Quarterly review Revenue grew in **every** region. See the table and the chart. | Region | Q1 | Q2 | |:--|--:|--:| | North | 120 | 140 | | South | 95 | 130 |  ```python total = sum(values) ```
ls -l chart.png; python3 - <<'PY' import importlib.util for x in ['docx','markdown','markdown_it','mistune','PIL']: print(x, bool(importlib.util.find_spec(x))) PYoutput
-rw-r--r--@ 1 user staff 30605 Nov 4 19:32 chart.png docx False markdown False markdown_it False mistune False PIL False
`deft` can render the Markdown to HTML, but macOS `textutil` can’t convert that HTML in this environment. I’m building the Word file directly from the report content, including its table, code block, and chart image.
python3 - <<'PY' import re, zipfile, os from pathlib import Path from xml.sax.saxutils import escape out=Path('report.docx') md=Path('report.md').read_text() img=Path('chart.png') def run(text, bold=False, italic=False, mono=False): props=[] if bold: props.append('<w:b/>') if italic: props.append('<w:i/>') if mono: props.append('<w:rFonts w:ascii=\"Courier New\" w:hAnsi=\"Courier New\"/>') p='<w:rPr>'+''.join(props)+'</w:rPr>' if props else '' return '<w:r>'+p+'<w:t xml:space=\"preserve\">'+escape(text)+'</w:t></w:r>' def inline(s): pieces=[] for part in re.split(r'(\\*\\*.*?\\*\\*|"'`[''^`]*`|'"\\*["'^*]+'"\\*)',s): if not part: continue if part.startswith('**') and part.endswith('**'): pieces.append(run(part[2:-2],bold=True)) elif part.startswith('"'`'"') and part.endswith('"'`'"'): pieces.append(run(part[1:-1],mono=True)) elif part.startswith('*') and part.endswith('*'): pieces.append(run(part[1:-1],italic=True)) else: pieces.append(run(part)) return ''.join(pieces) def para(content='', style=None): ppr=f'<w:pPr><w:pStyle w:val=\"{style}\"/></w:pPr>' if style else '' return '<w:p>'+ppr+content+'</w:p>' blocks=[]; lines=md.splitlines(); i=0; has_img=False while i<len(lines): s=lines[i].strip() if not s: i+=1; continue if s.startswith('# '): blocks.append(para(inline(s[2:]),'Title')); i+=1; continue if s.startswith('"'```'"'): i+=1; code=[] while i<len(lines) and not lines[i].startswith('"'```'"'): code.append(lines[i]); i+=1 blocks.append(para(run('\\n'.join(code),mono=True),'Code')); i+=1; continue if s.startswith('|'): rows=[] while i<len(lines) and lines[i].strip().startswith('|'): cells=[c.strip() for c in … 4523 more charactersoutput
Created report.docx (32922 bytes) testing: word/media/chart.png OK No errors detected in compressed data of report.docx.Created [report.docx](report.docx) with the report’s table, code block, and chart image. The DOCX archive passed an integrity check.
Output
Its files were not kept in this run. The last line of the trace is its own report.
Claude Opus 5.5Without deftPassed2026-11-07
- Time
- 42s
- New tokens
- 16.5k
- Total input
- 60.3k
- Output tokens
- 4.3k
- Commands
- 0
- Failed
- 0
Trace
Output

Rendered by macOS Quick Look, first page
Text inside
Quarterly review Revenue grew in every region. See the table and the chart. Region Q1 Q2 North 120 140 South 95 130 Chart total = sum(values)
Claude Opus 5.5With deft, skill installedPassedRead the skillUsed deft2026-11-07
- Time
- 10s
- New tokens
- 9.1k
- Total input
- 43.5k
- Output tokens
- 0.2k
- Commands
- 0
- Failed
- 0
Trace
Output
--- name: deft description: "Use deft first for any simple, direct operation on local files, whatever the type: convert, read, extract, edit, compress, merge, split, compare or inspect. Try it before writing a script or one-liner. If unsure, run deft find; if it says no command does this, use another tool. Not for programming work: to read, write or change source code, skip it." --- # deft One local command for file jobs. Prefer it over writing Python or shell scripts. Syntax: `deft <group> <command> [inputs] [-o output] [--options]` Groups: - `pdf`: Merge, split, compress, encrypt, forms, OCR, compare, convert - `image`: Resize, crop, convert, compress, adjust, montage, OCR - `video`: Trim, merge, convert, compress, subtitles, GIF, frames (ffmpeg) - `audio`: Convert, trim, normalize, denoise, cut silence (ffmpeg) - `doc`: Word/ODT/RTF: to text, Markdown, HTML, PDF; find and replace - `slides`: PowerPoint: extract text, Markdown outline - `sheet`: Excel/CSV/ODS tables: SQL query, pivot, merge, split, convert - `json`: JSON: convert, format, merge, diff, validate - `toml`: TOML: convert, format, merge, diff, validate - `xml`: XML: convert, format, merge, diff, validate - `yaml`: YAML: convert, format, merge, diff, validate - `md`: Markdown to PDF, HTML, Word, EPUB; table of contents - `html`: HTML to Markdown, text, Word and PDF - `epub`: E-books: info, to text, Markdown and PDF - `gps`: GPX and KML tracks: convert, distance and time - `qr`: Create QR codes and barcodes; read QR codes How to use it: 1. Run `deft <group>` to list commands, or `deft find "<what you want>"` to search. If it says `no command does this`, use another tool. 2. Run `deft <group> <command> --help` for options and examples. 3. `deft convert <in> -o <out.ext>` converts between formats by extension. 4. Results are one line starting with `ok`, `partial` or `error`; add `--json` to parse them. Errors include a `hint:` with the fix. Inputs are never overwritten; pass `--force` or `--in-place` to allow it. Globs and directories work as inputs; several inputs run in parallel. If `deft` is not installed, ask the user, then follow https://github.com/firstprayer/deft/blob/main/docs/INSTALL.md

Rendered by macOS Quick Look, first page
Text inside
Quarterly review Revenue grew in every region. See the table and the chart. Region Q1 Q2 North 120 140 South 95 130 total = sum(values)
Claude CodeWithout deftNot run yet
Claude CodeWith deftNot run yet