Drive a running Microsoft Word instance from Python — xlwings, but for Word, and LLM-friendly.
Project description
wordlive
Drive a running Microsoft Word instance from Python — xlwings, but for Word.
Built for both human scripting and LLM agents. Windows-only.
Install
pip install wordlive
# Add to a python project
uv add wordlive
# Or as a `uv` tool
uv tool install wordlive
(Requires Python 3.10+ and pywin32 on Windows.)
Rendering pages to PNG (snapshot) needs the optional snapshot extra, which
pulls in PyMuPDF:
pip install "wordlive[snapshot]"
uv add "wordlive[snapshot]"
Python
import wordlive as wl
with wl.attach() as word:
doc = word.documents.active
# Reads
outline = doc.outline()
bookmarks = doc.bookmarks.list()
# See it the way a vision model would — render a section to PNG
# (needs `wordlive[snapshot]`):
png = doc.heading("Introduction").snapshot()[0].png
# Polite writes — preserves the user's cursor and view, atomic Ctrl-Z.
with doc.edit("Update address block"):
doc.bookmarks["Address"].set_text("123 Main St")
doc.content_controls["Signatory"].set_text("Jane Doe")
doc.heading("Introduction").insert_paragraph_after("New context paragraph.")
# Hand back a deliverable (the Python API is ungated):
doc.export_pdf("report.pdf")
CLI
JSON in, JSON out — designed to drop straight into an LLM tool-use loop:
wordlive status
wordlive outline # heading structure (heading:N)
wordlive outline --all # every paragraph (para:N) — alias of `paragraphs`
wordlive paragraphs # same: para:N, level, offsets, text
wordlive read bookmark Address
wordlive write bookmark Address --text "123 Main St"
# Navigate & locate by structure (read-only):
wordlive read section "Introduction" # body under a heading
wordlive read markdown --within heading:3 # serialise to clean Markdown (read mirror of insert-markdown)
wordlive read between --start heading:1 --end heading:3 # block between two headings
wordlive read nearest-heading --anchor-id para:42 # the section a paragraph sits in
wordlive find --text "exact phrase" # exact (normalized) → range:S-E
wordlive find-paragraph --text "roughly remembered text" # FUZZY → ranked para:N + scores
# Insert a new paragraph relative to ANY anchor (heading, paragraph, bookmark, …):
wordlive insert --anchor-id heading:1 --text "..." # after (default)
wordlive insert --anchor-id para:3 --text "..." --before
wordlive insert --anchor-id end --runs '[{"text":"Bold lead","bold":true},{"text":" — rest"}]'
# Drop a whole styled section in ONE op (item text takes **bold**/*italic* markdown):
wordlive insert-block --anchor-id heading:1 --items \
'[{"text":"**Politeness** first.","style":"List Bullet"},"Atomic undo."]'
# → reports range:START-END; then: wordlive list apply --anchor-id range:… --type bulleted
# Or hand it constrained Markdown and get real headings/lists/paragraphs (a subset, not CommonMark):
printf '# Plan\n\nKick-off.\n\n- scope it\n- staff it\n' | wordlive insert-markdown --anchor-id end --markdown -
# insert-section adds a heading + body in one op; replace-section --anchor-id heading:N rewrites a section's body
# Append / prepend at the very end / start of the document (no anchor needed):
wordlive append --text "Closing note." # new final paragraph
wordlive prepend --text "DRAFT" --inline # join the first paragraph
# Address anchors by ID (the IDs `outline`/`paragraphs` emit — `heading:N`, `para:N`, `bookmark:NAME`, `cc:NAME`):
wordlive replace --anchor-id heading:3 --text "Updated section text"
wordlive go-to --anchor-id bookmark:Address
# Durable handles — positional para:N/heading:N renumber under edits; pin for a stable id:
wordlive pin heading:3 --name methods # -> {"pin": "pin:methods", ...}
wordlive replace --anchor-id pin:methods --text "Survives later inserts"
# Explicit cursor surface (the non-preferred mode — deliberately moves the cursor):
wordlive cursor read # where is the cursor? which para:N?
wordlive cursor write --text "inserted here" # type at the cursor
# Styles + paragraph formatting (atomic-undo):
wordlive style list
wordlive style apply --anchor-id heading:3 --name "Heading 2"
wordlive format-paragraph --anchor-id heading:3 --alignment center --space-before 6
# Tables (cells are anchors: table:N:R:C):
wordlive table list
wordlive table read 1
wordlive table records 1 # body rows as {header: value} dicts
wordlive replace --anchor-id table:1:2:2 --text "$450"
wordlive table add-row --table 1 --values '["Lodging", "$600"]'
wordlive table append-record --table 1 --record '{"Item":"Lodging","Cost":"$600"}' # by header name
wordlive table update-row --table 1 --key Travel --values '{"Cost":"$450"}' # match a row by content
wordlive table create --anchor-id end --data '[["Item","Cost"],["Travel","$400"]]' --header
wordlive table create --anchor-id end \
--data '[{"Item":"Travel","Cost":"$400"}]' # records → keys are a header row
# --rows/--cols are inferred from --data (give them only to pad larger)
wordlive table autofit --table 1 --mode content # fit columns to cells (or window/fixed)
wordlive table set-style --table 1 --style "Grid Table 4 - Accent 1" # restyle an existing table
wordlive table set-alignment --table 1 --alignment center # the whole table across the page
wordlive table set-borders --table 1 --sides box,horizontal,vertical # the whole grid in one call
wordlive table set-banding --table 1 --first-row --banded-rows # Table Style Options
wordlive shading --anchor-id table:1:row:1 --fill "#2E86C1" # style a whole row (table:N:row:R)
wordlive format-run --anchor-id table:1:col:2 --bold # …or a whole column (table:N:col:C)
wordlive cell-valign --anchor-id table:1:2:2 --align bottom # a cell's vertical alignment
wordlive table delete 2
# Page / column / section breaks (explicit one-off mark; for a style use --page-break-before):
wordlive insert-break --anchor-id heading:3 --kind page
wordlive format-paragraph --anchor-id heading:3 --page-break-before
# Collaboration: comments + track changes (the polite, non-destructive surface):
wordlive comment add --anchor-id heading:3 --text "Please expand this." --author Bot
wordlive comment list
wordlive comment resolve --index 1
wordlive track on # record edits as revisions; `track off` to stop
wordlive revisions # read the tracked changes back (type/author/text/range)
wordlive revision accept --index 1 # …or accept / reject them
wordlive revision accept-all --anchor-id heading:3 # accept every change in one section
wordlive read text --anchor-id para:5 --view original # text as if changes were rejected
# …and `wordlive snapshot --markup all` renders those changes as visible marks.
# Non-visual layout introspection (reason about pages without a snapshot):
wordlive stats # pages/words/…/tables/images/comments + saved, one read
wordlive locate --anchor-id heading:8 # {page, end_page, line, column, in_table}
wordlive proofing # spelling/grammar errors + readability (Flesch, …)
wordlive read format --anchor-id heading:3 # effective style/paragraph/font, with override flags
wordlive lint # audit formatting/structure: severity-ranked findings (some fixable)
wordlive regularize # apply the fixable lint findings in one atomic-undo (--dry-run to preview)
# Checkpoint + diff — "what changed in session" (Word fires no content-change event):
wordlive checkpoint --out cp.json # fingerprint structure now → a stored token
# … agent or user edits …
wordlive diff --since cp.json # content-aligned change list (replace/insert/delete/restyle), each w/ current para:N
# Document metadata, variables, and the read mirrors of link / insert-field:
wordlive properties list # built-in + custom document properties
wordlive properties set --name Title --value "Q3 Report"
wordlive variables set --name ClientName --value "Acme" # invisible DOCVARIABLE storage
wordlive hyperlinks # every link: text, destination, range:START-END id
wordlive fields # every field: kind (PAGE/REF/TOC), code, rendered result
# Citations & bibliography (source → cite → build), and a legal table of authorities:
wordlive bibliography-style --style APA
wordlive add-source --type book --author "Smith, Jane" --title "On Risk" --year 2020 # → tag
wordlive insert-citation --anchor-id range:120-140 --tag Smith2020 --pages 15
wordlive insert-bibliography # works-cited block (update-fields to fill it)
wordlive mark-citation --anchor-id range:200-240 --long "Brown v. Board, 347 U.S. 483 (1954)" --category cases
wordlive table-of-authorities --category cases # build TOA from the marks (update-fields for pages)
# Document themes / branding (apply a theme, then set brand colours & fonts):
wordlive apply-theme --theme Facet # a built-in theme or a .thmx path (list-themes for names)
wordlive set-theme-colors --accent1 "#1A73E8" --accent2 "#34A853"
wordlive set-theme-fonts --major Arial --minor Calibri
wordlive theme # read back the 12 brand colours + major/minor fonts
# Lists & numbering (any anchor's paragraphs):
wordlive list apply --anchor-id heading:6 --type numbered
wordlive list restart --anchor-id heading:6
# Sections, headers & footers (header:S:WHICH / footer:S:WHICH):
wordlive sections
wordlive header write --section 1 --text "ACME Corporation"
wordlive footer read --section 1
# Images — from a file or base64 (--wrap is required: inline | auto | square | …):
wordlive insert-image --anchor-id heading:3 --path diagram.png --wrap auto
base64 logo.png | wordlive insert-image --anchor-id bookmark:Logo --base64 - --wrap inline --width 96
# …and read embedded pictures back out (image:N ids; for a vision model):
wordlive images # list every embedded picture
wordlive read-image --anchor-id image:1 --out logo.png # extract bytes + mime
# Floating-shape flourishes — a watermark behind every page, a pull-quote box:
wordlive watermark --text DRAFT # …or --remove
wordlive insert-text-box --anchor-id heading:2 --text "Key takeaway" --width 2.5in # → shape:N
# …then restyle any floating shape in place (text box, floating image, WordArt) by shape:N:
wordlive shapes # list shape:N ids, kind, size, rotation, z-order, wrap
wordlive set-shape-size --anchor-id shape:1 --width 3in --no-lock-aspect
wordlive format-shape --anchor-id shape:1 --fill navy --border-color white
wordlive set-shape-rotation --anchor-id shape:1 --degrees 15 # rotate; set-shape-z-order restacks
wordlive set-shape-wrap --anchor-id shape:1 --side left --distance-top 0.1in # which sides text flows past + standoff
wordlive set-shape-crop --anchor-id shape:2 --left 0.2in --bottom 6 # trim a picture shape in from its edges
wordlive replace-shape-image --anchor-id shape:2 --path v2.png # swap a floating picture in place
wordlive group-shapes --anchor-id shape:1 --anchor-id shape:2 # group → one shape:N (ungroup-shape reverses)
wordlive set-image-size --anchor-id image:1 --width 3in # resize an inline picture (no floating)
wordlive set-image-crop --anchor-id image:1 --right 0.1in # crop an inline picture in from its edges
# Equations — UnicodeMath (native), LaTeX (needs the `latex` extra), or MathML:
wordlive insert-equation --anchor-id heading:3 --unicodemath "x=(-b±√(b^2-4ac))/(2a)"
wordlive insert-equation --anchor-id heading:3 --latex "\frac{-b}{2a}" # pip install "wordlive[latex]"
wordlive equations # list equation:N ids, type, preview
# Charts — Excel-backed (needs Excel installed; data becomes static):
wordlive insert-chart --anchor-id end --kind bar --data '{"Q1": 10, "Q2": 25, "Q3": 18}' --title "Quarterly"
echo '[[1.2, 3.4], [2.5, 6.1]]' | wordlive insert-chart --anchor-id end --kind scatter --data -
wordlive charts # list chart:N ids, kind, title
# Format & design an existing chart (no Excel needed; tri-state):
wordlive format-chart --anchor-id chart:1 --chart-style 240 --legend --title "Quarterly"
wordlive format-axis --anchor-id chart:1 --which value --scale log --title "USD (M)"
wordlive add-trendline --anchor-id chart:1 --kind power --display-equation # law of best fit
wordlive set-series-color --anchor-id chart:1 --color "#2E86C1" --point 2
wordlive format-series --anchor-id chart:1 --marker circle --marker-size 8 --smooth # markers + smoothed line
wordlive add-error-bars --anchor-id chart:1 --kind percent --amount 5 # ± error bars
# Snapshot — render page(s) to PNG so a vision model can SEE the layout
# (needs the `snapshot` extra: pip install "wordlive[snapshot]"):
wordlive snapshot --anchor-id heading:3 --out section.png # the section's page(s)
wordlive snapshot --page 2 --out p2.png # one page
wordlive snapshot --pages 1-3 # base64 PNGs inline (JSON)
wordlive snapshot --max-dim 1000 # whole doc, low-res layout check
# Batch multiple ops in a single Ctrl-Z:
wordlive exec --script ops.json
# Save / hand back a deliverable — GATED behind a directory whitelist
# (--save-dir or WORDLIVE_SAVE_DIRS; with none set, saving is off):
wordlive --save-dir C:\out save-as C:\out\report.docx
wordlive --save-dir C:\out export-pdf C:\out\report.pdf # pixel-faithful PDF
Where ops.json looks like:
{
"label": "Update report",
"ops": [
{"op": "write_bookmark", "name": "Address", "text": "123 Main St"},
{"op": "write_cc", "name": "Signatory", "text": "Jane Doe"},
{"op": "insert_paragraph", "anchor_id": "heading:3", "text": "New risk paragraph."},
{"op": "replace", "anchor_id": "heading:3", "text": "Updated section text"},
{"op": "apply_style", "anchor_id": "heading:3", "name": "Heading 2"},
{"op": "format_paragraph", "anchor_id": "heading:3", "alignment": "center", "space_before": 6}
]
}
Exit codes: 0 ok, 1 other, 2 anchor-not-found, 3 Word-busy, 4 Word-not-running, 5 ambiguous-match (replace --find hit several), 6 Excel-not-available (insert-chart).
Agent skills
Setting up a specific tool (Claude Code, Claude Desktop, Cursor, …)? The Agents & LLM tools guide has copy-paste setup per client.
wordlive ships two LLM-facing skills (SKILL.md): wordlive-cli (the
command-line workflow) and wordlive-python (the import wordlive as wl API).
Each covers the anchor model, every verb, and the exit-code / exception contract.
An agent that hits wordlive --help is pointed straight at wordlive llm-help,
which prints the whole guide to stdout in one shot — no install step, no Word:
wordlive llm-help # the CLI guide
wordlive llm-help --python # the Python-API guide
Or drop the skill files into a project or your home directory so coding tools
discover them on their own (CLI skill by default; --python for just Python,
--both for both):
wordlive install-skill # ./.agents/skills/wordlive-cli/SKILL.md
wordlive install-skill --both # also drops wordlive-python/SKILL.md
wordlive install-skill --system # into ~/.agents/skills/ instead
MCP server (Claude Desktop & other agents)
Prefer MCP? wordlive ships a server so Claude Desktop and other MCP clients can
drive your open document directly. Three ways to set it up, easiest first:
1. One-click bundle. Download wordlive.mcpb (built from
mcpb/) and drop it
onto Claude Desktop → Settings → Extensions.
2. install-mcp. Register the server in your client's config in one command
(it uses uvx, so there's no separate install step):
wordlive install-mcp # → Claude Desktop's config
wordlive install-mcp --client claude-code # → ./.mcp.json
wordlive install-mcp --print # just print the JSON snippet
3. By hand. pip install "wordlive[mcp,snapshot]" (the snapshot extra adds
the vision tool), then add to claude_desktop_config.json:
{ "mcpServers": { "wordlive": { "command": "wordlive-mcp" } } }
Or, if you prefer uvx:
{ "mcpServers": { "wordlive": { "command": "uvx wordlive[mcp,snapshot]" } } }
It exposes four dispatch tools — word_read, word_write, word_exec, and
word_snapshot (which returns a rendered page as an image). The full op
vocabulary and anchor model are in the one-page guide, fetchable as a tool call
with word_read(command="guide") (also the wordlive://guide resource). Word
must be running on the same Windows machine. See
docs/mcp.md.
Examples
Runnable, out-of-the-box scripts live in
examples/ — Python
(using the library) and PowerShell (driving the CLI). Each attaches to the
document you already have open; the read-only and append-only ones are safe to
try on a real document.
python examples/python/read_outline.py # read-only: print the outline
python examples/python/append_note.py "Reviewed." # append one paragraph (atomic, polite)
.\examples\powershell\Show-Outline.ps1
.\examples\powershell\Invoke-WordliveWithRetry.ps1 write bookmark Address --text "123 Main St"
Design
- Politeness first — operations preserve the user's
Selection, view, and scroll. The user keeps editing alongside you. - Semantic anchors over
Selection— operations target bookmarks, content controls, or headings — never the live cursor unless you ask. - Atomic undo — every
doc.edit()opens a WordUndoRecord, so a single Ctrl-Z reverts the whole block. - Escape hatch — every wrapper exposes
.comfor the raw COM object; you're never blocked by missing coverage.
See spec.md for the full design.
Contributing & security
Contributions are welcome — see
CONTRIBUTING.md
for the dev setup (uv), the design invariants, and the test / lint / docs
gates. To report a security issue, please use private disclosure as described in
SECURITY.md
rather than a public issue.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file wordlive-0.18.0.tar.gz.
File metadata
- Download URL: wordlive-0.18.0.tar.gz
- Upload date:
- Size: 851.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
575daf356b9681c7e220e6cbfbaf58353ee1426cd8ea1dd613fa57474974c509
|
|
| MD5 |
1e16db204353749fd87c07fb7dbbbc07
|
|
| BLAKE2b-256 |
4d3b2a621afd62d27434e42a63ad51a660d65cd34593061f147973c038537d75
|
Provenance
The following attestation bundles were made for wordlive-0.18.0.tar.gz:
Publisher:
release.yml on thomas-villani/wordlive
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wordlive-0.18.0.tar.gz -
Subject digest:
575daf356b9681c7e220e6cbfbaf58353ee1426cd8ea1dd613fa57474974c509 - Sigstore transparency entry: 1934322979
- Sigstore integration time:
-
Permalink:
thomas-villani/wordlive@49cb1d181bc966b7152a462e5a8d825afe224f11 -
Branch / Tag:
refs/tags/v0.18.0 - Owner: https://github.com/thomas-villani
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@49cb1d181bc966b7152a462e5a8d825afe224f11 -
Trigger Event:
push
-
Statement type:
File details
Details for the file wordlive-0.18.0-py3-none-any.whl.
File metadata
- Download URL: wordlive-0.18.0-py3-none-any.whl
- Upload date:
- Size: 374.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3db12f4ef58731ff63c3abf2142a25b37eba0d790a665d27ed206f0584b164c5
|
|
| MD5 |
77e11c15e6079e99c1f1dadc83be6c6d
|
|
| BLAKE2b-256 |
d59c5a7316cee44f2fd8056aface89b756ed31ab2f5ac0e4cba7a7b5188d124a
|
Provenance
The following attestation bundles were made for wordlive-0.18.0-py3-none-any.whl:
Publisher:
release.yml on thomas-villani/wordlive
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
wordlive-0.18.0-py3-none-any.whl -
Subject digest:
3db12f4ef58731ff63c3abf2142a25b37eba0d790a665d27ed206f0584b164c5 - Sigstore transparency entry: 1934323023
- Sigstore integration time:
-
Permalink:
thomas-villani/wordlive@49cb1d181bc966b7152a462e5a8d825afe224f11 -
Branch / Tag:
refs/tags/v0.18.0 - Owner: https://github.com/thomas-villani
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@49cb1d181bc966b7152a462e5a8d825afe224f11 -
Trigger Event:
push
-
Statement type: