office
Use whenever a Word document (.docx), spreadsheet (.xlsx), slide deck (.pptx) or PDF in the workspace is to be written, filled in, edited or commented on and the office_edit tool is available: the user watches your edits appear live in the file pane, under your name, and may edit
Install
npx skills add https://github.com/invergent-ai/surogates/tree/master/skills/productivity/office
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install invergent-ai-surogates@llmmart
git clone https://github.com/invergent-ai/surogates.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole invergent-ai/surogates collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Working on documents live with the user
The document is open in the user's file pane, or opens there once your first edit to it has run (once per document:
a user who closes it is not pulled back). Every office_edit call appears whole, under your name, while the user
watches and may edit the same document. Work like a colleague in a shared document, not like a program writing a file.
Where the document is
- Name a document by its path relative to the workspace, the folder the file pane shows. The same on a folder of the
user's computer and in a project:
office_editalways reaches the real document, never a thread's copy. Never~,$HOMEor an absolute path outside that folder. - Nothing to look up first:
office_editmakes a missing document, andoffice_readsays when there is none. Nolist_files, terminal or other tool to find or check the place. office_readis how you check what you wrote: once it shows your change, the document has it. Your own files can lag the editor or not have the document at all (a project thread's copy of the folder gets it at its next turn): never look for it withread_file,list_filesor the terminal.- You cannot see pages or slides. Never render or convert the document to check it: the user is looking at it live.
The workflow
- Read first.
office_readbefore any edit, and again whenever an edit is refused: its outline gives what the ops target (block ids and hashes, sheets and ranges, slide ids and hashes, pages and fields). - One piece of work per call: a section, a block of cells, a slide or two, a set of fields. Between calls, say one short line in the chat ("Building the pricing slide.").
- Change only what was asked.
- When an edit is refused, nothing of the call applied. The result says why, and shows what you targeted as it is now: someone edited it, and their edit wins. Read again and work from what is there; never put your old version back over theirs. A user editing the very cells or slide you want: wait, or ask them.
- Never touch an open document any other way. No
write_file,patch, terminal, python libraries or the docx, xlsx, pptx or pdf skills on it: the editor is its only writer. - Finish with one line in the chat saying what you did and what the user can do next. The work belongs in the document, not in the chat.
Each kind of document
- Word documents: plant a new document's outline first, every section a pending block saying what it will hold,
then fill one section per call; re-word with
replaceon quoted words, which keeps their formatting. Seereferences/word.md. - Spreadsheets: read the range before writing it, and pass what it showed as
expect. Values go in as typed into a cell: a formula starts with=and stays live. Seereferences/cell.md. - Slide decks: a small change (a title, a word, a number) is
texton that one shape, which keeps its place and style. A new or redesigned slide is one<section>in the Claude Slides format, built as native shapes the user can edit. A slide too full for its boxes is refused: split it; nothing shrinks. Seereferences/slide.md. - PDFs: fill fields, add, remove or rotate pages, redact, annotate. Existing text cannot be changed. See
references/pdf.md.
When a comment calls you
A message that starts [Comment on <file>] is a person who mentioned you in a comment in that document: the words in
« » are theirs, quoted from it. "On it, I'll reply here when done." is already posted in the thread, and the note with
the message says which thread it is, or, in a PDF, how to find it.
- Read the document with
office_read, and the thread in its outline: the mention may be a reply, with the question earlier in the thread. - Do what it asks with
office_edit, as for any request. - Reply in the thread with only the result, in one or two sentences: "Fixed: Q1 North is 120, from the March export." Never your reasoning, the steps or the conversation: those go in the chat. A reply over 400 characters is refused. If you cannot do it, say why in one sentence, and leave the thread open.
- Resolve the thread last, in the same call as the reply or after it: a resolve can close the person's open reply box.
- Finish with one line in the chat, as after any request.
Writing rules
- Lead with the answer; a reader skimming the first line of each section gets the point.
- Specifics over adjectives: numbers, names, dates.
- Short paragraphs; lists for parallel items; a table when the reader compares across rows.
- Use the document's own styles (headings, lists, table style): never fake a heading with bold text.
Files (surogates)
-
references
-
cell.md 3.3 KB
# Spreadsheets (.xlsx) ## Reading - `office_read` view `outline`: every sheet with its used range, its first two rows as shown, merged cells and charts (name, type, title, data range, anchor). Protected sheets and a protected workbook say so. - view `range` with `target: "Sales!A1:D20"`: the cells **as shown** (`values`), the formulas of the cells that have one, and the number formats that are not General. A long range comes in parts: view its `next` as the next `target`. - view `search` with `query`: every cell whose shown text holds the words, as `Sheet!A1`. A range always names its sheet: `Sales!A1:D20`, `'Q3 plan'!B2`. ## Writing `set` writes a block of cells. Give its top-left cell, or the whole range, and the rows of values. Each value goes in exactly as typed into the cell: - `"=SUM(B2:B5)"` is a live formula; `120` and `"120"` are numbers; `"2026-03-01"` is a date; `"50%"` is a percent. - A leading `'` keeps text as text: `"'0042"` stays `0042`. - `""` or `null` empties a cell. Pass `expect`: the range's `values` exactly as `office_read` showed them. If anyone changed a cell since, nothing applies and the result shows the range as it is now. ```json {"op": "set", "range": "Sales!A6", "values": [["West", 50, 60, "=B6+C6"]], "expect": [["", "", "", ""]]} ``` ```json {"op": "set", "range": "Sales!A7:D7", "values": [["Total", "=SUM(B2:B6)", "=SUM(C2:C6)", "=SUM(D2:D6)"]]} ``` `format` styles a range: `number_format` (an Excel code), `bold`, `italic`, `wrap`, `fill` (`"#RRGGBB"` or `"none"`), `color`, `align` (left, center, right, justify), `valign` (top, center, bottom), `width` (column width in characters). ```json {"op": "format", "range": "Sales!A1:D1", "bold": true, "fill": "#14285A", "color": "#FFFFFF"} ``` ```json {"op": "format", "range": "Sales!B2:D7", "number_format": "#,##0", "align": "right", "width": 12} ``` `chart` adds a chart from a range whose first row names the series and whose first column holds the categories. Its `type` is column, bar, line, pie, area, doughnut or scatter; `anchor` is the cell its top-left corner sits on. The result gives its name. ```json {"op": "chart", "range": "Sales!A1:C5", "type": "column", "title": "Q1 and Q2 by region", "anchor": "F2"} ``` `sheet` adds (after a named sheet, or last), renames (`to` the new name) or deletes a sheet. A call changes each sheet at most once, and sheet changes apply before the call's other ops, so a sheet added in a call can be written in the same call. ```json {"ops": [{"op": "sheet", "action": "add", "name": "Plan", "after": "Sales"}, {"op": "set", "range": "Plan!A1", "values": [["Hire two engineers"]]}]} ``` ## Refusals - **Someone is editing a cell you write:** the call names them and the cell. Try again in a moment, or ask them. - **A protected sheet or workbook:** only the user can lift the protection. ## Comments The outline lists the comment threads, open ones first: each one's `id`, its `cell`, the author, the text, the latest replies and whether it is resolved. Reply and resolve in a call of their own: ```json {"ops": [{"op": "reply", "thread": "{F80AA885-C2F8-8A96-7622-6B76D3A85AB7}", "body": "Fixed: Q1 North is 120, from the March export."}, {"op": "resolve", "thread": "{F80AA885-C2F8-8A96-7622-6B76D3A85AB7}"}]} ``` A reply states only the result, in one or two sentences (at most 400 characters). Resolve last. -
pdf.md 2.9 KB
# PDFs (.pdf) ## Reading - `office_read` view `outline`: every page with its size in points, its rotation, its first line, its form fields (name, type, value, rect; a checkbox's value is true or false, as `fill` takes it) and annotations. A long PDF comes in parts: continue with `start`. - view `page` with `target: <page number>`: the page's text. - view `search` with `query`: the pages that hold the words. Rects are `[x1, y1, x2, y2]` in points (1/72 inch) **from the page's top-left corner**, y growing down: a field 20 pt tall, 60 pt below the top of an A4 page, is `[x, 60, x + width, 80]`. ## Editing A call's ops apply together or not at all, and each finds the document as the ops before it left it (a page added by an earlier op counts). ```json {"op": "fill", "fields": {"client": "ACME Lisbon Lda", "signed": true}, "expect": {"client": null, "signed": false}} ``` ```json {"op": "add_field", "type": "text", "page": 1, "rect": [170, 250, 420, 270], "name": "po_number", "value": "PO-4471"} ``` ```json {"op": "pages", "action": "add", "page": 2} {"op": "pages", "action": "rotate", "page": 3, "degrees": 90} {"op": "pages", "action": "remove", "page": 4} ``` ```json {"ops": [{"op": "redact", "text": "account number 4471", "match_case": false}]} ``` ```json {"op": "annotate", "kind": "free_text", "page": 1, "rect": [60, 560, 280, 600], "text": "Checked against the order."} {"op": "annotate", "kind": "stamp", "page": 1, "rect": [300, 360, 450, 410], "stamp": "approved"} ``` - `fill`: text for a text field, true or false for a checkbox; the names are the outline's. Pass `expect`: each field's value as the outline showed it (null when empty). A field someone changed since your read refuses the call: read again and work from its value now. - `pages`: `add` puts a blank page after `page` (0 for the first); `rotate` turns a page to 0, 90, 180 or 270 degrees. - `redact` removes every occurrence of the words. It goes in a call of its own: one redaction, nothing beside it. **Once the document saves, they are gone for good.** If someone has redaction marks of their own pending, the call is refused: ask the user to apply or remove them first. - `annotate`: a `free_text` note, or a `stamp`: approved, not_approved, draft, final, confidential, for_comment, information_only, completed, void, sign_here. **Existing text cannot be changed.** To correct a PDF's words, tell the user; offer a note, a redaction or a new page. ## Comments A comment in a PDF is an annotation, usually a sticky note (`text`), and its replies. The outline lists each page's annotations with their `id`, author, text, latest replies and whether they are resolved. Reply and resolve in a call of their own: ```json {"ops": [{"op": "reply", "thread": "1_1", "body": "March: the contract says 1 March 2026."}, {"op": "resolve", "thread": "1_1"}]} ``` A reply states only the result, in one or two sentences (at most 400 characters). Resolve last. -
slide.md 12.1 KB
# Slide decks (.pptx) You write each slide in the **Claude Slides format**: one `<section>` of HTML on a fixed canvas, every style inline. runtime-office lays it out exactly as the editor will, then builds it as native PowerPoint shapes, text boxes, pictures and tables at the measured places: the user gets a slide they can click into and edit, not a picture. ## Reading - `office_read` view `outline`: the canvas (`[1920, 1080]` on a 16:9 deck; a 4:3 deck is 1920 × 1440) and every slide's id, number, title, layout, shape count, whether it has notes or is hidden, and its **hash**. - view `slide` with `target: <id>`: every shape, in canvas px, with its text. - view `search` with `query`: the slides that hold the words. ## Editing - `text`: the text of one shape, by its slide's `id` and `hash` and the shape's `id` from view `slide`; everything else of the slide stays as it is. Use it for a small change: a title, a word, a number. - `slides`: add slides from HTML (`after` a slide id, `"start"`, or last), or replace one in place with its `id` and the `hash` the outline gave: that rebuilds the whole slide from your HTML, so use it to redesign a slide, not to change its words. A slide the user changed since you read it refuses the call: read it again. A new deck's one empty title slide gives way to the first slides you add. - `order`: every slide's id, in the order wanted (a call of its own). - `remove`: slides by `id` and `hash`. At most ten slides per call; one or two per call lets the user watch the deck grow. ```json {"op": "slides", "slides": [{"after": "start", "html": "<section style=\"background:#14213D;color:#FBFBF8;font-family:Georgia, serif;padding:128px;display:flex;flex-direction:column;justify-content:center;gap:32px\"><h1 style=\"font-size:120px;font-weight:600;line-height:1.1\">Lisbon office</h1><p style=\"font-size:44px;color:#BFD8D5\">Why, when and what it costs</p><aside>Open with the decision we need today.</aside></section>"}]} ``` ```json {"op": "slides", "slides": [{"html": "<section style=\"background:#FBFBF8;color:#1A1A1A;font-family:Arial, sans-serif;padding:128px;display:flex;flex-direction:column;gap:48px\"><h2 style=\"font-size:72px;font-weight:600;line-height:1.1\">Two numbers</h2><div style=\"display:flex;flex-direction:row;gap:32px\"><div style=\"flex:1;background:#FFFFFF;border:2px solid #E3E6E4;padding:40px;display:flex;flex-direction:column;gap:12px\"><p style=\"font-size:96px;font-weight:600;color:#2F5597\">12</p><p style=\"font-size:32px\">engineers by June</p></div><div style=\"flex:1;background:#FFFFFF;border:2px solid #E3E6E4;padding:40px;display:flex;flex-direction:column;gap:12px\"><p style=\"font-size:96px;font-weight:600;color:#2F5597\">€1.2M</p><p style=\"font-size:32px\">first-year cost</p></div></div><p style=\"position:absolute;left:128px;bottom:64px;width:1664px;font-size:24px;color:#6A7179\">Source: finance plan, March</p></section>"}]} ``` ```json {"op": "remove", "slides": [{"id": "1_402", "hash": "ab12cd34"}]} ``` ```json {"op": "order", "ids": ["1428", "1_630", "1473"]} ``` ### Changing text ```json {"op": "text", "slide": "1473", "hash": "dc3285c8", "shape": "1482", "text": "Q3 plan"} ``` The shape keeps its place, size, placeholder, alignment and font: the new text takes the style of its first run. Each line of `text` is a paragraph, set in the style of the shape's paragraph in that place (a line past them, as its last one). Several shapes of one slide change in one call, each its own op with the hash you read. A picture, a table or a shape without a text box has no text to change: rebuild its slide with `slides`. ## The format **The slide** is `<section style="…">` with, always, a `background`; the text defaults (`font-family`, `color`); and its layout: `display:flex; flex-direction:column` (the default) or `display:grid`, `padding:128px` (the margins: 1664 × 824 inside on 16:9), `gap`, `align-items`, `justify-content`. Children flow in it. `position:absolute` pins a child to the slide (`left`, `top`, `right`, `bottom`, `width`, `height`; give pinned text a `width`). Flow content paints as one layer where its first flow child sits: a pinned backdrop listed before it paints below, anything pinned after it above. In a `position:relative` div, pinned children paint over its flow. `hidden` skips the slide; `data-transition="fade| push|magic"` is how it arrives from the slide before it. **Text:** `<h1>` `<h2>` `<h3>` `<p>` (start at 96/64/44/32 px; set `font-size` yourself, **never under 24 px**), `<ul>`/`<ol>` of plain `<li>` (one level), `<br>`, and inline `<b>` `<i>` `<u>` `<a href="https://…">` `<span style="color:…">` (no size, font, background or border on them: a badge is a `<div>` of its own). A `<div>` holding only text and those inline tags is one text box of one paragraph, set by its own font, colour, alignment, background, border and padding; pieces its own flex or grid layout sets apart go in a `<p>` each. `<div data-text>` of plain `<p>`/`<ul>`/`<ol>` is one text box of several paragraphs. Text beside blocks in one div, or directly in the section, is refused: put it in a `<p>`. Inside a heading, paragraph, list item, table cell or div of text go only `<b>` `<i>` `<u>` `<a>` `<span>` `<br>`: no icon, picture or other block. A label is a `<p>` with `background` and `padding`: it stays one shape. Text wraps only at spaces, so size a box to its longest word (about 0.6 × the font size per character). **Too much for its box? The slide is refused: split it. Nothing shrinks.** **Containers:** `<div>` is a flex column unless it says `display:flex` (a row) or `display:grid`; it is invisible unless painted (`background`, `border`, `box-shadow`). An empty `<div style="flex:1">` is a spacer. At most 15 nested divs. **Pictures:** `<img src="<workspace path>" alt="…" style="width; height; object-fit:cover|contain">`, a PNG, JPEG or SVG from the workspace. `data-crop="ZOOM X% Y%"` crops. `<svg aria-label>` (at most 52 KB) is drawn as a picture. An SVG, from a file or inline, is plain shapes: no script, foreignObject or `on…` attribute, no `<!ENTITY>` or other DTD declaration, and no link but to `#…` or to a PNG or JPEG as data. matplotlib's SVGs go in as they are. **Tables:** `<table>` of `<tr>` with `<th>` (the first row) and `<td>`, plain text, no colspan or rowspan. Column widths: `width:N%` on every cell of the first row. A `<tr>` or a cell, not the `<table>`, may carry a solid `background` colour, never a translucent one or a gradient: the editor draws one opaque colour. A row is about 2.1 × the font size per line. **Shapes:** a painted `<div>` is a rectangle; `<hr>` is a line; `<x-shape kind="rect|ellipse|diamond|arrow-right| arrow-left|arrow-up|arrow-down|line">` fills its box. `<x-connector x1 y1 x2 y2 route="straight|hv|vh|elbow" head-end="open|filled">` draws a line between points measured from the slide (or a `position:relative` box), so it is a direct child of that box. Between two boxes in a row, leave the points out and give it a width: `<x-connector head-end="filled" style="width:48px;height:0;color:#FBFBF8;border-width:3px">` runs across the gap it takes up (a height, in a column). `<x-icon name="…" style="color; width; height">`, name one of Activity Book Chart Chat Check CheckCircle Clock Cloud Code Database Globe GraduationCap Home Key Lightbulb Lightning Link Lock PaperPlane Play Search Settings Star ThumbsUp Tool Trust Users Verified Warning Wrench. Nothing goes inside an `<x-shape>`, `<x-icon>`, `<x-connector>` or `<hr>`: a label goes in a text element beside it. `<x-embed>` (a static HTML and CSS snippet, pinned as a direct child of the section, at most 16 KB and 8 per slide) goes in as a picture of what it shows. Scripts in it do not run: draw a chart as an inline `<svg>`. **Styles:** lengths in px (no `em`, `rem`, `vw`, `%` only where a box's share is meant); colours as hex or `rgb[a]()`; `linear-gradient` and `repeating-linear-gradient` backgrounds; `border` (solid, dashed, dotted, double); `box-shadow`; `opacity`; `transform: rotate()`, `translate()`, `scale()`; `letter-spacing`, `text-transform`, `text-decoration`, `-webkit-text-stroke`, gradient text (`background-clip:text`); `margin` (not negative), which moves a box as CSS does. No `z-index`, no classes or `<style>`. A radial gradient is a picture behind its box's text. A filter or a blend mode on anything, opacity on a div that holds children, and a turn, a scale or a skew on such a div or on a table make the element and everything in it one picture, which looks right but which the user cannot edit: keep them for decoration, and fade or turn single shapes and text boxes instead (a turned text box stays native; a scaled or skewed one is a picture). A blurred shadow is a picture behind its shape, which stays editable. **Motion:** `data-build-in` / `data-build-out="fade|rise|drop|left|right|scale|pop [order N] [auto]"` on the section's pinned children. A magic move takes both of these: `data-transition="magic"` on the **second** slide's `<section>` (it is how that slide arrives; it goes on a `<section>` only), and the same `id` on a pinned element of each slide. The element then moves from its place on the first slide to its place on the second: ```json {"op": "slides", "slides": [{"html": "<section style=\"background:#14213D;color:#FBFBF8;font-family:Georgia, serif;padding:128px\"><h2 style=\"font-size:72px;font-weight:600;line-height:1.1\">Where we are</h2><div id=\"next-step\" style=\"position:absolute;left:128px;top:760px;width:560px;background:#F2C14E;color:#14213D;padding:32px\"><p style=\"font-size:32px\">Next: the budget</p></div></section>"}, {"html": "<section data-transition=\"magic\" style=\"background:#14213D;color:#FBFBF8;font-family:Georgia, serif;padding:128px\"><h2 style=\"font-size:72px;font-weight:600;line-height:1.1\">The budget</h2><div id=\"next-step\" style=\"position:absolute;left:1232px;top:760px;width:560px;background:#F2C14E;color:#14213D;padding:32px\"><p style=\"font-size:32px\">Next: the budget</p></div></section>"}]} ``` A build or a magic-move `id` inside another moving element is refused, and so is a table inside a moving box: give the table its own motion. A connector or a build inside a box that goes in as one picture (see Styles) is refused too. **Notes:** one `<aside>` of plain text, the section's last child. A slide you replace keeps its notes when its HTML has no `<aside>`; an `<aside>` replaces them, and an empty `<aside></aside>` clears them. **Fonts:** the document server's own, so the text sets exactly as measured. A `font-family` list resolves to its first font the server has, then the generic: Arial, Calibri, Cambria, Georgia, Montserrat, Noto Sans, Open Sans, Times New Roman, Verdana, Trebuchet MS and Courier New are there; `sans-serif` is Arial, `serif` Times New Roman. At most 200 elements per slide. ## Designing the deck - **One idea per slide.** A statement beats bullets; turn lists into tables, card rows, big numbers or quotes. - **A type scale of four or five sizes** (for example 120 / 72 / 44 / 32 / 24) and one or two fonts; emphasise with weight, italic or colour, not a new size. - **A hex palette, chosen once:** one dark, one light, one or two accents; toned whites and blacks (#FBFBF8, #14213D) rather than pure. Text holds 4.5:1 contrast on its background (3:1 from 44 px). - **The vertical space adds up:** 824 px inside the margins on 16:9. A heading is about size × lines × 1.1; a card is lines × size × line height plus its padding. Leave a card's height out and let it fit its text. - **Too much? Split the slide.** Nothing shrinks. - **Square corners.** Never round a card, label, frame or picture: no `border-radius`, no `kind="rounded"`. - Keep repeated elements in the same place on every slide of a kind, the heading at the top margin. ## Comments The outline lists the comment threads, open ones first: each one's `id`, its `slide` number, the author, the text, the latest replies and whether it is resolved. Reply and resolve in a call of their own: ```json {"ops": [{"op": "reply", "thread": "{9D24FA3F-798C-22EB-4B89-84EF55C02C52}", "body": "Moved the start to March, as the lease says."}, {"op": "resolve", "thread": "{9D24FA3F-798C-22EB-4B89-84EF55C02C52}"}]} ``` A reply states only the result, in one or two sentences (at most 400 characters). Resolve last. -
word.md 4.8 KB
# office_edit for Word documents: formats and examples ## Working on a Word document - Target blocks by the `id` and `hash` the outline gives. Quote words with `find` only for a phrase inside a block. - A new document: one call inserts the title and every section's heading, each followed by a pending block saying what it will hold. The user sees the whole shape at once and can delete a section before you write it. Then fill one section per call, replacing its pending block. A pending block the user deleted is a section they do not want: leave it out. - Re-word with `replace` on quoted words; replace a whole block only when it all changes. - Ops in one call run in order, each on what the earlier ops left: a block an earlier op replaced, moved or deleted is gone for the later ones (a moved block gets a new id, found in that op's result `ids`), and one it re-worded has a new hash. - For a length the user asks for, count words: about 450 words of body text fill a page. ## Read ```json {"path": "reports/Q3.docx"} {"path": "reports/Q3.docx", "view": "outline", "start": 200} {"path": "reports/Q3.docx", "view": "block", "target": "853"} {"path": "reports/Q3.docx", "view": "search", "query": "revenue"} ``` The outline lists blocks as `{"id", "hash", "kind", "text"}`, where `text` is the first words only; a long document comes in pages, continue with `start` = its `next`. Kinds include `heading1`–`heading9`, `paragraph`, `bullet`, `numbered`, `table`, `image`, `pending` (with `key` and `intent`). Comment threads come with their `id`, a string. The `block` view gives one block's exact text, tabs and spaces kept (a table's cells): copy the words for a `{"find": …}` target from it. `find` searches the whole document and `nth` counts its exact matches (case and spacing kept) from the top, from 1: quote enough words to be unique (the `search` view ignores case, so its count can differ). ## Plant a new document's outline ```json {"path": "reports/Q3.docx", "ops": [{"op": "insert", "target": {"end": true}, "content": { "markdown": "# Q3 report\n\n## Summary\n<?block summary?>\n\n## Results\n<?block results?>\n\n## Risks\n<?block risks?>", "blocks": { "summary": {"type": "pending", "intent": "Three sentences: what happened this quarter and what it means"}, "results": {"type": "pending", "intent": "Revenue and costs by region, with a chart"}, "risks": {"type": "pending", "intent": "What could slip next quarter, and what we do about it"} }}}]} ``` ## Fill one section ```json {"path": "reports/Q3.docx", "ops": [{"op": "replace", "target": {"pending": "results"}, "content": { "markdown": "Revenue grew **12%** in the north and 8% in the east.\n\n<?block chart?>\n\n| Region | Revenue | Growth |\n|---|---|---|\n| North | 4.2M | 12% |\n| East | 3.1M | 8% |", "blocks": {"chart": {"type": "chart", "chart": "bar", "title": "Revenue by region", "rows": [["", "Q2", "Q3"], ["North", 3.75, 4.2], ["East", 2.87, 3.1]]}}}}]} ``` A picture from the workspace: `{"type": "image", "path": "charts/funnel.png", "width": 480}` (PNG or JPEG, at most 5 MB). ## Change what is there ```json {"op": "replace", "target": {"find": "revenue target", "nth": 1}, "content": {"text": "sales goal"}} {"op": "insert", "target": {"block": "853", "hash": "a1b2c3d4"}, "side": "after", "content": {"markdown": "One more paragraph."}} {"op": "replace", "target": {"block": "861", "hash": "0f9e8d7c"}, "content": {"markdown": "A rewritten paragraph."}} {"op": "delete", "target": {"block": "870", "hash": "11aa22bb"}} {"op": "move", "target": {"block": "880", "hash": "33cc44dd"}, "to": {"block": "853", "hash": "a1b2c3d4"}, "side": "before"} ``` A list you insert starts its own numbering. A block you replace or move keeps the comment threads anchored in it: over the same words when they are still in it, otherwise over the whole new block. ## Comments ```json {"op": "comment", "anchor": {"find": "grew 12%"}, "body": "Source: the regional sales export, 3 Oct."} {"op": "reply", "thread": "4697b1d5", "body": "Updated the figure to 12.4%, from the October export."} {"op": "resolve", "thread": "4697b1d5"} ``` `thread` is a comment's `id` from the outline, or the `thread` a `comment` op returned. The outline lists each thread's latest replies. A reply in a thread states only the result, in one or two sentences (at most 400 characters): the reasoning and the conversation stay in the chat. ## Refusals `{"applied": false, "refused": {"op": 1, "reason": "…", "block": {…}}}` means nothing of the call applied, and no co-editor saw any of it. `op` is the refused op's place in `ops`, counting from 0. `block` is how the targeted block reads now, or null when the `reason` is something else (a block someone deleted is "gone", quoted words are no longer there). Read again, then edit from what is there.
-
-
SKILL.md 5.2 KB
--- name: office description: "Use whenever a Word document (.docx), spreadsheet (.xlsx), slide deck (.pptx) or PDF in the workspace is to be written, filled in, edited or commented on and the office_edit tool is available: the user watches your edits appear live in the file pane, under your name, and may edit beside you. Covers drafting a document section by section, filling and formatting sheets, building slides in the Claude Slides format, and filling, redacting or annotating a PDF. Use the docx, xlsx and pptx skills only when the office tools are not available." requires_tools: [office_edit] --- # Working on documents live with the user The document is open in the user's file pane, or opens there once your first edit to it has run (once per document: a user who closes it is not pulled back). Every `office_edit` call appears whole, under your name, while the user watches and may edit the same document. Work like a colleague in a shared document, not like a program writing a file. ## Where the document is - Name a document by its path relative to the workspace, the folder the file pane shows. The same on a folder of the user's computer and in a project: `office_edit` always reaches the real document, never a thread's copy. Never `~`, `$HOME` or an absolute path outside that folder. - Nothing to look up first: `office_edit` makes a missing document, and `office_read` says when there is none. No `list_files`, terminal or other tool to find or check the place. - `office_read` is how you check what you wrote: once it shows your change, the document has it. Your own files can lag the editor or not have the document at all (a project thread's copy of the folder gets it at its next turn): never look for it with `read_file`, `list_files` or the terminal. - You cannot see pages or slides. Never render or convert the document to check it: the user is looking at it live. ## The workflow 1. **Read first.** `office_read` before any edit, and again whenever an edit is refused: its outline gives what the ops target (block ids and hashes, sheets and ranges, slide ids and hashes, pages and fields). 2. **One piece of work per call**: a section, a block of cells, a slide or two, a set of fields. Between calls, say one short line in the chat ("Building the pricing slide."). 3. **Change only what was asked.** 4. **When an edit is refused,** nothing of the call applied. The result says why, and shows what you targeted as it is now: someone edited it, and their edit wins. Read again and work from what is there; never put your old version back over theirs. A user editing the very cells or slide you want: wait, or ask them. 5. **Never touch an open document any other way.** No `write_file`, `patch`, terminal, python libraries or the docx, xlsx, pptx or pdf skills on it: the editor is its only writer. 6. **Finish with one line** in the chat saying what you did and what the user can do next. The work belongs in the document, not in the chat. ## Each kind of document - **Word documents:** plant a new document's outline first, every section a pending block saying what it will hold, then fill one section per call; re-word with `replace` on quoted words, which keeps their formatting. See `references/word.md`. - **Spreadsheets:** read the range before writing it, and pass what it showed as `expect`. Values go in as typed into a cell: a formula starts with `=` and stays live. See `references/cell.md`. - **Slide decks:** a small change (a title, a word, a number) is `text` on that one shape, which keeps its place and style. A new or redesigned slide is one `<section>` in the Claude Slides format, built as native shapes the user can edit. A slide too full for its boxes is refused: split it; nothing shrinks. See `references/slide.md`. - **PDFs:** fill fields, add, remove or rotate pages, redact, annotate. Existing text cannot be changed. See `references/pdf.md`. ## When a comment calls you A message that starts `[Comment on <file>]` is a person who mentioned you in a comment in that document: the words in « » are theirs, quoted from it. "On it, I'll reply here when done." is already posted in the thread, and the note with the message says which thread it is, or, in a PDF, how to find it. 1. Read the document with `office_read`, and the thread in its outline: the mention may be a reply, with the question earlier in the thread. 2. Do what it asks with `office_edit`, as for any request. 3. Reply in the thread with **only the result**, in one or two sentences: "Fixed: Q1 North is 120, from the March export." Never your reasoning, the steps or the conversation: those go in the chat. A reply over 400 characters is refused. If you cannot do it, say why in one sentence, and leave the thread open. 4. Resolve the thread **last**, in the same call as the reply or after it: a resolve can close the person's open reply box. 5. Finish with one line in the chat, as after any request. ## Writing rules - Lead with the answer; a reader skimming the first line of each section gets the point. - Specifics over adjectives: numbers, names, dates. - Short paragraphs; lists for parallel items; a table when the reader compares across rows. - Use the document's own styles (headings, lists, table style): never fake a heading with bold text.
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.