From document-generation
Generates branded Word documents (DOCX). Triggered when the user asks to "create a Word document", "generate a DOCX", "write a memo", "create a proposal", "DOCX report", "Word file", or references Word/DOCX output for reports, memos, proposals, letters, or formal documents.
npx claudepluginhub equiforte/reporting-services-plugins --plugin document-generationThis skill is limited to using the following tools:
Generate branded Word documents using python-docx. Supports creating new documents, editing existing ones, tracked changes, comments, and table of contents.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Creates, reads, edits, and analyzes .docx files using docx-js for new documents, pandoc for text extraction, Python scripts for XML unpacking/validation/changes, and LibreOffice for conversions.
Share bugs, ideas, or general feedback.
Generate branded Word documents using python-docx. Supports creating new documents, editing existing ones, tracked changes, comments, and table of contents.
Before generating output, check that {WORKING_DIR}/.reporting-resolved/brand-config.json exists. If it does not, tell the user: "The branding plugin is required but has not run. Please install the branding plugin and run /reporting-plugins:brand first." Do not produce unbranded output.
If a JSON data file was generated earlier in this session (in output/text/), read it as the canonical data source to ensure cross-format parity. If no prior JSON exists, use data from the conversation context directly.
Read .reporting-resolved/brand-config.json for all brand values. Read the logo from .reporting-resolved/logo.png (if it exists).
pip install python-docx defusedxml
Office XML utilities are in the branding plugin. Read _office_scripts_path from .reporting-resolved/brand-config.json to find them:
pack.py / unpack.py — extract and repack DOCX XML for low-level editingvalidate.py — XSD schema validation + XXE strippinghelpers/merge_runs.py — consolidate fragmented text runshelpers/simplify_redlines.py — accept all tracked changesoutput/docx/{slug}-{YYYY-MM-DD}-{HHmm}-{xxx}.docxfrom docx import Document
from docx.shared import Inches, Pt, Emu
doc = Document()
section = doc.sections[0]
# CRITICAL: Set page size explicitly — python-docx defaults to A4, not US Letter
section.page_width = Emu(12240 * 914) # 8.5 inches in EMU
section.page_height = Emu(15840 * 914) # 11 inches in EMU
# Or use DXA (twentieths of a point): 12240 x 15840 for US Letter
# Margins in EMU (1 inch = 914400 EMU, 72pt margin = 914400 EMU)
section.top_margin = Emu(brand["layout"]["margin_top"] * 12700)
section.bottom_margin = Emu(brand["layout"]["margin_bottom"] * 12700)
section.left_margin = Emu(brand["layout"]["margin_left"] * 12700)
section.right_margin = Emu(brand["layout"]["margin_right"] * 12700)
Header: Add logo (left-aligned) and firm name (right-aligned) to the default header.
from docx.shared import Inches
header = section.header
header_para = header.paragraphs[0]
# Add logo
if logo_exists:
run = header_para.add_run()
run.add_picture(".reporting-resolved/logo.png", height=Inches(0.4))
Footer: Add confidentiality notice (left) and page number (right).
font_size_h1, semantic.heading_color, font_family_heading, boldfont_size_h2, semantic.heading_colorfont_size_body, colors.text, font_familyfont_size_caption, semantic.muted_colorsemantic.link_color\n for line breaksAlways use Paragraph elements. \n in a run creates invalid line breaks in some renderers.
# WRONG
para.add_run("Line 1\nLine 2")
# RIGHT
doc.add_paragraph("Line 1")
doc.add_paragraph("Line 2")
Use proper list formatting with numbering definitions, not • or – characters.
# WRONG
doc.add_paragraph("• Item 1")
# RIGHT
doc.add_paragraph("Item 1", style="List Bullet")
Both columnWidths AND individual cell widths must be set, in DXA units (1 inch = 1440 DXA):
from docx.shared import Inches
from docx.oxml.ns import qn
table = doc.add_table(rows=2, cols=3)
table.autofit = False
# Set table width
table.width = Inches(6.5)
# Set each cell width explicitly
for row in table.rows:
for i, cell in enumerate(row.cells):
cell.width = Inches(6.5 / 3)
from docx.shared import Pt, RGBColor
from docx.oxml.ns import qn
# Brand-colored header row
for cell in table.rows[0].cells:
shading = cell._element.get_or_add_tcPr()
shading_elem = shading.get_or_add_shd()
# components.table_header_bg without '#'
shading_elem.set(qn("w:fill"), brand["components"]["table_header_bg"].lstrip("#"))
for para in cell.paragraphs:
for run in para.runs:
run.font.color.rgb = RGBColor.from_string(
brand["components"]["table_header_text"].lstrip("#")
)
run.font.bold = True
When editing raw DOCX XML (via unpack/pack), use XML entities for smart quotes:
‘ (left single quote ')’ (right single quote ')“ (left double quote ")” (right double quote ")For changes that python-docx can't handle (complex formatting, custom XML):
# Read office scripts path from resolved brand config
OFFICE_SCRIPTS=$(python3 -c "import json; print(json.load(open('.reporting-resolved/brand-config.json'))['_office_scripts_path'])")
# Extract
python "$OFFICE_SCRIPTS"/unpack.py input.docx extracted/
# Edit XML files in extracted/word/document.xml
# ...
# Validate
python "$OFFICE_SCRIPTS"/validate.py extracted/word/document.xml --strip-xxe
# Repack
python "$OFFICE_SCRIPTS"/pack.py extracted/ output.docx
python {PLUGIN_ROOT}/skills/docx-generator/scripts/accept_changes.py input.docx output.docx
python {PLUGIN_ROOT}/skills/docx-generator/scripts/comment.py input.docx output.docx --comment "Review needed" --author "System"
validate.py after generation.{name}.validation.json sidecar.If firm.name was overridden but firm.website was NOT explicitly set, do not render firm.website in the document.