--- name: docx description: "Create, read, edit Word .docx documents and templates." version: 1.0.0 author: Anthropic (adapted by Nous Research) license: Proprietary. LICENSE.txt has complete terms platforms: [linux, macos, windows] metadata: hermes: tags: [Word, DOCX, Documents, Office, Productivity] category: productivity related_skills: [pdf, xlsx, powerpoint, ocr-and-documents] --- # DOCX Skill Create, read, and edit Word documents — reports, memos, letters, letterheads, tables of contents, tracked changes (redlining), and comments. A `.docx` is a ZIP archive of XML files; this skill covers both the high-level creation path and surgical XML editing. ## When to Use Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx) or Word templates (.dotx). Triggers include: any mention of "Word doc", ".docx", ".dotx", or requests for a "report", "memo", "letter", or similar deliverable as a Word file; extracting or reorganizing content from .docx files; find-and-replace in Word files; inserting images; tracked changes or comments. Do NOT use for PDFs (see the `pdf` skill), spreadsheets (`xlsx`), or presentations (`powerpoint`). ## Prerequisites ```bash npm ls docx --depth=0 2>/dev/null | grep -q docx || npm install docx # creation (docx-js) pip show pandoc >/dev/null 2>&1 || true; which pandoc || sudo apt install -y pandoc # reading which soffice || sudo apt install -y libreoffice # rendering/verification which pdftoppm || sudo apt install -y poppler-utils # PDF → images pip install defusedxml lxml # validation scripts ``` macOS: `brew install pandoc libreoffice poppler`. ## Quick Reference | Task | Approach | |---|---| | **Create** a new document | Write a `docx` (npm) script — see gotchas below | | **Edit** an existing document | `unzip` → edit `word/document.xml` → `zip` (docx-js cannot open existing files) | | **Read** content | `pandoc -t markdown file.docx` (or `read_file`, which auto-extracts .docx text) | > Script paths below are relative to this skill's directory. ## Creating with docx-js — gotchas Write the script and `require('docx')`. The model knows the API; these are the footguns: - **Page size defaults to A4.** For US Letter set `page: { size: { width: 12240, height: 15840 } }` (DXA; 1440 = 1″). - **Landscape:** pass portrait dimensions and `orientation: PageOrientation.LANDSCAPE` — docx-js swaps width/height internally. - **Tables need dual widths:** set `columnWidths` on the table AND `width` on every cell, both in `WidthType.DXA` (PERCENTAGE breaks in Google Docs). Column widths must sum to the table width. - **Table shading:** use `ShadingType.CLEAR`, never `SOLID` (renders black). - **Lists:** never insert `•` literally; use a `numbering` config with `LevelFormat.BULLET`. - **`ImageRun` requires `type:`** (`"png"`, `"jpg"`, …). - **`PageBreak` must be inside a `Paragraph`.** - **Never use `\n`** — use separate `Paragraph` elements. - **TOC:** headings must use built-in `HeadingLevel.*`; custom heading styles need `outlineLevel` set or they won't appear. - **Don't use a table as a horizontal rule** — use a paragraph bottom border instead. - **Dot-leader / right-aligned-on-same-line:** use `PositionalTab` (`alignment: PositionalTabAlignment.RIGHT`, `leader: PositionalTabLeader.DOT`) inside a `TextRun`, not literal `.` or space padding. ## Verify the output After writing a `.docx`, render it and look at it: ```bash python scripts/office/soffice.py --headless --convert-to pdf output.docx pdftoppm -jpeg -r 100 output.pdf page ls page-*.jpg # then inspect each with vision_analyze ``` `pdftoppm` zero-pads page numbers to the width of the page count (`page-01.jpg`…`page-12.jpg`). ## Editing existing documents Legacy `.doc` files must be converted first: `python scripts/office/soffice.py --headless --convert-to docx file.doc`. ```bash unzip -q doc.docx -d unpacked/ find unpacked -type l -delete # strip symlink entries — docx from external parties is untrusted python scripts/merge_runs.py unpacked/ # coalesce fragmented runs so text is findable # edit unpacked/word/document.xml in place — do NOT reformat or pretty-print (cd unpacked && rm -f ../out.docx && zip -Xr ../out.docx .) python scripts/office/validate.py out.docx --original doc.docx # XSD checks; --auto-repair fixes common issues # redlining? add --author "" to check every edit is tracked ``` Word splits text across many `` runs (revision ids, spell-check markers), so a phrase you can see in the document often doesn't exist as a contiguous string in the XML. `merge_runs.py` merges adjacent identically-formatted runs in `word/document.xml` without changing content or rendering; it also accepts a `.docx` directly (`python scripts/merge_runs.py doc.docx -o merged.docx`). **Tracked changes:** when redlining, validate with `--author ""` (needs `--original`) — it reports any text you changed without a ``/`` around it, which is easy to do by accident and invisible in the accepted view. Wrap runs in ``/`` with `w:id`, `w:author`, `w:date` attributes. Inside ``, the text element is ``, not ``. A deleted paragraph mark (``) means "merge this paragraph into the next" — so deleting a paragraph outright is that plus a `` around every run. The `` must come before the rPr's other children; their order is schema-enforced. To produce a clean copy with all tracked changes accepted: `python scripts/accept_changes.py in.docx out.docx`. Accepting a deleted paragraph mark should join that paragraph to the one below it, so a paragraph whose runs are *all* deleted vanishes. Word does this; `accept_changes.py` and `pandoc --track-changes=accept` don't always. Both fail the same way — they strip the deleted text but leave the emptied paragraph behind, which reads as a stray empty bullet when it was auto-numbered: - `pandoc --track-changes=accept` never joins the paragraphs. - `accept_changes.py` (LibreOffice) joins them correctly, except when the deleted paragraph is followed by an empty spacer paragraph. An empty bullet in either view is an artifact of that view, not a defect in the document. Check paragraph deletions in the XML. ## Comments Comments require six cross-linked files. Use the helper — directory mode when you'll also be editing `document.xml` (saves an unzip/rezip cycle), `.docx`-direct mode otherwise: ```bash # Against an already-unpacked directory (preferred when also placing markers) python scripts/comment.py unpacked/ "Fees & expenses cap is too low" python scripts/comment.py unpacked/ "Agreed" --parent 0 # Against a .docx directly python scripts/comment.py contract.docx "This cap is too low" -o annotated.docx ``` The script writes `comments.xml`, `commentsExtended.xml`, `commentsIds.xml`, `commentsExtensible.xml`, the relationships, and the content-type overrides. Comment IDs are auto-assigned. It then prints the ``/``/`` snippet to add to `word/document.xml` so the comment anchors to specific text — until you place those markers, the comment exists but is not visible. ## Pitfalls - Don't round-trip OOXML through `xml.etree.ElementTree` — it rewrites namespace prefixes and corrupts the file. Use `defusedxml.minidom` for scripted transforms. - Zip from INSIDE the unpacked directory (`cd unpacked && zip -Xr ../out.docx .`) and `rm` the target first, or deleted parts survive in the archive. ## Verification 1. `python scripts/office/validate.py out.docx --original in.docx` — schema, relationship, and content-type checks; every failure names its fix. 2. Render to PDF → images (see "Verify the output") and inspect each page with `vision_analyze` — look for broken tables, missing images, spacing artifacts, leftover placeholder text. ## Related skills `pdf` (PDF work), `xlsx` (spreadsheets), `powerpoint` (decks), `ocr-and-documents` (scanned input extraction).