agentleFS
nb-99nb-99/skillektion/skills/docx/SKILL.md

docx

Skill1 starsChanged 3 months agoMITView on GitHub

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.

  • Installs packages

194 other projects use a version of this.

Adopted by

Most recently taken up by

Used with

Projects with it also use Video Generation, VLM and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • huang-x-hSkill

    Video Generation

    Implement AI-powered video generation capabilities using the z-ai-web-dev-sdk. Use this skill when the user needs to generate videos from text prompts or images, create video content programmatically, or build applications that produce video outputs. Supports asynchronous task management with status polling and result retrieval.

    Also in 6 projects that use this

  • huang-x-hSkill

    VLM

    Implement vision-based AI chat capabilities using the z-ai-web-dev-sdk. Use this skill when the user needs to analyze images, describe visual content, or create applications that combine image understanding with conversational AI. Supports image URLs and base64 encoded images for multimodal interactions.

    Also in 6 projects that use this

  • dav-niu474Skill

    gift-evaluator

    The PRIMARY tool for Spring Festival gift analysis and social interaction generation. Use this skill when users upload photos of gifts (alcohol, tea, supplements, etc.) to inquire about their value, authenticity, or how to respond socially. Integrates visual perception, market valuation, and HTML card generation.

    Also in 5 projects that use this

  • dav-niu474Skill

    Podcast Generate

    Generate podcast episodes from user-provided content or by searching the web for specified topics. If user uploads a text file/article, creates a dual-host dialogue podcast (or single-host upon request). If no content is provided, searches the web for information about the user-specified topic and generates a podcast. Duration scales with content size (3-20 minutes, ~240 chars/min). Uses z-ai-web-dev-sdk for LLM script generation and TTS audio synthesis. Outputs both a podcast script (Markdown) and a complete audio file (WAV).

    Also in 5 projects that use this

  • Liang-MHSkill

    stock_analysis

    获取 A 股、基金和港股的实时行情和技术分析数据

    Also in 5 projects that use this

  • SeifBenayedSkill

    file-reading

    Use this skill when a file has been uploaded but its content is NOT in your context — only its path at /mnt/user-data/uploads/ is listed in an uploaded_files block. This skill is a router: it tells you which tool to use for each file type (pdf, docx, xlsx, csv, json, images, archives, ebooks) so you read the right amount the right way instead of blindly running cat on a binary. Triggers: any mention of /mnt/user-data/uploads/, an uploaded_files section, a file_path tag, or a user asking about an uploaded file you have not yet read. Do NOT use this skill if the file content is already visible in your context inside a documents block — you already have it.

    Also in 5 projects that use this

  • +1 more from huang-x-h
  • +5 more from dav-niu474

The file

We split this skill into the rules it gives an agent

5 rules. 0 are common, 2 are distinctive, and 3 are very specific to this project.

  • 0

    Common

    Most of the 189 edited versions we read keep these.

  • 2

    Distinctive

    This author's own choices. Worth a look.

  • 3

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    All state lives in word/document.xml (plus w:author/w:date):

    line 136
  2. Very specific:

    Every w:id must be unique document-wide.

    line 149
  3. Very specific:

    Verify redlines mechanically, not by eye: pandoc -f docx --track-changes=all -t markdown renders [text]{.insertion ...} / [text]{.deletion …

    line 161
  4. Distinctive:

    Never trust generation code — render and read back:

    line 201
  5. Distinctive:

    Check, then install what's missing:

    line 223

“Common” is measured: kept by at least 6 in 10 of the 189 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

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:

TaskApproach
Create a new documentWrite a Node script with the docx package, Packer.toBuffer to a file
Edit an existing documentUnzip, edit word/document.xml (and friends), rezip from inside the directory
Read the textpandoc -t markdown file.docx (or textutil -convert txt on macOS)
Verify visuallysoffice --headless --convert-to pdf then pdftoppm -png and inspect
Redline / tracked changesEdit XML: wrap runs in w:ins / w:del (see below)
Add a commentscripts/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.

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):

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')". Attribute order never matters; element order often does (see rPr below).
  • Legacy .doc is not .docx. It's an OLE2 compound file; python's zipfile and your XML tooling will fail on it. Convert first with soffice --headless --convert-to docx file.doc, edit the result, and convert back with --convert-to doc if the user needs .doc again.

scripts/merge_runs.py dir/ merges in place on an unpacked directory; scripts/merge_runs.py file.docx -o out.docx on an archive.

Tracked changes (redlining)

All state lives in word/document.xml (plus w:author/w:date):

<w:ins w:id="100" w:author="Reviewer" w:date="2026-09-06T10:00:00Z">
  <w:r><w:t>inserted text</w:t></w:r>
</w:ins>
<w:del w:id="101" w:author="Reviewer" w:date="2026-09-06T10:00:00Z">
  <w:r><w:delText>deleted text</w:delText></w:r>
</w:del>
  • Inside w:del, the text element is w:delText, not w:t — a plain w:t inside w:del renders the text as if never deleted.
  • Every w:id must be unique document-wide. Use one author name consistently; a mix of authors looks like several people edited the file.
  • Deleting a whole paragraph is two operations: wrap every run in w:del, AND mark the paragraph mark itself deleted by adding <w:rPr><w:del w:id="N" .../></w:rPr> inside that paragraph's w:pPr. Without the second part the paragraph mark survives and you get an empty line; with it, Word merges the paragraph with the next one when the change is accepted.
  • Element order inside w:rPr is schema-constrained: in the paragraph-mark rPr (w:pPr > w:rPr), w:ins/w:del come first, before other properties like w:rFonts/w:b. Order violations are tolerated by some readers and rejected by others — keep the schema order.
  • Verify redlines mechanically, not by eye: pandoc -f docx --track-changes=all -t markdown renders [text]{.insertion ...} / [text]{.deletion ...} / paragraph-mark deletions as .paragraph-deletion; then diff --track-changes=accept vs =reject plain-text output to prove each edit flips the right way. As a second opinion, soffice --headless --convert-to docx round-trips the file — LibreOffice re-emits its own w:ins/w:del for anything it understood, and a round-trip that loses a change means your markup was wrong.

Comments

A visible comment is several cross-linked parts, not one:

  • word/comments.xml — the comment bodies (w:comment with w:id, w:author, w:date, w:initials)
  • word/commentsExtended.xml, word/commentsIds.xml, word/commentsExtensible.xml — modern Word state (resolved/resolved status, durable ids); linked by w14:paraId on the comment's paragraphs
  • one relationship per part in word/_rels/document.xml.rels and one <Override> per part in [Content_Types].xml
  • the anchor triplet in word/document.xml — the comment is invisible until this exists: <w:commentRangeStart w:id="0"/> ...commented runs... <w:commentRangeEnd w:id="0"/><w:r><w:rPr><w:rStyle w:val="CommentReference"/> </w:rPr><w:commentReference w:id="0"/></w:r>

scripts/add_comment.py does all the parts bookkeeping (id allocation, paraIds, threading via --parent, relationships, content types) and either inserts the anchor itself (--insert, when the target phrase sits inside a single run) or prints the exact anchor XML plus offsets to place by hand. For a phrase that spans runs, run scripts/merge_runs.py first.

pandoc shows comments ONLY with --track-changes=all ([text]{.comment-start id="0" author="..." date="..."}anchored text []{.comment-end id="0"}); with the default accept-changes they vanish silently. LibreOffice renders comments in the UI but headless PDF export omits them unless you enable annotation export.

Verify the output

Never trust generation code — render and read back:

# render to images (best proxy for "what Word will show")
soffice --headless --convert-to pdf file.docx
pdftoppm -png -r 100 file.pdf page            # then inspect page-*.png

# independent text read-back
pandoc -t plain file.docx                      # cross-platform
textutil -convert txt file.docx -output -      # macOS built-in

# structural check (every edited part)
python3 -c "from xml.etree import ElementTree as ET; ET.parse('dir/word/document.xml')"

Assertions worth scripting per task: the table's gridCol/tcW widths sum to the table width; each redline edit appears inside a w:ins/w:del (and w:delText inside w:del); comment anchors sit inside the same paragraph as their target text; page count matches expectation via pdfinfo.

Dependencies

Check, then install what's missing:

  • node + docx: npm install docx in a scratch directory (v9.x assumed above).
  • pandoc: brew install pandoc or apt install pandoc.
  • LibreOffice (soffice): brew install --cask libreoffice or apt install libreoffice. On some distros the binary is versioned, e.g. libreoffice25.2.
  • poppler (pdftoppm, pdftotext, pdfinfo): brew install poppler or apt install poppler-utils.
  • zip/unzip/zipinfo: system defaults. textutil: macOS built-in.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.