docx
Skill1 starsChanged 6 months agoApache-2.0View on GitHub
Use when the user asks to create, edit, convert, validate, preview, or password-protect Microsoft Word .docx documents. Triggers include "markdown to docx", "docx to markdown", "fill Word template", "accept tracked changes", "validate docx", "preview docx as image", "encrypt/decrypt docx", and related .docx round-trip or template-fill tasks.
- Installs packages
194 other projects use a version of this.
Adopted by
Most recently taken up by
- CloudChef/atlasclaw-providerstheir own version, first seen Nov 8, 2026
- holon-run/holontheir own version, first seen Nov 8, 2026
- rvdbreemen/OTGW-firmwaretheir own version, first seen Nov 8, 2026
- BrightGold70/skillstheir own version, first seen Nov 8, 2026
- ashleysbrain/skillstheir own version, first seen Nov 8, 2026
- EvanLee2004/finance-skillstheir own version, first seen Nov 8, 2026
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-hSkillVideo 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-hSkillVLM
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-niu474Skillgift-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-niu474SkillPodcast 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-MHSkillstock_analysis
获取 A 股、基金和港股的实时行情和技术分析数据
Also in 5 projects that use this
SeifBenayedSkillfile-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
Other versions194 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 194
- anthropics/skills★ 189ksame name
- spinabot/brigade★ 11ksame name
- netease-youdao/LobsterAI★ 6.2ksame name
- OpenBMB/PilotDeck★ 5ksame name
- nextlevelbuilder/goclaw★ 3.7ksame name
- jjyaoao/HelloAgents★ 3.2ksame name
- anthropics/claude-agent-sdk-demos★ 2.8ksame name
- K-Dense-AI/claude-scientific-writer★ 2.6ksame name
- OpenCoworkAI/open-cowork★ 2.2ksame name
- ChenglinPoly/infiAgent★ 1.2ksame name
184 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository23 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- brainstormingSkill
- design-mdSkill
- hooks-creatorSkill
- htmlSkill
- marp-slideSkill
- mcp-builderSkill
- obsidian-cliSkill
- pdfSkill
- post-writingSkill
- pptxSkill
- skill-auto-improveSkill
- skill-creatorSkill
- skill-enhancerSkill
- skill-validatorSkill
- summarizing-meetingsSkill
- text-humanizerSkill
- transcript-fetcherSkill
- vdd-adversarialSkill
- vdd-sarcasticSkill
- wiki-ingestSkill
- xlsxSkill
The file
We split this skill into the rules it gives an agent
11 rules. 0 are common, 5 are distinctive, and 6 are very specific to this project.
0
Common
Most of the 189 edited versions we read keep these.
5
Distinctive
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 74
All scripts above accept --json-errors to emit failures as a single line of JSON on stderr ({v, error, code, type?, details?}).
- Distinctive: line 79
Dry-run support: not applicable; operations are file-to-file and reversible by re-running.
- Very specific: line 82
Allowed scope: only the input/output paths named on the command line and, for Python scripts, modules under scripts/.
- Distinctive: line 84
Default exclusions: never overwrite the input in place unless the same path is also passed as output.
- Distinctive: line 105
Use graduated language: steps marked MUST are safety-critical.
- Distinctive: line 110
Look up the task in §10 Quick Reference first: If a script exists, run it — it captures accumulated OOXML knowledge that inline code tends …
- Very specific: line 115
MUST: run bash scripts/install.sh once.
- Very specific: line 123
Prefer md2docx.js over hand-writing new Document({...}).
- Very specific: line 129
Use docx2md.js for GFM output with tables preserved.
- Very specific: line 138
MUST: ensure the template contains placeholders in the exact form {{key}} or {{nested.key}}.
- Distinctive: line 157
Create a new document from Markdown:
“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.
Files
docx skill
Purpose: Give the agent a deterministic, script-first way to create,
edit, and convert Microsoft Word .docx files so it does not have to
re-derive low-level OOXML constructs (page sizes, tables, tracked
changes, comment markers, image embeddings, template substitution) on
every task. Writing .docx by hand through docx-js or python-docx
calls is feasible but error-prone; the scripts here encode the
practical knowledge and make the common operations a single command.
1. Red Flags (Anti-Rationalization)
STOP and READ THIS if you are thinking:
- "I'll just assemble the docx inline with
docx-jscalls, the scripts are overkill." → WRONG.md2docx.jsalready handles page size, numbering, dual-width tables, and image alt-text — regressions from hand-rolled code usually show as silently broken tables or images on Word for Windows. - "I can skip
docx_fill_template.py's run-merge pass and just regex-replace{{placeholder}}." → WRONG. Word and Google Docs fracture a single placeholder across multiple<w:r>siblings after spell-check or autocorrect; a naïve regex won't match and the placeholder will ship unfilled. - "For tracked changes I'll just delete every
<w:del>block." → WRONG. That loses author attribution and breaks paragraph-level delete markers. Usedocx_accept_changes.pywhich drives LibreOffice's own accept dispatcher. - "The smart-quote entity rewrite in
office/unpack.pyis ugly, I'll turn it off." → WRONG. Different Word builds round-trip UTF-8 quotes inconsistently. Keeping them as’etc. on unpack and reversing on pack makes edits deterministic. - "I'll just regex-replace
\|in OOXML to extract or modify text." → WRONG. Raw XML regex operates on serialised bytes and breaks namespace prefixes, entity encoding, and split-run anchors. Usedocx_replace.py(anchor-and-action surgical edit) which works at the lxml element level after run canonicalisation. - "It's an Obsidian note but it's still Markdown — I'll just run it through
md2docx.js." → WRONG.md2docx.jsexits 0 and writes a valid.docxthat is missing content. Measured on a real vault note: the YAML frontmatter is stripped unconditionally (soparticipants, which live only there, vanish),![[embed]]and[[wikilink]]reachmarkedastexttokens and land in the document as literal![[…]]strings, and a bare space in an image path also lexes as text — every vault path has spaces. Exit 0 guarantees nothing here. Usemd2docx.js --obsidian(orobsidian2md.jsfirst). - "The note has no
![[embeds]], so plainmd2docx.jsis fine." → WRONG if it has frontmatter. D1 fires on every note: the frontmatter is dropped silently and metadata that lives nowhere else goes with it. - "I need to edit a
.docxand thedocx2md → md2docxround-trip loses styling." → Usedocx_replace.py --replaceinstead. It operates at run level, preserving bold/italic/colour/numbering. Round-trip through markdown is lossy: inline formatting, list numbering schemas, and table borders all flatten. - "The doc has
$x^2$in it,md2docx.jswill just write it as text and that's fine." → WRONG before TASK 031.$…$/$$…$$used to reach the document as literal characters, exit 0, no warning — the same silent-loss class as the Obsidian defects above. As of TASK 031, math is on by default: it converts to a native OOXML<m:oMath>object, editable in Word like any equation authored with the equation editor. Only--no-mathrestores the old literal-text behaviour.
2. Capabilities
- Surgical anchor-and-action edit of a live
.docx(no template required) viadocx_replace.py— find a text anchor and--replace TEXT/--insert-after PATH/--delete-paragraphwithout a lossydocx2md → md2docxround-trip. Run-level formatting (bold, italic, colour, numbering) is preserved.--insert-afterautomatically relocates images, charts, OLE objects, SmartArt diagrams, and numbered/bulleted lists from the MD source into the base document (docx-6.5 + docx-6.6 v2 relocators, 2026-05-12). Honest scope:--replacerequires the anchor to fit within a single<w:t>after run-merge (cross-run anchors not supported, R10.a);--insert-afterand--delete-paragraphwork at paragraph granularity. Anchor not found → exit 2AnchorNotFound; last-paragraph delete refused → exit 2LastParagraphCannotBeDeleted. - Convert Markdown to
.docxwith headings, lists, tables, images, and optional mermaid diagrams viamd2docx.js. - Render inline
$…$and display$$…$$math as native OOXML<m:oMath>objects viamd2docx.js— on by default, editable in Word like an equation authored with its own equation editor, not a raster image. Pipeline: KaTeX renders TeX to MathML in-process,mathml2ommlconverts to OMML, spliced into the paragraph as a fresh object per occurrence. A standalone$$…$$alone between blank lines (or the sole content of a table cell) gets a dedicated centred paragraph; a 4-column table whose 1st/3rd cells are blank — the Pandoc| | $$…$$ | | (N) |equation-with-number convention — gets asymmetric column widths so the formula isn't clipped to 1/4 of the page width. A formula that fails to render degrades to its literal source text plus a stderr warning (never a silent drop);--strict-mathturns that into a hard exit 1 instead.--no-mathrestores pre-TASK-031 literal-text behaviour exactly. Honest scope: no Word-native automatic equation numbering (a source document's own numbers, sitting outside the$$…$$delimiters, convert unchanged); math inside**bold**/*italic*/a link still renders as a real object but without that styling; math in an image's alt text renders as literal text (theImageRun.altTextfield has nowhere to splice a run into). - Convert an Obsidian vault note to
.docxin one command viamd2docx.js --obsidian [--vault-root DIR], or to clean CommonMark viaobsidian2md.js(both share_obsidian_lib.js). Closes four measured silent-loss defects: YAML frontmatter dropped,![[embed]]and[[wikilink]]unrecognised, and spaces in image paths. Frontmatter is re-emitted as a two-column table by default (--frontmatter table|render|strip) — a GFM table, so it goes through the sametabletoken path as every other table and carries the identical borders and header shading. Attachments resolve in Obsidian's own order (.obsidian/app.json→ note directory → literal path → NFC-folded vault-wide index); callouts (> [!warning] Title),==highlight==,%%comment%%,#inline-tags, and- [ ]task boxes are normalised; fenced/indented/inline code is masked and passes through byte-identical, so a note documenting Obsidian syntax is not rewritten. Opt-in--transcludeinlines![[Note]]with cycle protection (depth ≤ 3, visited-set) and confines the target to the vault — inlining text from outside it would be an arbitrary-file read driven by a note somebody else wrote. Missing attachment → placeholder + stderr warning (exit 0), or exit 8AssetNotFoundunder--strict-assets. Honest scope: math ($…$) still reaches the document as text (backlogdocx-11); a|300width hint is an upper bound and never upscales, unlike Obsidian itself;.webpis named as a file rather than embedded, because the docx image layer cannot carry it; and an indented code block nested inside a list is treated as list content, so Obsidian syntax inside one is still rewritten. - Convert HTML / web archives to
.docxviahtml2docx.js— accepts.html/.htm, Chrome.mhtml/.mht, and Safari.webarchive(sub-resources extracted to a temp dir). Confluence, GitLab wiki, and CMS chrome (ARIA-role-tagged headers/sidebars/footers, namespaced<ac:*>macros,tablesorter<button>wrappers) is stripped automatically before walking. Inline SVG diagrams (drawio/mermaid/PlantUML) are rastered to PNG via a two-tier renderer: Tier 1 = headless Chrome / Chromium / Edge / Brave (auto-detected from conventional install paths orHTML2DOCX_BROWSER) gives pixel-perfect CSS layout including foreignObject labels and word-wrap; Tier 2 =@resvg/resvg-jsfallback for hosts without a browser, with foreignObject → SVG<text>conversion that preserves drawio's centring math, synthesises a viewBox on canvas-clipped diagrams (5% expansion absorbs drawio's edge-overshoot artifact), and word-wraps long Cyrillic/Latin labels at the wrapper'swidth:Npxso text fits inside its box.--reader-modeopt-in: stripped CMS chrome via a curated candidate list (#main-content,.entry,.post-content,article) with per-candidate min-text filter — useful for browser-saved news / blog pages where Confluence-priority selectors fall through to<body>. - Extract
.docxcontent back to Markdown preserving tables, lists, and embedded images viadocx2md.js. Comments and tracked changes (which mammoth strips) are pulled into a JSON sidecar (<output>.docx2md.json) — useful for contract audits where the audit trail must accompany the converted markdown. Footnotes/endnotes are converted to pandoc-style[^fn-N]/[^en-N]markers with definitions appended at the end. Text Word hides (<w:vanish/>, set directly or through a character/paragraph style) is dropped, because Word neither shows nor prints it;--keep-hidden-textkeeps it. Schema versioned (v: 1); the sidecar'sunsupportedfield reports counts of revision types not yet captured (rPrChange,pPrChange,moveFrom,moveTo,cellIns,cellDel) so callers know what was lost. Opt-out via--no-metadata(skip sidecar) and--no-footnotes(skip pandoc conversion). Sidecar is not written when the source has no comments, no revisions, and zero unsupported counts — clean docs stay clean. - Fill
.docxtemplates containing{{placeholder}}or{{nested.key}}markers from a JSON payload with safe run-merging. - Accept all tracked changes in a
.docxvia headless LibreOffice without leaving artefacts in the user's profile. - Unpack and repack
.docxarchives for raw OOXML editing, with smart-quote entity round-tripping and run canonicalisation. - Structurally validate a
.docx: relationships, content types, tracked-change/<w:delText>integrity, comment marker pairing, package-layout allow-list (every ZIP entry must live under[Content_Types].xml,_rels/,word/,docProps/, orcustomXml/per ECMA-376 §11.3.10 — catches scratch-file leaks that Word refuses to open while LibreOffice silently tolerates), and optional XSD binding. With--strict, the package-layout warning is promoted to exit 1. - Reject password-protected and legacy
.doc(CFB-container) inputs early with a clear remediation message (exit 3) instead of aBadZipFiletraceback. - Detect macro-enabled inputs (
.docm, withvbaProject.bin) and warn when the chosen output extension would silently drop the macros (docm→docx). - Render any
.docx/.docm/.pdf(or peer-skill.xlsx/.pptx) into a single PNG-grid preview viapreview.py(LibreOffice + Poppler). - Emit failures as machine-readable JSON to stderr with
--json-errors(uniform across all four office skills). - Set or remove a password on a
.docx/.xlsx/.pptx(MS-OFB Agile, Office 2010+) viaoffice_passwd.py— three modes:--encrypt PASSWORD,--decrypt PASSWORD,--check(exit 0 encrypted / 10 clean / 11 missing). - Insert a Word review comment anchored on a text substring via
docx_add_comment.py— wires<w:commentRangeStart>/<w:commentRangeEnd>/<w:commentReference>markers, appends toword/comments.xml, and patches[Content_Types].xml+ relationships. Supports threaded replies (--parent N) via<w15:commentEx w15:paraIdParent=…>incommentsExtended.xml; reply-to-reply chains are flattened to the conversation root to match Word's review-pane render. Multi-paragraph bodies via\nin--commentare split into separate<w:p>per ECMA-376 §17.13.4.2. Opt-in library mode (--unpacked-dir DIR) operates in-place on an already-unpacked tree (skips unpack/pack/encryption-check) — not reentrant: do not run two processes against the same tree concurrently, no file locking. Malformed OOXML side-parts surface asMalformedOOXMLenvelope (not a traceback). Seereferences/add-comment-howto.mdfor verification steps and §6 troubleshooting for failure modes. - Merge N
.docxfiles into one viadocx_merge.py(VDD iter-2 real-world hardened). Appends body content + styles + numbering + media + relationships into the first input (base), with full reference relocation: image rIds renumbered,r:embed/r:link/r:idin body remapped,<w:bookmarkStart/End w:id>bumped past base's max,<w:abstractNum>/<w:num>shifted with<w:numId w:val>body refs rewritten, missingDefault Extensionentries pulled into[Content_Types].xml. Strips paragraph-level<w:sectPr>from extras (their header/footer references would dangle). Inserts new<w:abstractNum>BEFORE first<w:num>per ECMA-376 §17.9.20 schema-order. Honest scope (still not merged, warned when extras have content): footnotes / endnotes / headers / footers / comments.
3. Execution Mode
- Mode:
script-first. - Why this mode: Each operation is a small, deterministic CLI wrapping a specific OOXML-aware library. Inline assembly in the agent's text is slower and regresses on details (page size, DXA units, namespace ordering). Scripts also make the skill reusable from any shell environment.
4. Script Contract
- Commands:
node scripts/md2docx.js INPUT.md OUTPUT.docx [--header "TEXT"] [--footer "TEXT"] [--page-size A4|Letter] [--landscape] [--margins T,R,B,L] [--no-math] [--strict-math]— math ($…$/$$…$$→ native<m:oMath>) is on by default;--no-mathdisables it entirely (literal text, pre-TASK-031 behaviour);--strict-mathexits 1 on the first formula KaTeX cannot render instead of the default degrade-to-literal-text-plus-warning.node scripts/md2docx.js INPUT.md OUTPUT.docx --obsidian [--vault-root DIR] [--frontmatter table|render|strip] [--lang ru|en|auto] [--links text|italic] [--inline-tags strip|keep] [--transclude] [--strict-assets]— the one-command Obsidian route.--vault-rootdefaults to the nearest ancestor of the note containing.obsidian/, else the note's own directory. Every Obsidian flag requires--obsidian; using one without it exits 1 with--vault-root requires --obsidian(exit 1 ismd2docx.js's code for every usage error, unchanged since TASK 019). Flag order is irrelevant.--strict-assetsexits 8, in plain text —md2docx.jshas no--json-errors.node scripts/obsidian2md.js INPUT.md OUTPUT.md [--vault-root DIR] [--frontmatter table|render|strip] [--lang ru|en|auto] [--links text|italic] [--inline-tags strip|keep] [--transclude] [--strict-assets] [--json-errors]— Obsidian-flavoured Markdown → CommonMark, no OOXML involved. Defaults:--frontmatter table,--lang auto(reads the note'slang:key, elseen),--links text,--inline-tags strip. Exit codes: 0 success, 1 I/O, 2 invalid argument / usage, 6 same-path self-overwrite refused (SelfOverwriteRefused), 8 attachment not found under--strict-assets(AssetNotFound).--json-errorsemits the standard{v:1, error, code, type, details}envelope on every non-zero path. Output is idempotent: a second run over the same input is byte-identical. A CommonMark document with no Obsidian syntax passes through byte-identical.node scripts/docx2md.js INPUT.docx OUTPUT.md [--metadata-json PATH] [--no-metadata] [--no-footnotes] [--keep-hidden-text] [--json-errors]node scripts/html2docx.js INPUT OUTPUT.docx [--header "TEXT"] [--footer "TEXT"] [--reader-mode] [--json-errors]— INPUT may be.html/.htm,.mhtml/.mht, or.webarchive; sub-resources in archives are extracted to a temp dir automatically (cleaned up on exit incl. SIGINT/SIGTERM).--reader-modeswaps the article-root candidate list for a CMS/blog-specific one (#main-content/.entry/.post-content/articlewith per-candidate min-text filter; bare<main>deliberately omitted as it wraps whole-site chrome on news sites). Override SVG renderer withHTML2DOCX_BROWSER=/path/to/chromeor set it to a non-existent path to force the resvg-js fallback for CI determinism. SetHTML2DOCX_ALLOW_NO_SANDBOX=1only inside a trusted CI container — default leaves Chrome's sandbox enabled.python3 scripts/docx_fill_template.py TEMPLATE.docx DATA.json OUTPUT.docx [--strict]python3 scripts/docx_accept_changes.py INPUT.docx OUTPUT.docx [--timeout 120]python3 scripts/office/unpack.py INPUT.docx OUTDIR/ [--no-pretty] [--no-escape-quotes] [--no-merge-runs]python3 scripts/office/pack.py INDIR/ OUTPUT.docx [--no-unescape-quotes] [--no-condense]python3 scripts/office/validate.py INPUT.docx [--strict] [--json] [--schemas-dir PATH] [--compare-to ORIGINAL.docx]python3 scripts/preview.py INPUT OUTPUT.jpg [--cols 3] [--dpi 110] [--gap 12] [--padding 24] [--label-font-size 14] [--soffice-timeout 240] [--pdftoppm-timeout 60]python3 scripts/office_passwd.py INPUT [OUTPUT] (--encrypt PASSWORD | --decrypt PASSWORD | --check)— pass-as PASSWORD to read it from stdin.python3 scripts/docx_add_comment.py INPUT.docx OUTPUT.docx --anchor-text TEXT --comment BODY [--author NAME] [--initials AB] [--date ISO] [--all]python3 scripts/docx_add_comment.py INPUT.docx OUTPUT.docx --parent N --comment BODY [--author NAME]— reply to comment N (inherits its anchor range; threads viacommentsExtended.xml).python3 scripts/docx_add_comment.py --unpacked-dir DIR --anchor-text TEXT --comment BODY [...]— library mode: edit an already-unpacked tree in-place (combine with--parentto add a reply over the same tree).python3 scripts/docx_replace.py INPUT.docx OUTPUT.docx --anchor TEXT (--replace TEXT | --insert-after PATH_OR_DASH | --delete-paragraph) [--all] [--unpacked-dir DIR] [--scope=LIST] [--json-errors]— Tier B (script-first); positional INPUT OUTPUT;--anchor TEXTis always required; exactly one of--replace/--insert-after/--delete-paragraphis required (mutex).--allreplaces/acts on every match instead of first.--unpacked-dir DIRoperates on an already-unpacked tree in-place (library mode — skips unpack/pack/encryption-check).--insert-after -reads the markdown body from stdin.--scope=LIST(docx-6.7) restricts anchor-search to a subset of OOXML parts: comma-separatedbody,headers,footers,footnotes,endnotes,all(default:all). Example:--scope=bodylimits edits toword/document.xml, leaving header/footer boilerplate untouched. Order within the requested set is deterministic (document → headers → footers → footnotes → endnotes). Exit codes: 0 success, 1 I/O or OOXML error, 2 anchor-not-found / last-paragraph-delete refused / invalid--scopevalue, 3 encrypted/password-protected input, 6 same-path self-overwrite refused, 7 post-validate failure.--json-errorsemits failures as{v:1, error, code, type, details}JSON on stderr (cross-5 envelope parity). Honest scope:--replaceanchor must fit within a single<w:t>after run-merge;--insert-afterconverts markdown to OOXML viamd2docx.js(requires Node.js in PATH).python3 scripts/docx_merge.py OUTPUT.docx INPUT1.docx INPUT2.docx [...] [--page-break-between] [--no-merge-styles]- All scripts above accept
--json-errorsto emit failures as a single line of JSON on stderr ({v, error, code, type?, details?}). The schema versionvis currently1; argparse usage errors are routed through the same envelope (type:"UsageError").
- Inputs: positional paths only; optional flags per command.
- Outputs: a single file at the named output path;
office/unpack.pyproduces a directory tree;office/validate.pyprints a report (or JSON with--json).docx2md.jsadditionally creates<stem>_images/next to the Markdown output when the document has embedded images. - Failure semantics: non-zero exit on missing input, invalid JSON, unresolved placeholders (with
--strict), unreadable ZIP, or soffice errors. Error detail goes to stderr. - Idempotency: repeated runs with the same inputs produce equivalent output files (byte-exact for the XML parts after pretty-print and entity normalisation).
docx_accept_changes.pyis idempotent on an already-accepted document. Exception:office_passwd.py --encryptis intentionally non-deterministic — Office encryption uses a fresh random salt per run, so the encrypted bytes differ each time even with the same password. The decrypted output, however, is byte-equal to the pre-encryption input (lossless round-trip). - Dry-run support: not applicable; operations are file-to-file and reversible by re-running.
5. Safety Boundaries
- Allowed scope: only the input/output paths named on the command line and, for Python scripts, modules under
scripts/. Never modify files the user did not name. - Read scope,
--obsidian/obsidian2md.jsonly (containment is compared onrealpath, not on path strings — a directory symlink shipped inside a vault walks straight through a prefix check): resolving an Obsidian note additionally reads (never writes) files the command line did not name — attachments under the vault root,<vault>/.obsidian/app.json, and, under--transclude, sibling notes. The vault root is--vault-root, else the nearest ancestor of the note containing.obsidian/, else the note's directory..obsidian/,.git/,node_modules/and.trash/are never walked, directory symlinks are never followed, and the index is built once per run.attachmentFolderPathis untrusted input that travels with a vault: an absolute value, or one resolving outside the vault, is refused with a warning. A note may still name an absolute path in its own link — the same capability plain Markdownalready has, and it is not confined. - Default exclusions: never overwrite the input in place unless the same path is also passed as output; never fetch remote images silently; never install npm/pip packages globally.
- Destructive actions:
docx_accept_changes.pyrewrites the destination; always use a distinct output path unless the user explicitly asks for in-place replacement.office/pack.pyoverwrites the destination archive. - Optional artifacts: the
office/schemas/directory is optional;office/validate.pyruns structural checks without it and only warns about missing XSDs unless--strict.
6. Validation Evidence
- Local verification:
cd skills/docx/scripts && npm install— installs docx, marked, mammoth, turndown, image-size, turndown-plugin-gfm intoscripts/node_modules/.python3 -m venv .venv && source .venv/bin/activate && pip install -r scripts/requirements.txt— installs python-docx, lxml, defusedxml.node scripts/md2docx.js examples/fixture-simple.md /tmp/out.docx && unzip -l /tmp/out.docx | grep word/document.xml— exits 0 and lists the document part.node scripts/docx2md.js /tmp/out.docx /tmp/back.md && test -s /tmp/back.md— round-trips non-empty Markdown. On a docx with comments / tracked changes, also writes/tmp/back.docx2md.json(schemav:1); on a docx with footnotes/endnotes, the markdown gets pandoc-style[^fn-N]markers + definitions block at the end. Seereferences/docx2md-sidecar.mdfor the full schema and--metadata-json/--no-metadata/--no-footnotesflags. Same-path (docx2md.js foo.docx foo.docx) is refused with exit 6 (SelfOverwriteRefused) so the input is never destroyed.node scripts/md2docx.js examples/fixture-obsidian-vault/note.md /tmp/obs.docx --obsidian --vault-root examples/fixture-obsidian-vault && unzip -l /tmp/obs.docx | grep -cE 'word/media/.*\.(png|jpg)'— the committed mini-vault note: exit 0, 5 embedded images, frontmatter inside a<w:tbl>. Full acceptance set (A1-A16) inscripts/tests/test_obsidian2md.py, 90 cases.node scripts/md2docx.js examples/fixture-math.md /tmp/math.docx && unzip -p /tmp/math.docx word/document.xml | grep -o '<m:oMath' | wc -l— exit 0, ≥4 native math objects.test_md2docx_math.py(26 cases).python3 scripts/office/validate.py /tmp/out.docx— exits 0, printsOKor only warnings.python3 scripts/office/unpack.py /tmp/out.docx /tmp/unpacked && python3 scripts/office/pack.py /tmp/unpacked /tmp/repack.docx && unzip -p /tmp/repack.docx word/document.xml | head -c 100— unpack/pack round trip.python3 scripts/docx_add_comment.py /tmp/out.docx /tmp/commented.docx --anchor-text "Quarterly" --comment "Verify this number" --author "QA Bot" && python3 -m office.validate /tmp/commented.docx— adds a Word comment anchored on the substring; output validates and contains a<w:comment>part. Seereferences/add-comment-howto.mdfor the full verification protocol.python3 scripts/docx_merge.py /tmp/merged.docx /tmp/out.docx /tmp/out.docx --page-break-between && python3 -m office.validate /tmp/merged.docx— merges N inputs (here, two copies ofout.docx) with a hard page break between them; output validates clean.
- Expected evidence:
/tmp/out.docx,/tmp/back.md,/tmp/unpacked/tree,/tmp/repack.docx. All reproducible fromexamples/fixture-simple.md. - CI signal:
python3 ../../.claude/skills/skill-creator/scripts/validate_skill.py skills/docx— exits 0 when the skill conforms to the Gold Standard (frontmatter, required sections, non-empty examples).
7. Instructions
Use graduated language: steps marked MUST are safety-critical; the rest are behavioural defaults with their rationale.
7.1 Select the right script before touching low-level APIs
- Look up the task in §10 Quick Reference first. If a script exists, run it — it captures accumulated OOXML knowledge that inline code tends to get wrong.
- Drop to
docx-jsorpython-docxcalls only when the user's requirement genuinely is not covered (e.g. inserting a custom content control, manipulating a specific style's inheritance chain).
7.2 Install dependencies locally
- MUST run
bash scripts/install.shonce. It createsscripts/.venv/andscripts/node_modules/locally (nothing installed globally), prints a warning for any missing system tool, runs a smoke-test of the documented commands before declaring success, and is idempotent. - Invocation —
python3is safe. The Python CLIs self-bootstrap intoscripts/.venv:python3 scripts/X.py …and./.venv/bin/python scripts/X.py …are equivalent — on a host wherepython3is not the venv (pyenv / conda / system Python), the script re-execs itself intoscripts/.venv. If the venv is missing, you get arun: bash scripts/install.shhint instead of a rawModuleNotFoundError. (Node CLIs resolvescripts/node_modules, also local.) - External system tools (checked by
install.sh, installed manually per project plan §3.3 "внешние инструменты — не бандлятся"):- LibreOffice (
soffice) — required bydocx_accept_changes.py. macOS:brew install --cask libreoffice. Debian:sudo apt install libreoffice --no-install-recommends. Fedora:sudo dnf install libreoffice. Commands that need it fail with a clear error until it's installed.
- LibreOffice (
7.3 Creating .docx from Markdown
- Prefer
md2docx.jsover hand-writingnew Document({...}). - Page size / orientation / margins are exposed as flags — default US Letter portrait (backward-compatible):
--page-size A4|Letter,--landscape,--margins T,R,B,L(dxa, each value optionallymm-suffixed). Content width, tables, and images/Mermaid diagrams all auto-derive from the chosen page, so they never overflow A4 — no manualword/document.xmlpatching needed. Example:node scripts/md2docx.js report.md report.docx --page-size A4. - For headers/footers use
--header "…"/--footer "…". Multi-line headers are a single string with\n—md2docx.jssplits on the newline.
7.4 Extracting text from .docx to Markdown
- Use
docx2md.jsfor GFM output with tables preserved. If the document contains images, a sibling<stem>_images/directory is created — tell the user so they know to ship it alongside the Markdown. - For docs that carry comments or tracked changes, the converter writes a JSON sidecar
<stem>.docx2md.jsonnext to the Markdown. Tell the user to ship the sidecar alongside the markdown if the audit trail matters; the sidecar's absence on a clean doc is a feature, not a missing artefact. Field semantics + theunsupportedcounters are documented inreferences/docx2md-sidecar.md. - For docs with footnotes/endnotes, the markdown body gets pandoc-style
[^fn-N]/[^en-N]markers and definitions appended at the end. Pass--no-footnotesif downstream tooling can't parse pandoc syntax. - Hidden text is dropped by default, and stdout reports
Dropped N hidden run(s). When it does, tell the user. Pass--keep-hidden-textwhen the hidden part is the point: template instructions, translation-tool markup, auditing what a document conceals. - MUST NOT invoke
docx2md.jswith the same input and output path — the cross-7 H1 guard exits 6 (SelfOverwriteRefused), but better to never tempt it. - For plain text without any formatting,
pandoc -t plainis a valid alternative but external — document this choice to the user rather than silently switching tools.
7.5 Filling a template
- MUST ensure the template contains placeholders in the exact form
{{key}}or{{nested.key}}. Spaces inside the braces ({{ key }}) are tolerated; anything else ([key],${key},<%=key%>) is not recognised. - Provide
data.jsonas an object at the top level. - Pass
--strictwhen the template must be fully resolved (legal documents, customer-facing letters). Without--strict, unresolved placeholders remain in the output and are listed on stderr.
7.6 Accepting tracked changes
docx_accept_changes.pyrequires LibreOffice. Check_soffice.pylocates it before telling the user it "just works" — on some CI images LibreOffice is absent.- Output must be a new path; do not overwrite the input unless asked. Even then, prefer an intermediate file and move on success.
- LibreOffice 26.2 limitation: the CLI
macro:///dispatch this script relies on is unreliable on 26.2 (dropped on cold profiles while soffice exits 0). The script self-verifies: if revision marks survive, it removes the bogus output and exits 1 withAcceptChangesVerificationError— tell the user to accept the changes in a desktop Word/LibreOffice session or use LibreOffice 25.x. It never silently hands back an unaccepted copy.
7.7 Raw OOXML editing
python3 scripts/office/unpack.py doc.docx work/first — this pretty-prints, merges adjacent runs, and entity-encodes smart quotes, so editing is safe.- Edit XML under
work/word/,work/ppt/, orwork/xl/(docx/pptx/xlsx respectively). python3 scripts/office/pack.py work/ out.docxrepackages and reverses the smart-quote entities.python3 scripts/office/validate.py out.docxconfirms basic structural sanity before shipping.
8. Workflows (Optional)
Create a new document from Markdown:
- [ ] Prepare input.md (YAML frontmatter optional)
- [ ] `cd scripts && npm install` if first run
- [ ] `node scripts/md2docx.js input.md output.docx`
- [ ] `python3 scripts/office/validate.py output.docx`
- [ ] Open in Word/LibreOffice for a spot-checkRound-trip an existing document:
- [ ] `node scripts/docx2md.js input.docx extracted.md`
- [ ] Edit `extracted.md`
- [ ] `node scripts/md2docx.js extracted.md back.docx`
- [ ] `python3 scripts/office/validate.py back.docx`Fill a template with JSON data:
- [ ] Prepare template.docx with `{{placeholders}}`
- [ ] Prepare data.json matching the placeholder keys
- [ ] `python3 scripts/docx_fill_template.py template.docx data.json out.docx --strict`
- [ ] Confirm no unresolved placeholders on stderr9. Best Practices & Anti-Patterns
| DO THIS | DO NOT DO THIS |
Use md2docx.js for Markdown → .docx; it handles page size, numbering, dual-width tables, and image alt-text. | Assemble docx inline with new Document({...}) on every task — you'll regress on paged-media details. |
Keep placeholders exactly {{key}}; pass --strict for legal/customer-facing outputs. | Roll your own regex replace on the extracted XML; split runs will defeat you. |
office/unpack.py → edit → office/pack.py for raw XML edits. | Edit the .zip in place with external tools — smart-quote round-trips will break. |
docx_accept_changes.py (LibreOffice) for applying tracked changes. | Delete <w:del> / <w:ins> blocks directly — you'll lose author attribution and break paragraph-delete markers. |
Rationalization Table
| Agent Excuse | Reality / Counter-Argument |
| "I know docx-js, I'll just write the assembly inline." | md2docx.js already encodes the fixes for Word's quirks; inline code regresses on tables, images, and numbering. |
| "The user wants a tiny change, unpacking is overkill." | Unpacking is three seconds; skipping it and patching the ZIP by hand routinely breaks smart-quote round-trips. |
"I don't need --strict, a missing placeholder is obvious." | Missing placeholders ship as literal {{customer.name}} in customer emails. --strict is exactly for customer-facing output. |
"Schemas aren't bundled, so validate.py is useless." | The structural layer (relationships, duplicate IDs, comment pairing) runs without schemas. XSD binding is a bonus. |
10. Quick Reference
| Task | Command |
Markdown → .docx | node scripts/md2docx.js input.md output.docx |
Markdown → A4 .docx | node scripts/md2docx.js input.md output.docx --page-size A4 [--landscape] [--margins T,R,B,L] |
.docx → Markdown | node scripts/docx2md.js input.docx output.md |
HTML / .webarchive / .mhtml → .docx | node scripts/html2docx.js input.html output.docx [--header ...] [--footer ...] [--reader-mode] [--json-errors] |
Web page / archive → .docx (reader mode, strips chrome) | node scripts/html2docx.js page.webarchive article.docx --reader-mode |
| Fill template | python3 scripts/docx_fill_template.py template.docx data.json out.docx [--strict] |
| Accept tracked changes | python3 scripts/docx_accept_changes.py in.docx out.docx |
| Unpack for raw editing | python3 scripts/office/unpack.py in.docx unpacked/ |
| Repack | python3 scripts/office/pack.py unpacked/ out.docx |
| Structural validate | python3 scripts/office/validate.py file.docx [--json] [--strict] |
| Compare tracked changes vs original | python3 scripts/office/validate.py edited.docx --compare-to ORIGINAL.docx |
| Preview as PNG-grid | python3 scripts/preview.py file.docx preview.jpg [--cols 3] [--dpi 110] |
| Set password | python3 scripts/office_passwd.py clean.docx encrypted.docx --encrypt PASSWORD (use - to read from stdin) |
| Remove password | python3 scripts/office_passwd.py encrypted.docx clean.docx --decrypt PASSWORD |
| Detect password | python3 scripts/office_passwd.py file.docx --check (exit 0 encrypted / 10 clean / 11 missing) |
| Surgical text replace (no round-trip) | python3 scripts/docx_replace.py in.docx out.docx --anchor "phrase" --replace "new text" |
| Insert paragraph after anchor | python3 scripts/docx_replace.py in.docx out.docx --anchor "phrase" --insert-after body.md |
| Delete paragraph at anchor | python3 scripts/docx_replace.py in.docx out.docx --anchor "phrase" --delete-paragraph |
| Add review comment | python3 scripts/docx_add_comment.py in.docx out.docx --anchor-text "phrase" --comment "body" --author "Reviewer" |
| Reply to comment | python3 scripts/docx_add_comment.py in.docx out.docx --parent N --comment "reply body" --author "Dev" |
| Edit unpacked tree | python3 scripts/docx_add_comment.py --unpacked-dir DIR --anchor-text "phrase" --comment "body" (combine with --parent for replies in-place) |
| Merge N docx | python3 scripts/docx_merge.py merged.docx a.docx b.docx c.docx [--page-break-between] |
| Obsidian note → .docx | node scripts/md2docx.js "<vault>/note.md" out.docx --obsidian --page-size A4 |
| Obsidian note → clean .md | node scripts/obsidian2md.js "<vault>/note.md" clean.md |
Markdown with $…$/$$…$$ → .docx (math on by default) | node scripts/md2docx.js input.md output.docx |
| Markdown → .docx, math disabled | node scripts/md2docx.js input.md output.docx --no-math |
| Machine-readable failures | append --json-errors to any of the above |
11. Examples (Few-Shot)
Full fixture: examples/fixture-simple.md.
Input — user request:
Convert
report.mdto a.docxand check it's structurally sound.
Output — agent action (abbreviated):
cd skills/docx/scripts && npm install # one-time setup
node scripts/md2docx.js report.md report.docx
python3 scripts/office/validate.py report.docxReport the exit codes and a one-line summary to the user ("report.docx created; validator OK").
Input — user request:
Fill
invoice.docxwith the data incustomer.json, fail if anything is missing.
Output — agent action:
python3 scripts/docx_fill_template.py invoice.docx customer.json invoice-filled.docx --strictOn non-zero exit, surface the Unresolved placeholders: … stderr line to the user.
12. Resources
- references/ooxml-basics.md —
.docxas a ZIP of XML parts, minimum viable structure, namespace table. - references/docx-js-gotchas.md —
docx-jspitfalls (A4 default, dual widths, numbering, image altText, TOC, rsids). - references/tracked-changes.md —
<w:ins>/<w:del>syntax, paragraph-delete markers, author attribution. - references/templating.md — why adjacent-run merging matters, supported placeholder grammar, anti-patterns.
- scripts/office/validators/redlining.py — RedliningValidator: compare edited
.docxagainst the original, report unmarked deletions/insertions/rewrites and author-attribution gaps. Driven by--compare-toflag onoffice/validate.py. - scripts/office/shim/ — LD_PRELOAD / DYLD_INSERT_LIBRARIES shim that lets LibreOffice start inside seccomp-tightened sandboxes that block AF_UNIX.
_soffice.pyauto-detects and auto-compiles. See scripts/office/tests/test_shim.md for nsjail/Docker end-to-end validation. - scripts/obsidian2md.js — Obsidian note → CommonMark CLI (TASK 030). Thin argv/exit-code wrapper; all rules live in
_obsidian_lib.js. - scripts/_obsidian_lib.js — the conversion core, required in-process by both
obsidian2md.jsandmd2docx.js --obsidian. Knows nothing about OOXML, so the rules are testable as text-in/text-out and stay reusable by peer skills. - examples/fixture-obsidian-vault/ — committed mini-vault driving
scripts/tests/test_obsidian2md.py:app.json, embeds with size hints, a%-in-name attachment, a space-named directory, and a mutual-transclusion pair. - scripts/_math_lib.js —
$…$/$$…$$extraction, sentinel substitution, and KaTeX → MathML →mathml2ommlOMML batch rendering (TASK 031). Required in-process bymd2docx.js; knows nothing aboutdocx-js or OOXML beyond the OMML string it returns. - examples/fixture-math.md — committed fixture driving
scripts/tests/test_md2docx_math.py: inline math, a standalone display formula, a Pandoc-style numbered equation table, math inside**bold**, currency/escaped-dollar negative cases, a formula inside a fenced code block, and one intentionally-malformed formula. - scripts/docx_replace.py — surgical anchor-and-action editor:
--replace/--insert-after/--delete-paragraphwithout lossy round-trip. Requiresdocx_anchor.pyand_actions.pysiblings. Honest scope documented in--help(single-run anchor for--replace; no cross-run anchor spanning format boundaries). - scripts/docx_anchor.py — anchor utilities:
_find_paragraphs_containing_anchor,_merge_adjacent_runs,_replace_in_run. Shared bydocx_replace.pyanddocx_add_comment.py. - scripts/_actions.py — F2 part-walker (
_iter_searchable_parts), F4_do_replace, F5_do_insert_after, F6_do_delete_paragraph; extracted fromdocx_replace.pyat task 006-07a per Q-A1 LOC guardrail. - scripts/_app_errors.py — domain exception hierarchy for
docx_replace.py(AnchorNotFound,LastParagraphCannotBeDeleted,Md2DocxFailed,Md2DocxNotAvailable,Md2DocxOutputInvalid,EmptyInsertSource). - scripts/md2docx.js — Markdown → .docx converter (original script, preserved).
- scripts/docx2md.js — .docx → Markdown converter (original script, preserved).
- scripts/docx_fill_template.py — template placeholder filler with run canonicalisation.
- scripts/docx_accept_changes.py — LibreOffice-based tracked-change acceptor.
- scripts/preview.py — universal
INPUT → PNG-gridrenderer for.docx/.docm/.xlsx/.pptx/.pdf. Byte-identical across all four office skills. - scripts/office_passwd.py — set / remove / detect password protection on
.docx/.xlsx/.pptxvia msoffcrypto-tool (MS-OFB Agile, Office 2010+). Byte-identical across the three OOXML skills (not pdf — pdf has its own AcroForm encryption). Pass-as the password to read it from stdin (avoids leaking viaps/shell history). - scripts/_errors.py —
--json-errorsenvelope helper used by every Python CLI. Schema-versioned (v=1); routes argparse usage errors through the same envelope as domain errors. - scripts/_soffice.py — LibreOffice subprocess wrapper with sandbox-aware AF_UNIX shim auto-load.
- scripts/office/ — OOXML unpack/pack/validate utilities; byte-identically replicated to
xlsxandpptxskills (docx is master — see CLAUDE.md §2 for the protocol). - scripts/office/_encryption.py — CFB-magic detection: rejects password-protected and legacy
.doc/.xls/.pptfiles with exit 3 + remediation hint. - scripts/office/_macros.py — XML-aware macro detection (Default/Override ContentType in
[Content_Types].xml); writer scripts warn when output extension drops the macros. - scripts/office/schemas/README.md — how to fetch ECMA-376 / Microsoft / W3C XSDs for strict validation.
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
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.

