agentsclimarketplace

Libreoffice writer

Skill dfk1352/LibreOffice-skills/skills/libreoffice-writer

Deterministic python skill suite for LibreOffice designed for AI agents

Install
npx -y skills add dfk1352/LibreOffice-skills --skill libreoffice-writer

Assembled 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

KindSupported fieldsUse
texttext, after, before, occurrenceRead, replace, delete, or format matched text
insertiontext, after, before, occurrenceInsert at a resolved boundary or after a matched span
tablename or indexUpdate/delete a table
imagename or indexUpdate/delete an image
listtext, after, before, occurrenceReplace/delete one logical list block

Resolution rules

  • Omit target to read the full document or append inserted content at the end.
  • Use after and before to constrain a search window.
  • Use occurrence when repeated text is expected; otherwise matching must be unique.
  • Prefer full sentences or distinctive paragraph-sized phrases for text, after, and before anchors; single-word anchors are often too brittle for realistic prose edits.
  • For table/image targets, prefer name; use index only 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 align apply 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)
  • level is zero-based nesting.
  • Nesting cannot skip levels.
  • ordered=True uses a numbering style; ordered=False uses bullets.

Metadata

  • set_metadata() accepts a dictionary of key-value pairs.
  • Supported keys are title, author, subject, keywords, and description.
  • keywords may 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_text
  • replace_text
  • delete_text
  • format_text
  • insert_table
  • update_table
  • delete_table
  • insert_image
  • update_image
  • delete_image
  • insert_list
  • replace_list
  • delete_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 bare target.kind = insertion without anchors.
  • Use format.* fields for formatting payloads.
  • Use list.ordered plus JSON items for list operations.
  • items and data must be valid JSON.
  • Heredoc blocks are supported with <<TAG ... TAG for multiline text or JSON.

Modes

  • atomic stops on first failure, resets the session, and persists nothing.
  • best_effort keeps successful earlier operations and records failures.

PatchApplyResult fields:

  • mode
  • overall_status = "ok" | "partial" | "failed"
  • operations = list of PatchOperationResult
  • document_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 a WriterSession.
  • Use standalone export_document() when exporting an existing file without keeping a session open.
  • get_metadata() normalizes keywords into one comma-and-space separated string.

Common Mistakes

  • Passing a relative path; UNO-facing Writer APIs expect absolute file paths.
  • Omitting occurrence for 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 / before bounds when possible.
  • Expecting align to apply only to a phrase; Writer applies paragraph alignment to the containing paragraph.
  • Supplying malformed JSON in items or data patch fields.
  • Forgetting to clean up captured snapshots after inspection.
  • Calling session.export() or other methods after session.close().
  • The Markdown filter for markdown import / export requires LibreOffice 26.2+.
  • The "start" and "end" paragraph alignment options require LibreOffice 26.2+.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.