Libreoffice writer
Deterministic python skill suite for LibreOffice designed for AI agents
npx -y skills add dfk1352/LibreOffice-skills --skill libreoffice-writerAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Use when creating, editing, formatting, exporting, or extracting LibreOffice Writer (.odt) documents via UNO, including session-based edits, structured text targets, tables, images, lists, patch workflows, and snapshots.
SKILL.md
9.3 KB, as published. Nobody here has run it
LibreOffice Writer
Use the bundled writer modules for UNO-backed Writer document work.
All paths must be absolute. Bundled modules live under scripts/ in this
skill directory, so set PYTHONPATH=<skill_base_dir>/scripts.
If setup or runtime issues appear, check references/troubleshooting.md.
API Surface
# Non-session utilities
create_document(path, source=None) # source: path to .md file to import
export_document(path, output_path, export_format) # formats: "pdf", "docx", "md"
snapshot_page(doc_path, output_path, page=1, dpi=150)
# Session (primary editing API)
WriterSession(path) -> context manager
WriterSession methods:
read_text(target: WriterTarget | None = None) -> str
insert_text(text, target: WriterTarget | None = None)
replace_text(target: WriterTarget, new_text)
delete_text(target: WriterTarget)
format_text(target: WriterTarget, formatting: TextFormatting)
insert_table(rows, cols, data=None, name=None, target: WriterTarget | None = None)
update_table(target: WriterTarget, data)
delete_table(target: WriterTarget)
insert_image(image_path, width=None, height=None, name=None, target: WriterTarget | None = None)
update_image(target: WriterTarget, image_path=None, width=None, height=None)
delete_image(target: WriterTarget)
insert_list(items: list[ListItem], ordered: bool, target: WriterTarget | None = None)
replace_list(target: WriterTarget, items: list[ListItem], ordered: bool | None = None)
delete_list(target: WriterTarget)
set_metadata(values: dict[str, str])
get_metadata() -> dict[str, str]
patch(patch_text, mode="atomic") -> PatchApplyResult
export(output_path, export_format)
reset()
close(save=True)
# Standalone patch utility
patch(path, patch_text, mode="atomic") -> PatchApplyResult
Structured Targets: WriterTarget
from writer import WriterTarget
WriterTarget(
kind="text" | "insertion" | "table" | "image" | "list",
text=None,
after=None,
before=None,
occurrence=None,
name=None,
index=None,
)
Target kinds
| Kind | Supported fields | Use |
|---|---|---|
text | text, after, before, occurrence | Read, replace, delete, or format matched text |
insertion | text, after, before, occurrence | Insert at a resolved boundary or after a matched span |
table | name or index | Update/delete a table |
image | name or index | Update/delete an image |
list | text, after, before, occurrence | Replace/delete one logical list block |
Resolution rules
- Omit
targetto read the full document or append inserted content at the end. - Use
afterandbeforeto constrain a search window. - Use
occurrencewhen repeated text is expected; otherwise matching must be unique. - Prefer full sentences or distinctive paragraph-sized phrases for
text,after, andbeforeanchors; single-word anchors are often too brittle for realistic prose edits. - For table/image targets, prefer
name; useindexonly when order is stable. - For insertion after inline text, Writer inserts at the boundary after the matched span; paragraph breaks must come from the inserted text or the session helper.
Formatting Payload
from writer import TextFormatting
TextFormatting(
bold=None,
italic=None,
underline=None,
font_name=None,
font_size=None,
color=None, # named color or integer
align=None, # "left" | "center" | "right" | "justify" | "start" | "end"
line_spacing=None,
spacing_before=None,
spacing_after=None,
)
Notes:
- Character and paragraph formatting can be combined in one call.
- Paragraph properties such as
alignapply to the full paragraph containing the match, not just the exact matched span. - At least one formatting field must be set.
List Items
from writer import ListItem
ListItem(text="Confirm scope", level=0)
levelis zero-based nesting.- Nesting cannot skip levels.
ordered=Trueuses a numbering style;ordered=Falseuses bullets.
Metadata
set_metadata()accepts a dictionary of key-value pairs.- Supported keys are
title,author,subject,keywords, anddescription. keywordsmay be a comma-separated string;get_metadata()returns them as one comma-separated string.
Patch DSL
Use patch() or session.patch() to apply ordered operations.
[operation]
type = format_text
target.kind = text
target.text = Quarterly revenue grew 18%.
target.after = Financial Summary
target.before = Action Items
format.bold = true
format.align = center
[operation]
type = insert_list
target.kind = insertion
target.after = Action Items
list.ordered = false
items <<JSON
[
{"text": "Confirm scope", "level": 0},
{"text": "Review output", "level": 0},
{"text": "Update packaging", "level": 1}
]
JSON
Supported operation types
insert_textreplace_textdelete_textformat_textinsert_tableupdate_tabledelete_tableinsert_imageupdate_imagedelete_imageinsert_listreplace_listdelete_list
Patch value rules
- Use
target.*fields for target definition. - To append at the end of the document, omit all
target.*fields; do not use a baretarget.kind = insertionwithout anchors. - Use
format.*fields for formatting payloads. - Use
list.orderedplus JSONitemsfor list operations. itemsanddatamust be valid JSON.- Heredoc blocks are supported with
<<TAG ... TAGfor multiline text or JSON.
Modes
atomicstops on first failure, resets the session, and persists nothing.best_effortkeeps successful earlier operations and records failures.
PatchApplyResult fields:
modeoverall_status="ok" | "partial" | "failed"operations= list ofPatchOperationResultdocument_persisted
For standalone patch(path, ...), document_persisted means the changes were
saved to disk. For session.patch(...), it means the patch produced successful
mutations in the current open session state.
Example: Edit a Report in Session
from pathlib import Path
from writer import ListItem, TextFormatting, WriterSession, WriterTarget
from writer.core import create_document
output = str(Path("test-output/report.odt").resolve())
create_document(output)
with WriterSession(output) as session:
session.insert_text(
"Executive Summary\n\n"
"Financial Summary\n\n"
"Quarterly revenue grew 18%.\n\n"
"Action Items"
)
session.format_text(
WriterTarget(
kind="text",
text="Quarterly revenue grew 18%.",
after="Financial Summary",
before="Action Items",
),
TextFormatting(bold=True, align="center"),
)
session.insert_list(
[
ListItem(text="Confirm scope", level=0),
ListItem(text="Review output", level=0),
ListItem(text="Update packaging", level=1),
],
ordered=False,
target=WriterTarget(kind="insertion", after="Action Items"),
)
Example: Patch an Existing Document
from writer import patch
result = patch(
"/abs/path/report.odt",
"""
[operation]
type = replace_text
target.kind = text
target.text = Draft
new_text = Final
[operation]
type = update_table
target.kind = table
target.name = Summary
data = [["Metric", "Value"], ["Revenue", "$2M"]]
[operation]
type = replace_list
target.kind = list
target.text = Confirm scope
items = [{"text": "Approve release", "level": 0}, {"text": "Notify team", "level": 1}]
list.ordered = true
""",
mode="best_effort",
)
print(result.overall_status)
Snapshots
from pathlib import Path
from writer import snapshot_page
result = snapshot_page(doc_path, "/tmp/page1.png", page=1, dpi=150)
print(result.file_path, result.width, result.height)
Path(result.file_path).unlink(missing_ok=True) # clean up used snapshots
Use snapshots to verify layout after formatting, list edits, image placement, or table changes.
- Prefer
session.export()while a document is already open in aWriterSession. - Use standalone
export_document()when exporting an existing file without keeping a session open. get_metadata()normalizeskeywordsinto one comma-and-space separated string.
Common Mistakes
- Passing a relative path; UNO-facing Writer APIs expect absolute file paths.
- Omitting
occurrencefor repeated text and then getting an ambiguity error. - Using anchors that are too short or too common; prefer full-sentence or paragraph-level anchor text plus
after/beforebounds when possible. - Expecting
alignto apply only to a phrase; Writer applies paragraph alignment to the containing paragraph. - Supplying malformed JSON in
itemsordatapatch fields. - Forgetting to clean up captured snapshots after inspection.
- Calling
session.export()or other methods aftersession.close(). - The Markdown filter for markdown import / export requires LibreOffice 26.2+.
- The
"start"and"end"paragraph alignment options require LibreOffice 26.2+.