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."""Inspect and resolve tracked changes (w:ins / w:del) in a .docx. Subcommands:  list        JSON list of revisions: id, author, date, type, text  accept-all  accept every insertion and deletion  reject-all  reject every insertion and deletion  accept      accept one revision by --id  reject      reject one revision by --id Examples:  docx_revisions.py list report.docx  docx_revisions.py accept-all report.docx -o accepted.docx  docx_revisions.py reject report.docx --id 3 -o out.docx Semantics (direct XML manipulation, python-docx oxml layer):  accept w:ins -> unwrap (keep inserted runs)   reject w:ins -> remove  accept w:del -> remove                        reject w:del -> restore  (restore = w:delText tags renamed to w:t, wrapper unwrapped) Covers run-level insertions/deletions anywhere in body, tables (nestedincluded), headers and footers. Row/paragraph-mark revisions and formatchanges (w:rPrChange etc.) are reported by docx_read.py --revisions butnot resolved here."""from __future__ import annotations import argparseimport jsonimport sys from docx import Document from docx_common import iter_part_roots W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"  def q(tag: str) -> str:    return f"{{{W}}}{tag}"  INS, DEL = q("ins"), q("del")  def _iter_revision_elements(doc):    """Yield every w:ins / w:del element across body, headers, footers."""    for root in iter_part_roots(doc):        for el in root.iter(INS, DEL):            yield el  def _rev_text(el) -> str:    tag = q("delText") if el.tag == DEL else q("t")    return "".join(t.text or "" for t in el.iter(tag))  def _rev_record(el) -> dict:    return {        "id": el.get(q("id")),        "author": el.get(q("author")),        "date": el.get(q("date")),        "type": "insertion" if el.tag == INS else "deletion",        "text": _rev_text(el),    }  def _unwrap(el) -> None:    """Replace `el` with its children, keeping document order."""    parent = el.getparent()    idx = list(parent).index(el)    for child in list(el):        parent.insert(idx, child)        idx += 1    parent.remove(el)  def _apply(el, accept: bool) -> None:    if el.getparent() is None:  # already detached via an outer wrapper        return    if el.tag == INS:        if accept:            _unwrap(el)        else:            el.getparent().remove(el)    else:  # w:del        if accept:            el.getparent().remove(el)        else:            for dt in list(el.iter(q("delText"))):                dt.tag = q("t")            _unwrap(el)  def resolve(doc, accept: bool, rev_id: str | None = None) -> int:    targets = [el for el in _iter_revision_elements(doc)               if rev_id is None or el.get(q("id")) == rev_id]    for el in targets:        _apply(el, accept)    return len(targets)  def main() -> int:    ap = argparse.ArgumentParser(        description="List, accept, or reject tracked changes in a .docx.")    sub = ap.add_subparsers(dest="cmd", required=True)     def common(p, out=True):        p.add_argument("path", help="input .docx")        if out:            p.add_argument("-o", "--output",                           help="output path (default: overwrite input)")     common(sub.add_parser("list", help="list revisions as JSON"), out=False)    common(sub.add_parser("accept-all", help="accept every revision"))    common(sub.add_parser("reject-all", help="reject every revision"))    for name in ("accept", "reject"):        p = sub.add_parser(name, help=f"{name} one revision by id")        common(p)        p.add_argument("--id", required=True, help="revision id (w:id)")     args = ap.parse_args()    doc = Document(args.path)     if args.cmd == "list":        revs = [_rev_record(el) for el in _iter_revision_elements(doc)]        print(json.dumps({"ok": True, "revisions": revs}, ensure_ascii=False))        return 0     accept = args.cmd in ("accept-all", "accept")    rev_id = getattr(args, "id", None)    n = resolve(doc, accept, rev_id)    if rev_id is not None and n == 0:        print(json.dumps({"ok": False,                          "error": f"no revision with id {rev_id}"}))        return 1    out = args.output or args.path    doc.save(out)    print(json.dumps({"ok": True, "output": out, "resolved": n,                      "action": "accept" if accept else "reject"},                     ensure_ascii=False))    return 0  if __name__ == "__main__":    sys.exit(main()) 
Referenced from SKILL.md