Skip to content
Agentmart
Submit a tool

docx

Create, edit, redline, and verify Word .docx files.

Skill by nb-99

IndexedMIT★ 1
Write a review

Install

Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.

npx agentmarket install skill/nb-99-docx

Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

Create, edit, redline, and verify Word .docx files. Use when generating documents with the docx npm package, editing a .docx's XML directly, adding tracked changes (redlining) or comments, or verifying rendered output. Not for .odt, .rtf, .pages, or PDF files.

Use cases: documents, writing

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

Permissions

What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.

Tools
No tools found
Secrets
None asked for
Install scripts
None found
Runs as
Python package

Network hosts it talks to 1

  • codeload.github.com · found in install command

Files it touches

  • ~/.claude/skills/docx

What's inside

Skill name
docx
Description
Create, edit, redline, and verify Word .docx files. Use when generating documents with the docx npm package, editing a .docx's XML directly, adding tracked changes (redlining) or comments, or verifying rendered output. Not for .odt, .rtf, .pages, or PDF files.
License
MIT
Instructions
230 lines in SKILL.md · view on GitHub

Before installing

  • Ships executable scripts

Files (4)

  • SKILL.md
  • openai.yaml · agents/
  • add_comment.py · scripts/
  • merge_runs.py · scripts/

SKILL.md (first lines)

# Word documents (.docx)

A .docx is a zip of XML parts. Generating from scratch is easiest through the
`docx` npm package; changing an existing file is usually surgical XML editing
of its unpacked parts. Pick the row that matches the task:

| Task | Approach |
| --- | --- |
| Create a new document | Write a Node script with the `docx` package, `Packer.toBuffer` to a file |
| Edit an existing document | Unzip, edit `word/document.xml` (and friends), rezip from inside the directory |
| Read the text | `pandoc -t markdown file.docx` (or `textutil -convert txt` on macOS) |
| Verify visually | `soffice --headless --convert-to pdf` then `pdftoppm -png` and inspect |
| Redline / tracked changes | Edit XML: wrap runs in `w:ins` / `w:del` (see below) |
| Add a comment | `scripts/add_comment.py` to create the parts and print/insert the anchor |

## Generating with the `docx` npm package

Everything below is verified against docx 9.x behavior; treat older versions
with suspicion.

```js
const { Document, Packer, Paragraph, TextRun, HeadingLevel, Table, TableRow,
        TableCell, WidthType, ShadingType, AlignmentType, LevelFormat,
        TableOfContents, ImageRun, PageBreak, PositionalTab, PositionalTabAlignment,
        PositionalTabLeader, PositionalTabRelativeTo, PageOrientation } = require("docx");

const doc = new Document({
  numbering: { config: [{ reference: "bullets",
    levels: [{ level: 0, format: LevelFormat.BULLET, text: "\u2022",
               style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] }] },
  sections: [{
    properties: { page: { size: { width: 12240, height: 15840 } } },  // US Letter
    children: [ /* Paragraphs and Tables */ ],
  }],
});
Packer.toBuffer(doc).then((buf) => fs.writeFileSync("out.docx", buf));
```

Dimensions are DXA: **1440 DXA = 1 inch**. The default page is A4
(11906 x 16838); for US Letter pass `width: 12240, height: 15840` explicitly.
Landscape keeps the same two numbers swapped plus
`orientation: PageOrientation.LANDSCAPE` — the library writes both the
swapped `w:pgSz` values and `w:orient="landscape"`, so don't pre-swap them
yourself.

Hard-won gotchas:

- **Tables need widths twice**: `columnWidths: [dxa, ...]` on the Table AND
  `width: { size: dxa, type: WidthType.DXA }` on every TableCell. DXA only —
  percentage widths resolve against different bases in Word, LibreOffice, and
  pandoc, and tables come out squashed or sprawling somewhere.
- **Cell shading is `ShadingType.CLEAR`, never SOLID.** SOLID darkens the
  fill; CLEAR (the Word default `w:shd w:val="clear"`) renders the plain fill
  color you chose.
- **Bullets come from a numbering config** (`numbering.config` + a Paragraph
  with `numbering: { reference, level }`). Never type a literal "•" into a
  run — it loses list semantics, indentation, and hanging indents.
- **`ImageRun` needs an explicit `type: "png"` (or jpeg/gif/svg).** Omit it
  and docx 9.x silently stores the media part as `image.undefined` with no
  content type; LibreOffice tolerates that, Word offers to "repair" the file.
- **`PageBreak` must sit inside a Paragraph's children**, not bare in a
  section. A break not wrapped in a paragraph is dropped or corrupts output.
- **No `\n` inside a TextRun.** It is written to the XML as a literal
  backslash-n and shows up in the document as that text. One paragraph per
  line, or `new TextRun({ break: 1 })` for a line break within a paragraph.
- **`TableOfContents` writes an empty field marked dirty** (`TOC \h \o "1-2"`
  with `w:dirty="true"`). It renders empty everywhere until a field update:
  Word populates it on open/print, LibreOffice via Tools > Update > Indexes —
  `soffice --headless --convert-to pdf` does NOT populate it, so don't panic
  when the PDF shows no TOC.
- **TOC and pandoc headings need heading styles that resolve.** Give body
  headings `heading: HeadingLevel.HEADING_1` (or `outlineLevel`). pandoc
  recognizes headings through the styles part; docx-js emits
  `<w:basedOn w:val="Normal"/>` on every style but never *defines* a `Normal`
  style, and pandoc's style resolution then fails, demoting every heading to
  a plain paragraph in the pandoc read-back. Fix by injecting one style into
  `word/styles.xml` before (or after) generation:
  `<w:style w:type="paragraph" w:styleId="Normal"><w:name w:val="Normal"/><w:qFormat/></w:style>`
- **Dot leaders are positional tabs**, not padded dots: `new PositionalTab({
  alignment: PositionalTabAlignment.RIGHT, leader: PositionalTabLeader.DOT,
  relativeTo: PositionalTabRelativeTo.MARGIN })` inside the paragraph
  produces `<w:ptab w:leader="dot"/>` — real right-aligned dot leaders that
  survive font changes.
- **Namespaces in output are heavy but harmless.** docx-js declares a dozen
  namespaces and drops an empty self-closing `<w:comments .../>` scaffold
  into the package. Word and LibreOffice ignore both.

## Editing an existing .docx

Unzip to a directory and edit the XML parts by hand (or with a script):

```sh
mkdir dir && unzip -o file.docx -d dir      # word/document.xml is the body
# ... edit ...
cd dir && zip -Xr ../out.docx .             # rezip FROM INSIDE the directory
```

- **Check for symlink entries in untrusted files first.** `zipinfo
  file.docx` prints a mode column; any entry starting with `l` (e.g.
  `lrwxrwxrwx`) is a symlink, and command-line `unzip` will happily create
  it (pointing wherever the archive wants, e.g. `/etc/passwd` when you later
  read or follow it). Python's `zipfile` is safe — it writes symlink entries
  as regular files — so for anything you didn't generate yourself, either
  extract with python or filter out `l`-mode entries.
- **Rezip from inside the directory** (`cd dir && zip -Xr ../out.docx .`).
  Zipping with paths (`zip -r out.docx dir/`) nests a `dir/` prefix inside
  the archive and Word can't find the parts. `-X` strips filesystem extra
  fields; unlike odt/epub there is no stored-first entry requirement, so any
  entry order works.
- **Visible phrases are usually fragmented XML.** Word splits text across
  many `<w:r>` runs (spellcheck markers, rsid revision ids, IME input), so
  "the quick brown fox" is rarely one `<w:t>` — you often can't wrap it in a
  `w:ins`, delete it, or anchor a comment to it directly. Run
  `scripts/merge_runs.py` to coalesce adjacent same-format runs (it never
  changes text or formatting), then re-read.
- **`w:rsid*` attributes are inert session bookkeeping** — safe to ignore
  and safe to keep; don't try to "clean" them while editing.
- **Validate after every edit**: `python3 -c "from
  xml.etree import ElementTree; ElementTree.parse('dir/word/document.xml')"`.

From nb-99/skillektion, MIT.

More from nb-99/skillektion

Signals

Stars
1
Forks
0
Open issues
1
Last commit (this folder)
Sep 6, 2026 (1 month ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from nb-99

Cite this page

docx skill. Agent Mart. https://agentmart.dev/skills/nb-99-docx (last updated 2026-10-24)