docx

Create, read, edit, template, and review Word .docx files.

  • word
  • docx
  • documents
  • office
  • templates
  • revisions
  • comments

Declared platforms: linux · macos · windows

Install
npx skills add 'https://github.com/NousResearch/hermes-agent/tree/main/skills/productivity/docx'
Download bundle ↓
main · 24fd22bScanned 2026-09-15

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗
View on GitHub
← Back to SKILL.md
#!/usr/bin/env python3# MIT License. Part of the Hermes docx skill."""Create a .docx document from a JSON spec. Usage: docx_create.py spec.json output.docxRun with --help for the spec format summary. Spec (JSON object):{  "page": {"width_mm": 210, "height_mm": 297,           "margins_mm": {"top": 25, "bottom": 25, "left": 20, "right": 20}},  "header": "text shown in page header",  "footer": "text shown in page footer",  "styles": [{"name": "MyStyle", "base": "Normal", "font": "Arial",              "size_pt": 12, "bold": true, "color": "1F4E79"}],  "blocks": [    {"type": "heading", "text": "Title", "level": 1},    {"type": "paragraph", "style": "MyStyle", "runs": [        {"text": "plain "}, {"text": "bold", "bold": true},        {"text": " italic", "italic": true},        {"text": " under", "underline": true}]},    {"type": "paragraph", "text": "shortcut: single plain run"},    {"type": "bullet_list", "items": ["a", "b"]},    {"type": "numbered_list", "items": ["one", "two"]},    {"type": "table", "header": ["Col1", "Col2"],     "rows": [["1", "2"]], "style": "Light Grid Accent 1",     "header_bold": true},    {"type": "image", "path": "pic.png", "width_mm": 60},    {"type": "page_break"},    {"type": "toc"}  ]} Extras: `"footer_page_numbers": true` at the top level adds a"Page X of Y" footer built from PAGE/NUMPAGES fields, and a `toc` blockinserts a Table of Contents field. Field results are computed byWord/LibreOffice when the file is opened, not by python-docx."""from __future__ import annotations import argparseimport jsonimport sys from docx import Documentfrom docx.enum.style import WD_STYLE_TYPEfrom docx.enum.text import WD_BREAKfrom docx.shared import Mm, Pt, RGBColor  def apply_page(doc, page: dict) -> None:    section = doc.sections[0]    if "width_mm" in page:        section.page_width = Mm(page["width_mm"])    if "height_mm" in page:        section.page_height = Mm(page["height_mm"])    m = page.get("margins_mm", {})    for side in ("top", "bottom", "left", "right"):        if side in m:            setattr(section, f"{side}_margin", Mm(m[side]))  def add_styles(doc, styles: list) -> None:    for s in styles:        style = doc.styles.add_style(s["name"], WD_STYLE_TYPE.PARAGRAPH)        if s.get("base"):            style.base_style = doc.styles[s["base"]]        font = style.font        if s.get("font"):            font.name = s["font"]        if s.get("size_pt"):            font.size = Pt(s["size_pt"])        if s.get("bold") is not None:            font.bold = s["bold"]        if s.get("italic") is not None:            font.italic = s["italic"]        if s.get("color"):            font.color.rgb = RGBColor.from_string(s["color"])  def add_runs(para, block: dict) -> None:    runs = block.get("runs")    if runs is None:        runs = [{"text": block.get("text", "")}]    for r in runs:        run = para.add_run(r.get("text", ""))        if r.get("bold"):            run.bold = True        if r.get("italic"):            run.italic = True        if r.get("underline"):            run.underline = True  def add_block(doc, block: dict) -> None:    btype = block["type"]    if btype == "heading":        doc.add_heading(block.get("text", ""), level=block.get("level", 1))    elif btype == "paragraph":        para = doc.add_paragraph(style=block.get("style"))        add_runs(para, block)    elif btype == "bullet_list":        for item in block.get("items", []):            doc.add_paragraph(item, style="List Bullet")    elif btype == "numbered_list":        for item in block.get("items", []):            doc.add_paragraph(item, style="List Number")    elif btype == "table":        header = block.get("header", [])        rows = block.get("rows", [])        ncols = len(header) if header else (len(rows[0]) if rows else 1)        table = doc.add_table(rows=0, cols=ncols)        table.style = block.get("style", "Table Grid")        if header:            cells = table.add_row().cells            for i, text in enumerate(header):                cells[i].text = str(text)                if block.get("header_bold", True):                    for para in cells[i].paragraphs:                        for run in para.runs:                            run.bold = True        for row in rows:            cells = table.add_row().cells            for i, text in enumerate(row):                cells[i].text = str(text)    elif btype == "image":        width = Mm(block["width_mm"]) if block.get("width_mm") else None        doc.add_picture(block["path"], width=width)    elif btype == "page_break":        doc.add_paragraph().add_run().add_break(WD_BREAK.PAGE)    elif btype == "toc":        from docx_edit import _add_field        para = doc.add_paragraph()        _add_field(para, r' TOC \o "1-3" \h \z \u ',                   "Table of contents - open in Word/LibreOffice and "                   "update fields to populate.")    else:        raise ValueError(f"unknown block type: {btype}")  def main() -> int:    ap = argparse.ArgumentParser(        description="Create a .docx from a JSON spec.",        epilog="See the module docstring (top of this file) for the spec format.")    ap.add_argument("spec", help="path to JSON spec file")    ap.add_argument("output", help="path of .docx to write")    args = ap.parse_args()     with open(args.spec, encoding="utf-8") as f:        spec = json.load(f)     doc = Document()    if spec.get("page"):        apply_page(doc, spec["page"])    if spec.get("styles"):        add_styles(doc, spec["styles"])    if spec.get("header"):        doc.sections[0].header.paragraphs[0].text = spec["header"]    if spec.get("footer"):        doc.sections[0].footer.paragraphs[0].text = spec["footer"]    for block in spec.get("blocks", []):        add_block(doc, block)    if spec.get("footer_page_numbers"):        from docx_edit import _add_field        para = doc.sections[0].footer.paragraphs[0]        para.add_run("Page ")        _add_field(para, " PAGE ", "1")        para.add_run(" of ")        _add_field(para, " NUMPAGES ", "1")    doc.save(args.output)    print(json.dumps({"ok": True, "output": args.output,                      "blocks": len(spec.get("blocks", []))}))    return 0  if __name__ == "__main__":    sys.exit(main()) 
Referenced from SKILL.md