| name | jaw-docx |
| description | Word DOCX create, read, edit, review. Triggers: Word doc, .docx, reports, memos, letters, templates. |
DOCX Skill
Use this skill for any .docx task: create, read, edit, review, template-fill, or QA verification.
Triggers: "Word doc", ".docx", reports, memos, letters, templates.
OfficeCLI routing and consent rule:
- First check whether
officecli is available with a probe that works in the agent's actual shell: command -v officecli on POSIX, Get-Command officecli -ErrorAction SilentlyContinue in PowerShell. command -v is not a PowerShell builtin or cmdlet — on Windows it prints nothing, sets no exit code, and raises no error, so it is indistinguishable from "not installed" and the agent asks to install a tool that is already there (#298). officecli --version is the portable fallback: a non-zero exit or no output means missing.
- If installed, recommend OfficeCLI first for high-fidelity DOCX mutations, validation, query, batch/resident flows, track changes, and CJK/rhwp-aware work.
- If missing, do not auto-install. Present choices before proceeding:
- Install forked OfficeCLI from
https://github.com/lidge-jun/OfficeCLI via bash "$(npm root -g)/cli-jaw/scripts/install-officecli.sh".
- Continue with lightweight fallback tools for the current task, with limitations stated.
- Stop or cancel.
- Before taking a lightweight fallback path, ask the user again and state what fidelity/features may be lost.
- If the user chooses lightweight mode, save that preference to memory for future Office work.
- Use upstream/vanilla
iOfficeAI/OfficeCLI only when the user explicitly asks for upstream behavior.
OfficeCLI is the recommended advanced backend for add/set/remove, validate, query, and track-change accept/reject workflows.
Lightweight fallback: Python OOXML scripts (scripts/*.py) for what OfficeCLI cannot do, or when the user chooses lightweight mode: tracked-change creation, OMML equations, bulk pattern matching, unpack/edit/repack. See §3.
DOCX only. Do NOT use this skill for PDFs, spreadsheets, HWPX, Google Docs, or any other format.
OfficeCLI discovery rule: before guessing paths, element names, or properties, ask the installed CLI. Use officecli --help for workflow entry points and officecli help docx ... --json for machine-readable schema.
Same-file execution rule: run OfficeCLI commands against the same .docx sequentially. Do not run officecli view, officecli validate, officecli query, or officecli get in parallel against one package. If a file lock occurs, stop and report the exact command and path before making a copy or retrying.
1. Quick Decision
| Task | Tool | Command pattern | Notes |
|---|
| Format like existing doc | shell + officecli | cp source.docx target.docx && officecli open target.docx | Inherit styles/headers/footers. See §2. |
| Create blank DOCX | officecli | officecli create report.docx | Start from real Office file |
| Add paragraph | officecli | officecli add FILE /body --type paragraph --prop text="..." | Primary write path |
| Edit paragraph/run | officecli | officecli set FILE /body/p[N] --prop ... | Exact path targeting |
| Read text/outline/stats | officecli | officecli view FILE text | Modes: text, annotated, outline, stats, issues, html |
| Query document | officecli | officecli query FILE "p[style=Heading1]" | CSS-like selectors |
| Template-safe replacement | officecli | officecli set FILE / --prop find="{{X}}" --prop replace="Y" | Preserves template structure |
| Validation / issue scan | officecli | officecli validate FILE | Pair with view FILE issues |
| Accept/reject tracked changes | officecli | officecli set FILE / --prop accept-changes=all | Also reject-changes=all |
| CREATE tracked changes | officecli (native) | add ... --prop revision.type=ins --prop revision.author=... | Native create + accept/reject (v1.0.115); Python only for bulk redline diffs |
| OMML equations | Python (L4) | Unpack → inject <m:oMath> → repack | officecli cannot generate OMML |
|
2. Reference-Based Editing (Edit > Create from Scratch)
When the user says "format like X.docx", "match existing style", "based on template", or provides a source file — start from the source file. Don't rebuild from scratch.
Workflow
- Copy the source:
cp source.docx target.docx — inherits all styles, margins, numbering, headers, footers
- Open with
officecli open target.docx — daemon starts; command returns immediately (do NOT run as run_in_background shell)
- Remove body content only — keep
/styles, /numbering, /header, /footer
- Add new paragraphs using style names that already exist (e.g.
--prop style=Heading1) — they auto-apply
Why This Matters
Pandoc-generated and Word-generated documents have specific style IDs (e.g. Heading1, BodyText, FirstParagraph) unique to that document. Adding a new style with the same name causes:
officecli validate errors about duplicate style IDs
- Word/LibreOffice rendering falls back to defaults
- The task takes 10× longer than necessary
Template Sources (priority order)
- User-provided source file — first-class template
tests/fixtures/*.docx — pre-built working examples shipped with this skill
officecli create blank — only when nothing else applies
Example — Assignment Case
# CORRECT: inherit Pandoc styles
cp Assignment1.docx Assignment2.docx
officecli open Assignment2.docx
officecli remove Assignment2.docx "/body/p[1]" # remove old body (keep styles/headers/footers)
# ... remove more paragraphs ...
officecli add Assignment2.docx /body --type paragraph --prop text="New title" --prop style=Heading1
officecli close Assignment2.docx
# WRONG: recreate styles that already exist — validate fails
officecli add doc.docx /styles --type style --prop name=Heading1 --prop size=20pt ...
3. Reference Materials & Script Map
officecli covers most DOCX tasks. For the rest, use these reference docs + Python scripts.
References (references/)
| File | Read when | Contains |
|---|
references/cjk-handling.md | Korean text / East Asian font / wrapping issues | rFonts East Asian fonts, lang tags, accessibility |
references/tracked-changes.md | Track changes / comments / redline work | w:ins, w:del, comments XML, script usage examples |
references/docx-js-api.md | DEPRECATED — npm docx library API, not applicable to officecli ecosystem | Ignore — kept for historical reference only |
Scripts (scripts/) — Python OOXML Toolkit
| Script | Run when | Command |
|---|
scripts/docx_cli.py | Unified Python CLI — unpack, save, validate, repair, search, TOC, chunk, comment, accept-changes, merge-runs | python3 scripts/docx_cli.py {open|save|validate|repair|text|search|toc|chunk|comment|accept-changes|merge-runs} |
scripts/accept_changes.py | Accept all tracked changes (alternative to officecli) | python3 scripts/accept_changes.py IN.docx OUT.docx |
scripts/comment.py | Add W3C-compliant OOXML comments anchored to text | python3 scripts/comment.py IN.docx OUT.docx --text "..." --anchor "..." |
scripts/ooxml/merge_runs.py | Merge adjacent runs with identical formatting (post-edit cleanup) | python3 scripts/ooxml/merge_runs.py unpacked/ |
scripts/ooxml/redline_diff.py | Validate tracked-change correctness vs original | python3 scripts/ooxml/redline_diff.py unpacked/ original.docx |
scripts/ooxml/simplify_tracked.py | Simplify same-author adjacent tracked changes | python3 scripts/ooxml/simplify_tracked.py unpacked/ |
Editing Escalation Ladder
When officecli can't do the job, escalate in this order:
| Level | When | Tool |
|---|
| L1 officecli high-level | Typical add/set/remove operations | officecli add/set/remove/query/view |
L2 officecli raw-set | XML injection — PAGE field, fldChar, hyperlink anchor, custom attributes | officecli raw-set FILE PATH --xpath X --action A --xml ... |
| L3 Python script | Bulk tracked-change ops, comment add, merge runs, redline validation | python3 scripts/*.py |
| L4 Unpack → edit XML → repack | OMML equations, custom style injection, pattern-match editing, anything L1-L3 can't reach | scripts/docx_cli.py open FILE work/ → edit work/word/*.xml → scripts/docx_cli.py save work/ OUT.docx |
Escalation signals:
- officecli shows "silently ignored" → L2 (raw-set)
- Need OMML/MathML equations → L4 (inject
<m:oMath> XML — officecli cannot generate these)
- Need to CREATE tracked changes (officecli only accepts/rejects) → L3 or L4
- Bulk find/replace across 100+ targets → L3 (
docx_cli.py search/replace)
- Pandoc-generated doc with custom style IDs → §2 Reference-Based Editing first
- Task still fails after L1+L2 → Read relevant
references/*.md BEFORE giving up
4. Subskill References
Additional detail lives in companion files. Load only the one you need.
| Subskill | Path | When to use |
|---|
| officecli-academic-paper | ./officecli-academic-paper/SKILL.md | Academic papers, citations, bibliography, TOC for papers |
| creating.md | ./creating.md | Detailed creation recipes (new documents from scratch) |
| editing.md | ./editing.md | Detailed editing guides (modify existing documents) |
Decision flow
Is the document an academic paper (thesis, journal, conference)?
YES --> read officecli-academic-paper/SKILL.md
NO --> continue with this file
User provided a source file to match?
YES --> §2 Reference-Based Editing + ./editing.md
NO --> ./creating.md
5. Design Principles for Business Documents
5.0 Design Read & Document Anti-Slop
Intent discovery (before building): ask audience (board / exec / internal team / public), formality
register (formal report · memo · whitepaper · proposal), and density (dense reference vs spacious) — then
match the type scale, palette restraint, and whitespace. If the user names a target ("McKinsey-style",
"academic", "startup memo"), map to it.
Named document anti-slop (AI-tells — pattern → fix):
- Calibri-everything — single default font → a deliberate heading+body pair (§5.3), committed.
- Fake-heading bold — manual bold+size instead of real styles → use Heading 1/2/3 styles (§5.1) so TOC/navigation work.
- Wall of text — dense unbroken prose → break into lists, callouts, tables, sectioned headings.
- Default-Word-template look — no palette/style identity → a restrained palette (§5.2) + consistent scale.
- Over-coloring — many accents / colored body text → one accent; body near-black.
- Justified-everything + orphan headings — full-justify body, headings stranded at page bottom → left-align body; keep headings with content.
- Placeholder leakage — "Acme Corp"/lorem/TBD in delivered output → realistic specific content, never invented data.
Named palettes + font pairings + personality→doc-type map: references/design-system.md.
5.1 Heading Hierarchy
- H1: Document title (one per document)
- H2: Major sections
- H3: Subsections under H2
- Never skip levels (H1 -> H3 is invalid). Table of Contents depends on correct heading structure.
# Verify heading hierarchy
officecli view report.docx outline
5.2 Color Palette
Use professional, muted tones only:
| Purpose | Allowed colors | Hex examples |
|---|
| Headings, emphasis | Navy | #003366, #1B2A4A |
| Body accents, borders | Charcoal | #333333, #4A4A4A |
| Highlights, callouts | Forest green | #2E5E3F, #1A4731 |
NEVER use rainbow colors, bright primary colors, or more than 3 accent colors in a single document.
5.3 Font Selection
| Script | Primary font | Fallback |
|---|
| Korean | Malgun Gothic | Pretendard |
| English / Latin | Calibri | Aptos |
The CJK fork auto-applies East Asian fonts, but verify with:
officecli raw report.docx /document | grep rFonts
5.4 Typography
Choose a readable body font (Calibri, Cambria, Georgia, Times New Roman). Keep body at 11-12pt. Headings should step up: H1=18pt minimum (20pt preferred for long documents), H2=14pt bold, H3=12pt bold.
5.5 Spacing & Page Setup
Use paragraph spacing (spaceBefore/spaceAfter) instead of empty paragraphs. Line spacing of 1.15x-1.5x for body text.
Always set margins explicitly. US Letter default: pageWidth=12240, pageHeight=15840, margins=1440 (1 inch).
5.6 Table of Contents
TOC generation depends entirely on heading styles. Before inserting TOC:
- Confirm all headings use proper Heading1/Heading2/Heading3 styles (not manual bold+size).
- Run
officecli view FILE outline to verify hierarchy.
- Generate TOC via the subskill method.
5.7 Table Design & Color Usage
Alternate row shading for readability. Header row with contrasting background. Consistent cell padding.
Use color sparingly -- accent color for headings or table headers, not rainbow formatting.
5.8 Content-to-Element Mapping
| Content Type | Recommended Element(s) | Why |
|---|
| Sequential items | Bulleted list (listStyle=bullet) | Scanning is faster than inline commas |
| Step-by-step process | Numbered list (listStyle=ordered) | Numbers communicate order |
| Comparative data | Table with header row | Columns enable side-by-side comparison |
| Trend data | Embedded chart (chartType=line/column) | Visual pattern recognition |
| Key definition | Hanging indent paragraph | Offset term from definition |
| Legal/contract clause | Numbered list with bookmarks | Cross-referencing via bookmarks |
| Mathematical content | Equation element (formula=LaTeX) | Proper OMML rendering |
| Citation/reference | Footnote or endnote | Keeps body text clean |
| Pull quote / callout | Paragraph with border + shading | Visual distinction from body |
| Multi-section layout | Section breaks with columns | Column control per section |
6. Mandatory Verification (NEVER SKIP)
Delivery Gate — treat verification as a gate, not a confirmation. Any failure = REJECT, do not deliver. Fix → re-run the checks → repeat until a pass finds zero new issues. After 3 rounds without convergence, STOP and report the likely root cause for the user to decide.
After ANY DOCX creation or edit, ALWAYS execute both steps:
# Step 1: Structural validation
officecli validate output.docx
# Step 2: Visual PDF verification
soffice --headless --convert-to pdf --outdir /tmp output.docx
# Open/inspect PDF to confirm: formatting, tables, images, headers/footers
- Skip PDF verification = unverified output. Inform user if soffice is unavailable.
- If
validate reports errors, fix them before delivering the file.
7. Prerequisite Check
# Required
python3 -c "import docx, lxml" || echo "MISSING: pip install python-docx lxml"
# LibreOffice: check only; ask before installing when PDF conversion is needed.
which soffice >/dev/null 2>&1 || echo "ASK USER: LibreOffice is not installed; install it for PDF conversion or skip PDF output."
# OfficeCLI: check only; do not auto-install from a skill.
if ! command -v officecli >/dev/null 2>&1; then
echo "ASK USER: install forked OfficeCLI from https://github.com/lidge-jun/OfficeCLI, continue lightweight, or stop."
echo "Install command after approval: bash \"\$(npm root -g)/cli-jaw/scripts/install-officecli.sh\""
fi
8. Tool Discovery
Always confirm syntax from help before guessing:
officecli --help
officecli help docx
officecli help docx add
officecli help docx set
officecli help docx query
officecli help docx add paragraph --json
officecli help docx set run --json
Drill into a specific area:
officecli help docx add paragraph
officecli help docx add picture
officecli help docx set run
officecli help docx set style
officecli help all --jsonl | grep '"format":"docx"'
| Binary | Path | Notes |
|---|
| officecli | officecli (PATH) | Optional advanced backend; ask before install. Supported fork: https://github.com/lidge-jun/OfficeCLI, with CjkHelper.cs for CJK font/language behavior |
9. Core Workflows
9.1 Execution Model
Run commands one at a time. Do not write all commands into a shell script and execute it as a single block.
OfficeCLI is incremental: every add, set, and remove immediately modifies the file and returns output.
- One command at a time, then read the output. Check the exit code before proceeding.
- Non-zero exit = stop and fix immediately. Do not continue building on a broken state.
- Verify after structural operations. After adding a style, table, chart, or section, run
get or validate before building on top of it.
9.2 Reading & Analyzing
officecli view doc.docx text # Full text extraction
officecli view doc.docx text --max-lines 200 # Truncated extraction
officecli view doc.docx text --start 1 --end 50 # Range extraction
officecli view doc.docx outline # Structure: stats, headings, headers/footers
officecli view doc.docx annotated # Style/font/size per run, equations as LaTeX
officecli view doc.docx stats # Paragraph count, style/font distribution
9.3 Element Inspection
officecli get doc.docx / # Document root (metadata, page setup)
officecli get doc.docx /body --depth 1 # List body children
officecli get doc.docx "/body/p[1]" # Specific paragraph
officecli get doc.docx "/body/p[1]/r[1]" # Specific run
officecli get doc.docx "/body/tbl[1]" --depth 3 # Table structure
officecli get doc.docx /styles # Style definitions
officecli get doc.docx "/styles/Heading1" # Specific style
officecli get doc.docx "/header[1]" # Header/footer
officecli get doc.docx /numbering # Numbering definitions
officecli get doc.docx "/body/p[1]" --json # JSON output for scripting
9.4 CSS-like Queries
officecli query doc.docx 'paragraph[style=Heading1]' # By style
officecli query doc.docx 'p:contains("quarterly")' # By text content
officecli query doc.docx 'p:empty' # Empty paragraphs
officecli query doc.docx 'image:no-alt' # Images without alt text
officecli query doc.docx 'p[align=center] > r[bold=true]' # Compound selectors
officecli query doc.docx 'paragraph[size>=24pt]' # By size
officecli query doc.docx 'field[fieldType!=page]' # Fields by type
9.5 Headers & Footers
Standard footer setup (always use this pattern for documents with a cover page):
--prop field=page works (verified v1.0.115) — add --type footer --prop field=page injects a live PAGE field directly. The old raw-set <w:fldChar> workaround is no longer needed.
# Step 1. Empty footer for the cover page (auto-enables a title-page footer)
officecli add doc.docx / --type footer --prop type=first --prop text=""
# Step 2. Default footer with a live PAGE field — no raw-set needed
officecli add doc.docx / --type footer --prop field=page --prop type=default --prop align=center --prop size=9pt --prop font=Calibri
# Verify: officecli get doc.docx "/footer[2]" --depth 3 # shows the fldChar PAGE field
Footer index rule: When both a first-page footer and a default footer are added, the default footer is /footer[2]. If there is no first-page footer, the default footer is /footer[1]. Always verify with officecli get doc.docx "/footer[2]" (or "/footer[1]") to confirm the <w:fldChar> element is present.
LibreOffice rendering note: Page number fields may display as static "Page" in LibreOffice PDF preview -- this is a LibreOffice limitation. Open in Microsoft Word to see actual page numbers. Confirm the field with officecli get doc.docx "/footer[2]" -- output must show fldChar children.
9.6 Resident Mode (Performance)
Always use open/close -- it is the smart default. Every command benefits: no repeated file I/O.
officecli open doc.docx # Load once into memory (returns IMMEDIATELY; daemon in bg)
officecli add doc.docx ... # All commands run in memory -- fast
officecli set doc.docx ...
officecli close doc.docx # Write once to disk
Do NOT run officecli open as a background shell job (e.g. via run_in_background). It returns immediately and the daemon lives in the background automatically. Running it as a monitored shell creates zombies and file locks. If stuck: pkill -9 -f "officecli.*resident" then retry.
9.7 Batch Mode (Performance)
Execute multiple operations in a single open/save cycle:
cat <<'EOF' | officecli batch doc.docx
[
{"command":"add","parent":"/body","type":"paragraph","props":{"text":"Introduction","style":"Heading1"}},
{"command":"add","parent":"/body","type":"paragraph","props":{"text":"This report covers Q4 results.","font":"Calibri","size":"11pt"}}
]
EOF
Batch supports: add, set, get, query, remove, move, swap, view, raw, raw-set, validate.
Batch fields: command, path, parent, type, from, to, index, after, before, props (dict), selector, mode, depth, part, xpath, action, xml.
parent = container to add into (for add). path = element to modify (for set, get, remove, move, swap).
Error decoding: 'X' is an invalid start of a value = shell syntax leaked into JSON (unquoted $, stray shell metachar). Use heredoc cat <<'EOF' | officecli batch FILE with single-quoted 'EOF' delimiter — prevents shell expansion.
10. Common Pitfalls
| Pitfall | Correct Approach |
|---|
--name "foo" | Use --prop name="foo" -- all attributes go through --prop |
| Guessing property names | Run officecli help docx set paragraph --json to see exact names |
\n in shell strings | Use \\n for newlines in --prop text="line1\\nline2" |
| Modifying an open file | Close the file in Word first |
Hex colors with # | Use FF0000 not #FF0000 -- no hash prefix |
| Paths are 1-based | /body/p[1], /body/tbl[1] -- XPath convention |
--index is 0-based | --index 0 = first position -- array convention |
Unquoted [N] in zsh/bash | Shell glob-expands /body/p[1] -- always quote paths: "/body/p[1]" |
| Spacing in raw numbers | Use unit-qualified values: '12pt', '0.5cm', '1.5x' not raw twips |
| Empty paragraphs for spacing | Use spaceBefore/spaceAfter properties on paragraphs |
$ in --prop text= (shell) | Use single quotes: --prop text='$50M' |
$ and ' in batch JSON | Use heredoc: cat <<'EOF' | officecli batch |
| Wrong border format | Use style;size;color;space format: single;4;FF0000;1 |
| listStyle on run | listStyle is a paragraph property, not a run property |
| Row-level bold/color/shd | Row set only supports height, header, and c1/c2/c3 text shortcuts. Use cell-level for formatting |
11. Known Issues
| Issue | Workaround |
|---|
| No visual preview | Unlike pptx (SVG/HTML), docx has no built-in rendering. Use view text/outline/annotated/issues for verification. Users must open in Word for visual check. |
| Track changes — native create | OfficeCLI creates tracked changes natively (v1.0.115): --prop revision.type=ins|del|moveTo|moveFrom|format with revision.author/revision.date on the host element; accept/reject via set /revision --prop revision.action=accept|reject. (scripts/docx_cli.py no longer required for creation.) |
| Tab stops may require raw XML | Tab stop creation is not exposed in high-level commands. Use raw-set to add tab stop definitions. |
| Chart series cannot be added after creation | set --prop data= can only update existing series, not add new ones. Delete and recreate the chart. |
| Complex numbering definitions | listStyle=bullet/ordered (canonical) covers simple cases. For multi-level lists, use numId/numLevel properties. |
| Shell quoting in batch with echo | Use heredoc: cat <<'EOF' | officecli batch doc.docx. |
| Batch intermittent failure | ~1-in-15 batch operations may fail with "Failed to send to resident". Retry or close/reopen file. Split large batches into 10-15 operation chunks. |
Table-level padding produces invalid XML | Do not use set tbl[N] --prop padding=N. Use cell-level padding.top/padding.bottom. If already applied, remove with raw-set --xpath "//w:tbl[N]/w:tblPr/w:tblCellMar" --action remove. |
| Internal hyperlinks not supported | hyperlink only accepts absolute URIs. For #bookmark links, use raw-set with <w:hyperlink w:anchor="bookmarkName">. |
12. QA Checklist
Assume there are problems. Your job is to find them.
Issue Detection
officecli view doc.docx issues
officecli view doc.docx issues --type format
officecli view doc.docx issues --type content
officecli view doc.docx issues --type structure
Content QA
officecli view doc.docx text
officecli view doc.docx outline
officecli query doc.docx 'p:empty'
officecli query doc.docx 'image:no-alt'
# Check for leftover placeholders
officecli query doc.docx 'p:contains("lorem")'
officecli query doc.docx 'p:contains("xxxx")'
officecli query doc.docx 'p:contains("placeholder")'
Pre-Delivery Checklist
Verification Loop
- Generate document
- Run
view issues + view outline + view text + validate
- List issues found (if none, look again more critically)
- Fix issues
- Re-verify -- one fix often creates another problem
- Repeat until a full pass reveals no new issues
Do not declare success until you've completed at least one fix-and-verify cycle.
QA display notes:
view text shows "1." for ALL numbered list items regardless of actual rendered number. This is a display limitation -- not a defect.
view issues flags "body paragraph missing first-line indent" on cover page paragraphs, centered headings, list items, callout boxes, etc. These warnings are expected. First-line indent is only required in APA/academic body text.
13. Anti-Patterns (MUST AVOID)
- Placeholder data: NEVER leave "Acme Corp", "Alice Chen", "Lorem ipsum" in output. If the user has not provided data, ask.
- Footer PAGE fields: When setting page numbers via
raw-set, the XML structure must be exact. See the Headers & Footers section for the correct fldChar/instrText/fldChar sequence.
- Empty paragraphs for spacing: Use
spacing-after properties.
- Manual bullet characters (-, *): Use
listStyle=bullet or listStyle=number.
- Manual font XML injection: Use
--prop font=... when it suffices.
- Chinese comments in output: Some subskill reference files contain Chinese-language code comments (operational notes). NEVER copy these into user-facing document output. Treat them as internal annotations only.
- Ignoring reference materials: If a complex task fails with officecli, READ
references/*.md + check scripts/*.py (§3) BEFORE giving up or falling back to inline text. The Pre-officecli OOXML workflow is still available via scripts.
- Recreating existing styles: When user provides a source file ("format like X.docx"), copy it and modify — do not rebuild styles from scratch. See §2.
14. Dependencies
| Tool | Purpose | Status |
|---|
officecli (PATH) | Recommended advanced DOCX backend; fork source is https://github.com/lidge-jun/OfficeCLI | Optional; ask before install |
dotnet | Runtime/build for OfficeCLI fork builds | Optional; required only after user approves fork install/build |
python3 | Fallback scripts (scripts/*.py) | Required for L3/L4 |
lxml | Python XML processing for scripts/ooxml/* | Required for L3/L4 (pip install lxml) |
soffice | PDF conversion / .doc migration / macro workflows | Optional fallback |
pdftoppm | Image-based QA after PDF render | Optional fallback |
Fork build (development only)
dotnet publish -c Release -o build-local