Create and edit documents. Every action is data — a fixed operation over the values you pass — and every input path you pass is {{input_location}}.
**Actions**
- `create` — write a new file. `format` is `docx`, `xlsx`, `pptx` or `pdf`. For docx/pptx/pdf pass `content`; for xlsx pass `sheets`, because a created spreadsheet is its sheets of cells — images belong in a document, a presentation or a PDF.
- `fill_template` — copy a user's own sample (`template`, a .docx/.pptx/.xlsx sample) and replace its `{name}` placeholders with `values` (an object keyed by placeholder name; each value is text, a number or a boolean). The sample itself is never modified, only copied, and the copy keeps the sample's own extension.
- `docx_edit` — edit an existing `.docx`/`.docm` (`path`). `edits` is a list applied in order, each object an `op` and its fields, and an edit is applied whole or not at all: a field the op does not take, a value a property does not know and anything else an edit cannot honour is refused by name rather than passed over. Text ops address a fragment that must be the document's own body text, matched inside one paragraph and never across paragraphs: a text box's own text and a table cell's are addressed, while a header, a footer, a footnote, an endnote, a comment and a field's cached result are not addressed at all; every occurrence of the fragment in every matching paragraph is rewritten, while `remove_paragraph` removes every paragraph that holds it. What a reading prints AROUND the text it shows is the reader's own printing, not text in the file — the labels and annotations a reading adds ({{docx_label_marks}}) and the marks it prints in brackets ({{docx_inline_marks}}), plus the indentation printed under a block — so a miss that carries one is refused for what it is: the reader's printing, not text the document holds — while text a header, a footnote or another part really holds is named as that part's instead, and a fragment the part's own text spells is never called the reader's printing. A tab and a line break a reading shows are markup of its own in the file (`<w:tab/>`, `<w:br/>`, `<w:cr/>`) — or, where the reading broke a line because the file's paragraph ended, a paragraph of its own — not text an edit addresses: a fragment holding one is refused with that reason. A rewrite — `replace_text`, `insert_text` or `remove_text` — whose fragment would match across a tab or a break is refused too, because writing across it would leave the tab or the break with nothing to belong to — name a fragment on one side of it; `format_text` changes no text, so a fragment it names may reach across one. A fragment found nowhere is refused with the reason it was not found: the part of the document that holds it, the paragraph boundary it crosses, the tracked revision that holds its text, the reader's indentation or label it carries, the tab or the line break it holds, or — when none of those fit — that the body, every header, footer, footnote, endnote and comment were searched. The text ops are: `{"op": "replace_text", "find": "…", "replace": "…"}`, `{"op": "insert_text", "find": "…", "insert": "…", "position"?: "after"|"before"}`, `{"op": "remove_text", "find": "…"}`, `{"op": "format_text", "find": "…", "bold"?: true, "italic"?: true, "underline"?: true, "strike"?: true, "size"?: {{text_size_points}} points, "color"?: "RRGGBB"}`, `{"op": "format_paragraph", "find": "…", "align"?: {{paragraph_alignments}}, "style"?: "…", "indent_left"?: N, "indent_right"?: N, "indent_first_line"?: N, "spacing_before"?: N, "spacing_after"?: N, "line_spacing"?: N, "list"?: {{paragraph_lists}}, "level"?: 0 to {{paragraph_level_max}}}`, `{"op": "add_paragraph", "text": "…", "after"?: "…"}` and `{"op": "remove_paragraph", "find": "…"}`. A `format_text` names at least one property: `bold`, `italic`, `underline` and `strike` are toggles whose `false` removes the property — a style that is bold stays bold, and a run already drawn with a richer underline than the plain one, or with a double strike, keeps what it draws — `size` is in points and `color` is {{color_digits}} hex digits with an optional `#`. The run is the smallest unit formatting is stated on, so a fragment covering part of a run formats the whole of that run and the answer says so; a recorded revision of a run's formatting is left exactly as it is, a request that changes nothing writes nothing at all, and a document whose tracked changes are turned on says so in the answer, since what the call writes is plain content rather than a recorded revision. A `format_paragraph` names at least one property and gives every paragraph whose text holds the fragment the ones it names: `align` ({{paragraph_alignments}}), `style` (a paragraph style the document defines, at most {{paragraph_style_max}} characters — `Normal`, `Heading 1`, `Heading 2` and `Heading 3` always work, and a name the document does not define is refused with the styles it does define listed), `indent_left`, `indent_right` and `indent_first_line` in points ({{paragraph_indent_points}}), a first-line indent below zero being the hanging indent Word states for it, `spacing_before` and `spacing_after` in points ({{paragraph_spacing_points}}), `line_spacing` as the line's own height in points ({{paragraph_line_points}}), and `list` — `"bullet"` or `"number"` with an optional `level` from 0 to {{paragraph_level_max}}, for which the tool uses the document's own list definition of that kind for that level and writes the one it needs when the document holds none, or `false`, which takes the paragraph's list away — the numbering its own style gives it included — while a `level` with no list to belong to is refused. Paragraph properties are the paragraph's own, so they land on a paragraph inside a table cell as they do on one in the body, the table keeps the shape it had, and a paragraph takes them whole even when the fragment covers part of its text. An `add_paragraph` `text` is added after the first paragraph that holds the `after` fragment among the document's own paragraphs — a table cell's is one, a text box's is not, and such an `after` is refused with the place named — and the new paragraph takes the style, the paragraph and run formatting and the list membership of the paragraph it follows, so a line added to a list stays a list item and one added after a heading is a heading; the section break such a paragraph may end is not copied to it; `pptx_edit`'s `after` anchors only in the slide's own text and refuses a table. Without `after` the paragraph goes at the end of the body and carries no formatting. `insert_text` puts the text after the fragment unless `position` says `before`. A `remove_paragraph` is refused when it would leave a table cell or a text box with no paragraph — use `remove_text` to take such a paragraph's text out instead — and when the paragraph it would remove holds a page break or ends a section, since the break would go with it.
- `xlsx_edit` — edit an existing `.xlsx`/`.xlsm` (`path`). Sheets are named; rows are 1-based numbers up to {{sheet_row_max}}, columns are letters up to the {{sheet_column_max}}th, and a row or column insert position may be one past the sheet's used rows or columns while a delete position must be inside them (`insert_row` on a sheet with no rows is refused, and so is an insert on a sheet that already reaches the last row or column, because a line pushed past the grid would have to keep the last line's own address). `{"op": "set_cell", "sheet": "Sheet1", "cell": "B2", "value": …, "number_format"?: "…"}`, where `value` is a string, a number, a boolean or `{"formula": "…"}` and a `number_format` is at most {{number_format_max}} characters, written beside the value it accompanies — giving a cell a format without writing a value is the `format_cells` op's. `{"op": "clear_cell", "sheet": …, "cell": …}` takes the cell's content and leaves the cell's own formatting as it is. `{"op": "insert_row"|"delete_row", "sheet": …, "row": N}` and `{"op": "insert_column"|"delete_column", "sheet": …, "column": "B"}`. `{"op": "format_cells", …}` is what gives a table its look, and it is the one op that changes how cells appear without touching what they hold. It names exactly one target: a cell or a rectangular range of cells (`"range": "B2"`, `"range": "B2:D2"`, corners in either order, at most {{format_cells_max}} cells), a column's width (`"column": "B"`, in characters as Excel shows one, {{column_width_chars}}) or a row's height (`"row": N`, in points, {{row_height_points}}). A range target carries `"font"?: "Arial"` (the face name, at most {{font_name_max}} characters), `"size"?: {{cell_font_points}} points`, `"bold"?: true`, `"italic"?: true`, `"color"?: "RRGGBB"` (the font's own colour, an optional `#` and {{color_digits}} hex digits), `"fill"?: "RRGGBB"` (the cell's fill, the same form) or `false` (takes the fill away), `"border"?: an object naming one or more of {{cell_border_sides}} as its keys, each `true` or `false` — each named side alone is given a thin border (`true`) or has its own border taken away (`false`), while every side the object does not name keeps the border it has; an object naming no side is refused, `"align"?: {{cell_alignments}}` (horizontal), `"vertical"?: {{cell_verticals}}`, `"wrap"?: true|false` and `"number_format"?: "…"`. A column target carries only `width` and a row target only `height`; a `format_cells` naming none of the properties its target takes is refused. What is named is the only thing that changes: the cell keeps its number format, its fill, its borders, its wrap and its alignment otherwise, including a look it inherits from its row or its column, which is written onto the cell rather than lost, a border side is set or taken away on its own, and a named property stated as `false` is what takes it away. `format_cells` writes no value and moves no address, so a formula and a chart's cached numbers are none of its business; a cell it formats that was empty is created as a styled cell, and the sheet's declared extent then covers it. A `.xlsx` with no saved styles of its own is given what it needs rather than refused. A range that touches a merged cell is widened to cover every merged cell it touches whole — cells the range itself does not name — and the answer says how far it was widened; a range the widening would take past {{format_cells_max}} cells is refused.
- `pptx_edit` — edit an existing `.pptx`/`.pptm` (`path`). A slide is its 1-based number as the reader shows it, and a text op names a fragment of text that must be on it — whitespace alone names nothing and is refused — matched inside one paragraph's text and never across paragraphs. The labels the reader prints — {{ppt_slide_label}}, {{ppt_notes_label}}, {{ppt_title_mark}}, {{ppt_hidden_slide_mark}}, {{ppt_diagram_text_mark}}, {{ppt_diagram_text_lost_mark}} and {{ppt_no_text_mark}} — are not text on the slide, nor is the indentation the reader prints under a block, nor the text inside a diagram (which no edit changes) or a fragment the speaker notes hold (`replace_notes` and `remove_notes` edit those): an op that names one is refused with that reason, not as text the slide does not hold. Text: `{"op": "replace_text"|"remove_text", "slide": N, "find": "…"}` (`replace_text` also takes `"replace"`), `{"op": "format_text", "slide": N, "find": "…", "bold"?: true, "italic"?: true, "underline"?: true, "size"?: {{slide_text_size_points}} points, "color"?: "RRGGBB", "align"?: {{slide_alignments}}}` (at least one property; the run carries the run properties and the paragraph the alignment, so naming a fragment that covers only part of either changes all of it and the answer says which, and a `false` toggle writes the property off on the run rather than removing it, so it takes away a bold, italic or underline the placeholder, layout or theme would have given the run), `{"op": "add_paragraph", "slide": N, "text": "…", "after"?: "…", "level"?: 0-{{paragraph_level_max}}}`, `{"op": "remove_paragraph", "slide": N, "find": "…"}`. A fragment holding a tab or a line break the reading shows is refused for what it is: a tab and a line break are markup of its own in the file (`<a:tab/>`, `<a:br/>`), not text an edit addresses; so is a fragment that reaches across a paragraph boundary, and a rewrite (`replace_text`, `remove_text`, `replace_notes`, `remove_notes`) whose fragment would match across a tab or a line break is refused too, because it would leave the tab or the break with nothing to belong to — name a fragment on one side of it. Slides: `{"op": "add_slide", "after"?: N, "title"?: "…", "bullets"?: ["…"]}` (at most {{bullets_max}} bullets), `{"op": "delete_slide", "slide": N}`, `{"op": "move_slide", "slide": N, "to": M}` (`to` is the position the slide holds afterwards, so the slides between the two shift with it, and a move to the position a slide already holds is refused because it would change nothing) and `{"op": "duplicate_slide", "slide": N}` (a copy right after the source, with the source's own content and notes, which a later edit changes one of without changing the other). Images: `{"op": "add_image", "slide": N, "path": "…", "x"?: fraction, "y"?: fraction, "width"?: fraction, "height"?: fraction}` — a PNG or JPEG placed and sized as fractions of the slide's own width and height (a place {{slide_position_fraction}}, a size {{slide_size_fraction}}), so the deck's size is never needed: an unnamed place is the slide's middle and an unnamed size is the largest that fits it, and naming only one of `width` and `height` keeps the image's own proportions (a place named without a size still fits the whole slide, so a place away from the middle can push part of the picture past the slide's edge — name a size to keep it inside). Speaker notes: `{"op": "replace_notes"|"remove_notes", "slide": N, "find": "…"}` (`replace_notes` also takes `"replace"`) and `{"op": "add_notes", "slide": N, "text": "…"}`, which appends a paragraph and writes the notes slide itself when the slide has none (a presentation with no notes master at all is refused). A `move_slide` or a `duplicate_slide` is refused when the presentation's slide list does not number its slides the way the reader shows them. An `add_paragraph` writes into the slide's own text, never into a table, and its `after` anchors after the first paragraph of that text which holds it; an `after` that names text only inside a table is refused. A `remove_paragraph` is refused when it would leave a shape's or a table cell's text body with no paragraph — use `remove_text` to take such a paragraph's text out instead.
- `pdf_merge` — merge 2+ PDFs (`files`) into one, in order; at most {{merge_inputs}} files, and at most {{merge_mb}} MB in total, in one call.
- `pdf_split` — split one PDF (`path`) into one file per range in `ranges`, e.g. `["1-3", "5"]`; at most {{split_parts}} ranges in one call.
- `pdf_rotate` — add `degrees` to `pages`: a multiple of `{{degrees_step}}`, taken modulo 360, and a whole turn is refused because it would change nothing.
- `pdf_text` — draw `text` on `pages`. Optional `x`, `y` (points), `size`, `color` (`#` plus `{{color_digits}}` hex digits), `stamp` (draw a box) and `rotate`.
- `pdf_image` — place `image` (a PNG or JPEG) on `pages`. Optional `x`, `y`, `width`, `height` (points); giving one of `width`/`height` keeps the image's proportions.
- `pdf_form_fill` — fill the PDF's form fields with `values` (each value is text, a number or a boolean); `flatten` bakes them into the page content.
`pages` is `"all"` or a list like `"1-3,5"`; it defaults to all pages. `file_name` is optional and only a base name — the tool appends the extension (on `fill_template` and the edit actions, the sample's or input's own), sanitizes it to one path component, makes it unique, and drops a trailing extension of a kind it produces rather than doubling it. Output always lands in the workspace `generated/` directory.
**Editing** (`docx_edit`/`xlsx_edit`/`pptx_edit`) changes a copy, never the file you pass: the result is a new file in `generated/` and keeps the input's own extension (a `.docm`/`.xlsm`/`.pptm` copy carries its macros over, because the macros are parts like any other). Every part an edit does not name keeps the content it had — images, fields, headers and footers, comments and anything else the operation never touched survive as they were. Each edit works on what the file really holds — a fragment that must be in the body, a cell by sheet and address, a slide by number — and the edits apply in the order given, so an earlier one can change what a later one finds. An edit object carries exactly the fields its op names — an unknown property, or an extra one, is refused rather than ignored. An edit that reaches markup the call never named — a section or page break, a bookmark, a link, a note, a comment, an unaccepted revision — says so in the answer, and one that would delete a section or page break is refused instead. A call carries at most {{edits_max}} edits and any text in one at most {{edit_text_max}} characters; the work one call costs is bounded, so an edit list that would walk the parts far more than one call can is refused with a hint to split it rather than run into the kit's own time limit. What the kit could not keep is named in the answer — a loss or a caveat is never passed off as success. An old `.doc`/`.xls`/`.ppt` is read but never edited: say so plainly instead of substituting another file. An encrypted, password-protected file cannot be opened at all and is refused with the password-protected answer.
**Editing limits.** An edited package is not rebuilt byte for byte — its parts are re-compressed — and aggregate formatting (styles, themes, a presentation's layouts and animations) and complex objects (charts, pivot tables, macros) are not rebuilt and may keep the values or shape they had. Formatting is applied only where an edit names it — a text fragment (bold, italic, underline, size and colour, plus strikethrough in a document and the paragraph's alignment in a presentation), a paragraph (its alignment, indents, spacing, style and list), a cell (its font, fill, borders, alignment, wrap and number format), a column's width or a row's height — but a run is the smallest unit a text edit rewrites, so formatting a fragment that sits inside a longer run formats that whole run, and a recorded revision of a run's formatting is left untouched. A formatting edit whose fragment matches in more than one place says how many places took it. Reading a presentation or a workbook back gives its text or its values, not its formatting, so what an edit applied is confirmed by the file it wrote rather than by reading it again. A text edit works on a paragraph's runs, so replacing text that spans several runs written with different formatting takes the first run's formatting for the whole replacement, which the answer says; removing text substitutes nothing and so takes no formatting with it. A row or column shift moves everything a sheet anchors to its rows and cells: the sheet's extent, merges, an autofilter, conditional formats and data validations with the references their formulas hold (whole-column and whole-row ranges included, a reference the delete took becoming `#REF!`, and the extended copies Excel writes beside a rule in its `x14:`/`xm:` spelling), protected ranges, ignored errors, hyperlinks, whole column ranges, page breaks and the view state. No address it moves leaves the sheet's grid, and a counted group it empties — merges, column entries, hyperlinks, data validations, protected ranges, ignored errors — goes with its last child instead of staying behind as one the format calls corrupt. What names cells without moving with them is left alone and named in the answer: a reference to another sheet keeps its text, cell formulas keep theirs while the cells they name move, and so do the workbook's defined names, the sheet's table ranges, its comments, their shapes and drawing anchors, and the formulas other sheets write about this one. A shift never rewrites a cell's formula, and the answer says how many it left naming the old cells; a conditional format, data validation, protected range or selection whose whole range the delete covered is named as gone with it. Charts and pivot tables are not rebuilt: one that reads the changed cells keeps the values it cached, said whenever the call really changed the sheet's content.
**Content blocks** (docx/pptx/pdf), one object each:
- `{"type": "heading", "level": 1, "text": "…"}` — level {{heading_levels}}.
- `{"type": "paragraph", "text": "…"}`
- `{"type": "list", "items": ["…"], "ordered": true}`
- `{"type": "table", "headers": ["…"], "rows": [["…"]]}` — either may be given on its own and the table needs at least one non-empty one; every bullet and every cell is text, a number or a boolean, and an empty header list or an empty row is treated as absent rather than laid out.
- `{"type": "image", "path": "…", "width": 300, "height": 200}` — width/height in px, optional, {{image_side_px}}; giving one of them keeps the image's proportions, and with neither the image keeps its own size capped to a width the page's text has room for.
- `{"type": "notes", "text": "…"}` — a presentation's speaker notes (pptx only).
**Sheets** (xlsx): `[{"name": "Sheet1", "rows": [["Cell", 42, true, {"formula": "SUM(A1:A2)"}]]}]`. A sheet's `name` is optional — without one it becomes `Sheet<n>` by position — and must be at most {{sheet_name_max}} characters, hold none of {{sheet_name_forbidden}}, and be unique ignoring case. A cell is a string, a number, a boolean, or `{"formula": "SUM(A1:A2)"}`. Text is always text — a string that starts with `=` stays a string. A formula is written as a formula and is never evaluated here; the application that opens the file computes it.
Embedded images must be PNG or JPEG — the file's whole content, not just its name. A size you pass (`size`, `width`, `height`) must be {{pdf_size_points}} points, and a position (`x`, `y`) must be within ±{{pdf_point_abs_max}} points. Paths (`template`, `path`, `files`, `image`, and image blocks) are {{input_location}}, and an input must be under {{input_mb}} MB.
**Limits.** A PDF is laid out as a simple stream — the blocks follow one another down the page, and columns, floating objects and exact page placement are not attempted. Marks, stamps and images drawn onto an existing PDF become page content rather than annotation objects, and saving such a file drops tagging and some service metadata; text inside a finished PDF is never reflowed to make room for what you draw. A table and a presentation sample keep everything they already hold — filling only substitutes placeholders — and a substitute value never becomes a formula: in a text document or a presentation it is written as text, and in a spreadsheet a cell that holds exactly one placeholder takes a number as a number.
Each call produces at most the files its one action names; `pdf_split` produces several. The produced file is attached to your reply as a file — mention it in your answer (its `[FILE:…]` marker is what delivers it), or it may be swept away.