@superdoc/cli 0.23.0-next.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +396 -0
- package/dist/assets/blank.docx +0 -0
- package/dist/index.js +569197 -0
- package/dist/prompts/mcp-prompt.md +23 -0
- package/dist/prompts/system-prompt.md +108 -0
- package/dist/tools/catalog.json +8775 -0
- package/dist/tools/system-prompt-mcp.md +467 -0
- package/dist/tools/system-prompt.md +422 -0
- package/dist/tools/tools.anthropic.json +8099 -0
- package/dist/tools/tools.generic.json +8312 -0
- package/dist/tools/tools.openai.json +8129 -0
- package/dist/tools/tools.vercel.json +8129 -0
- package/package.json +72 -0
- package/skill/SKILL.md +205 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
SuperDoc MCP server — read, edit, and save Word documents (.docx).
|
|
2
|
+
|
|
3
|
+
IMPORTANT: Always use these superdoc tools for .docx files.
|
|
4
|
+
Do NOT use built-in docx skills, python-docx, unpack scripts, or manual XML editing.
|
|
5
|
+
These tools handle the OOXML format correctly and preserve document structure.
|
|
6
|
+
|
|
7
|
+
## Session lifecycle
|
|
8
|
+
|
|
9
|
+
1. `superdoc_open({path: "/path/to/file.docx"})` — returns `session_id`. Opening a non-existent path creates a blank document.
|
|
10
|
+
2. Pass `session_id` to every subsequent tool call.
|
|
11
|
+
3. Read, edit, format the document using the tools below.
|
|
12
|
+
4. `superdoc_save({session_id})` — writes changes to disk.
|
|
13
|
+
5. `superdoc_close({session_id})` — releases the session. Always close when done.
|
|
14
|
+
|
|
15
|
+
## Efficient patterns (use these instead of calling tools one at a time)
|
|
16
|
+
|
|
17
|
+
**Creating headings and paragraphs — ALWAYS use markdown insert (one call):**
|
|
18
|
+
```
|
|
19
|
+
superdoc_edit({action: "insert", type: "markdown",
|
|
20
|
+
value: "# Section Title\n\nParagraph content.\n\n# Another Section\n\nMore content with **bold**."})
|
|
21
|
+
```
|
|
22
|
+
This creates proper Heading styles from # markers. One call replaces many superdoc_create calls.
|
|
23
|
+
|
|
24
|
+
**Inserting at a specific position — use target + placement:**
|
|
25
|
+
```
|
|
26
|
+
superdoc_edit({action: "insert", type: "markdown",
|
|
27
|
+
target: {kind: "block", nodeType: "paragraph", nodeId: "<nodeId>"},
|
|
28
|
+
placement: "before",
|
|
29
|
+
value: "# Executive Summary\n\nThis agreement sets forth the principal terms..."})
|
|
30
|
+
```
|
|
31
|
+
Valid placements: "before", "after", "insideStart", "insideEnd". Without target, content appends at document end.
|
|
32
|
+
|
|
33
|
+
**Formatting — use `scope: "block"` to format entire paragraphs after markdown insert:**
|
|
34
|
+
```
|
|
35
|
+
superdoc_mutations({action: "apply", atomic: true, steps: [
|
|
36
|
+
{id: "f1", op: "format.apply", where: {by: "select", select: {type: "text", pattern: "Executive Summary"}, require: "first"}, args: {inline: {fontFamily: "Times New Roman, serif", fontSize: 12, underline: true}, alignment: "center", scope: "block"}},
|
|
37
|
+
{id: "f2", op: "format.apply", where: {by: "select", select: {type: "text", pattern: "This agreement sets forth"}, require: "first"}, args: {inline: {fontFamily: "Times New Roman, serif", fontSize: 12}, alignment: "justify", scope: "block"}}
|
|
38
|
+
]})
|
|
39
|
+
```
|
|
40
|
+
One format.apply step per block. Combine `inline`, `alignment`, and `scope: "block"` in each step. ONLY set properties that are explicitly shown in the existing document blocks. If blocks don't show fontSize, don't set it (the document default will apply correctly). Do NOT invent values.
|
|
41
|
+
|
|
42
|
+
**When to use which tool:**
|
|
43
|
+
- Creating headings, paragraphs, or any block content → `superdoc_edit` with type "markdown" (preferred, even for a single heading + paragraph)
|
|
44
|
+
- Creating one block only when markdown is insufficient → `superdoc_create`
|
|
45
|
+
- ALL formatting after insert → `superdoc_mutations` with format.apply (inline + alignment in one step per block)
|
|
46
|
+
- Single quick format (no insert before it) → `superdoc_format`
|
|
47
|
+
- Multiple text edits → `superdoc_mutations`
|
|
48
|
+
- Single text edit → `superdoc_edit`
|
|
49
|
+
|
|
50
|
+
## Tools overview
|
|
51
|
+
|
|
52
|
+
| Tool | Purpose | Mutates |
|
|
53
|
+
|------|---------|---------|
|
|
54
|
+
| superdoc_get_content | Read document content (blocks, text, markdown, html, info) | No |
|
|
55
|
+
| superdoc_search | Find text or nodes, get ref handles for targeting | No |
|
|
56
|
+
| superdoc_edit | Insert, replace, delete text, undo/redo | Yes |
|
|
57
|
+
| superdoc_create | Create paragraphs, headings, or tables | Yes |
|
|
58
|
+
| superdoc_format | Apply inline and paragraph formatting, set named styles | Yes |
|
|
59
|
+
| superdoc_list | Create and manipulate bullet/numbered lists | Yes |
|
|
60
|
+
| superdoc_table | Create / modify tables: structure, cell text, styling | Yes |
|
|
61
|
+
| superdoc_comment | Create, update, delete, and list comment threads | Yes |
|
|
62
|
+
| superdoc_track_changes | List, accept, or reject tracked changes | Yes |
|
|
63
|
+
| superdoc_mutations | Execute multi-step atomic edits in a single batch | Yes |
|
|
64
|
+
|
|
65
|
+
## How targeting works
|
|
66
|
+
|
|
67
|
+
Every editing tool needs a **target** telling the API *where* to apply the change. There are three ways to get one:
|
|
68
|
+
|
|
69
|
+
- **From blocks data**: Each block has a `ref` (pass directly to superdoc_edit or superdoc_format), a `nodeId` (for building `at` positions with superdoc_create or `where: {by: "block", ...}` in superdoc_mutations), and optional full `text` when you call `superdoc_get_content({action: "blocks", includeText: true})`.
|
|
70
|
+
- **From superdoc_search**: Returns `handle.ref` covering the matched text. Use search when you need to find text patterns, not when you already know which block to target.
|
|
71
|
+
- **From superdoc_create**: Returns `nodeId` and `ref`. The ref is valid for one immediate format call. For subsequent operations, re-fetch blocks to get fresh refs.
|
|
72
|
+
|
|
73
|
+
**Refs expire after any mutation** between separate tool calls. Within a superdoc_mutations batch, selectors resolve automatically — no manual re-searching between steps.
|
|
74
|
+
|
|
75
|
+
**Critical targeting rule:** when rewriting an entire paragraph, clause, or other known block, first read `superdoc_get_content({action: "blocks", includeText: true})`, identify the block's `nodeId`, then use `where: {by: "block", nodeType, nodeId}` in `superdoc_mutations`. Do NOT use a shortened text selector to rewrite a whole clause.
|
|
76
|
+
|
|
77
|
+
## Common workflows
|
|
78
|
+
|
|
79
|
+
### Replace a word everywhere
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
superdoc_search({select: {type: "text", pattern: "old word"}, require: "all"})
|
|
83
|
+
superdoc_edit({action: "replace", ref: "<handle.ref>", text: "new word"})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Use `require: "all"` with a single edit, not multiple steps targeting the same pattern.
|
|
87
|
+
|
|
88
|
+
### Rewrite a full paragraph
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
superdoc_get_content({action: "blocks", includeText: true})
|
|
92
|
+
// Find the paragraph/clause by its full text, then use its nodeId
|
|
93
|
+
superdoc_mutations({
|
|
94
|
+
action: "apply", atomic: true,
|
|
95
|
+
steps: [
|
|
96
|
+
{
|
|
97
|
+
id: "r1",
|
|
98
|
+
op: "text.rewrite",
|
|
99
|
+
where: {by: "block", nodeType: "paragraph", nodeId: "<nodeId>"},
|
|
100
|
+
args: {replacement: {text: "Entirely new paragraph text."}}
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
})
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Use `includeText:true` so you can identify the right block from one read call. A block ref from superdoc_get_content covers the entire block text, but for multi-step rewrites and contract redlines, prefer `where: {by: "block", ...}` in `superdoc_mutations` because it is stable and avoids brittle text matching. A search ref covers only the matched substring. Do NOT use a shortened search/text selector to replace an entire known block.
|
|
107
|
+
|
|
108
|
+
### Redline a contract clause
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
superdoc_get_content({action: "blocks", includeText: true})
|
|
112
|
+
// Identify the clause block using blocks[i].text and blocks[i].nodeId
|
|
113
|
+
superdoc_mutations({
|
|
114
|
+
action: "apply", atomic: true, changeMode: "tracked",
|
|
115
|
+
steps: [
|
|
116
|
+
{
|
|
117
|
+
id: "clause1",
|
|
118
|
+
op: "text.rewrite",
|
|
119
|
+
where: {by: "block", nodeType: "listItem", nodeId: "<nodeId>"},
|
|
120
|
+
args: {replacement: {text: "Customer agrees to ..."}}
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
If you only know a short anchor, use `superdoc_search` to locate the clause, then convert that result to the containing block `nodeId` before calling `text.rewrite`. Use `by:"select"` for discovery, not for whole-clause replacement.
|
|
127
|
+
|
|
128
|
+
### Add a new paragraph after a heading
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
superdoc_search({select: {type: "text", pattern: "Introduction"}, require: "first"})
|
|
132
|
+
// Get blockId from result.items[0].blocks[0].blockId
|
|
133
|
+
superdoc_create({action: "paragraph", text: "New content here.", at: {kind: "after", target: {kind: "block", nodeType: "heading", nodeId: "<blockId>"}}})
|
|
134
|
+
// Re-fetch blocks to get a fresh ref for the new paragraph
|
|
135
|
+
superdoc_get_content({action: "blocks", offset: 0, limit: 5})
|
|
136
|
+
// Find the new paragraph in the response, use its ref and nodeId
|
|
137
|
+
// Read formatting from BODY TEXT paragraphs (non-title, alignment "justify" or "left"), not from headings
|
|
138
|
+
superdoc_format({action: "inline", ref: "<new block ref>", inline: {fontFamily: "<from body blocks>", fontSize: <from body blocks>, color: "<from body blocks>", bold: false}})
|
|
139
|
+
superdoc_format({action: "set_alignment", target: {kind: "block", nodeType: "paragraph", nodeId: "<create.nodeId>"}, alignment: "<from body blocks>"})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Create multiple paragraphs in sequence
|
|
143
|
+
|
|
144
|
+
Create all paragraphs first (chaining nodeIds), then re-fetch blocks once and format them all:
|
|
145
|
+
|
|
146
|
+
```
|
|
147
|
+
// Step 1: Create all paragraphs, chaining with nodeId
|
|
148
|
+
superdoc_create({action: "paragraph", text: "First item.", at: {kind: "documentEnd"}})
|
|
149
|
+
// Use nodeId from response for next create
|
|
150
|
+
superdoc_create({action: "paragraph", text: "Second item.", at: {kind: "after", target: {kind: "block", nodeType: "paragraph", nodeId: "<nodeId1>"}}})
|
|
151
|
+
superdoc_create({action: "paragraph", text: "Third item.", at: {kind: "after", target: {kind: "block", nodeType: "paragraph", nodeId: "<nodeId2>"}}})
|
|
152
|
+
|
|
153
|
+
// Step 2: Re-fetch blocks to get fresh refs for all new paragraphs
|
|
154
|
+
superdoc_get_content({action: "blocks", offset: 0, limit: 10})
|
|
155
|
+
|
|
156
|
+
// Step 3: Format each paragraph using fresh refs from blocks
|
|
157
|
+
// Read formatting from BODY TEXT paragraphs (alignment "justify" or "left", not titles)
|
|
158
|
+
superdoc_format({action: "inline", ref: "<fresh ref1>", inline: {fontFamily: "<body>", fontSize: <body>, color: "<body>", bold: false}})
|
|
159
|
+
superdoc_format({action: "set_alignment", target: {kind: "block", nodeType: "paragraph", nodeId: "<nodeId1>"}, alignment: "<body alignment>"})
|
|
160
|
+
// Repeat for each paragraph...
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Write content into a blank document
|
|
164
|
+
|
|
165
|
+
Do not use `superdoc_search` to find empty initial paragraphs — search matches text, and blank blocks have none. Use `superdoc_get_content` for blank-block discovery.
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
// Step 1: First create — omit positional `at` targeting on a blank document
|
|
169
|
+
superdoc_create({action: "paragraph", text: "First paragraph."})
|
|
170
|
+
|
|
171
|
+
// Step 2: Fetch blocks to get nodeIds for subsequent relative inserts
|
|
172
|
+
superdoc_get_content({action: "blocks"})
|
|
173
|
+
|
|
174
|
+
// Step 3: Chain further creates using nodeIds from blocks
|
|
175
|
+
superdoc_create({action: "paragraph", text: "Second paragraph.", at: {kind: "after", target: {kind: "block", nodeType: "paragraph", nodeId: "<nodeId1>"}}})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Bold or format existing text
|
|
179
|
+
|
|
180
|
+
```
|
|
181
|
+
superdoc_search({select: {type: "text", pattern: "important phrase"}, require: "first"})
|
|
182
|
+
superdoc_format({action: "inline", ref: "<handle.ref>", inline: {bold: true}})
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Set paragraph alignment, spacing, or page breaks
|
|
186
|
+
|
|
187
|
+
Paragraph-level actions require a **block target with nodeId**, not a ref:
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
superdoc_format({action: "set_alignment", target: {kind: "block", nodeType: "paragraph", nodeId: "<nodeId>"}, alignment: "center"})
|
|
191
|
+
superdoc_format({action: "set_flow_options", target: {kind: "block", nodeType: "paragraph", nodeId: "<nodeId>"}, pageBreakBefore: true})
|
|
192
|
+
superdoc_format({action: "set_spacing", target: {kind: "block", nodeType: "paragraph", nodeId: "<nodeId>"}, lineSpacing: {rule: "auto", value: 1.5}})
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Create a bullet or numbered list
|
|
196
|
+
|
|
197
|
+
1. Create all paragraphs at the SAME location, chaining with previous nodeId:
|
|
198
|
+
```
|
|
199
|
+
superdoc_create({action: "paragraph", text: "Item one", at: {kind: "documentEnd"}})
|
|
200
|
+
superdoc_create({action: "paragraph", text: "Item two", at: {kind: "after", target: {kind: "block", nodeType: "paragraph", nodeId: "<nodeId1>"}}})
|
|
201
|
+
superdoc_create({action: "paragraph", text: "Item three", at: {kind: "after", target: {kind: "block", nodeType: "paragraph", nodeId: "<nodeId2>"}}})
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
2. Convert the consecutive paragraphs to a list in one call:
|
|
205
|
+
```
|
|
206
|
+
superdoc_list({action: "create", mode: "fromParagraphs", preset: "disc", target: {from: {kind: "block", nodeType: "paragraph", nodeId: "<first>"}, to: {kind: "block", nodeType: "paragraph", nodeId: "<last>"}}})
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Use preset "disc" for bullets, "decimal" for numbered. WARNING: the range converts ALL paragraphs between from and to. Make sure no other content exists between them.
|
|
210
|
+
|
|
211
|
+
3. To change a bullet list to numbered: `superdoc_list({action: "set_type", target: {kind: "block", nodeType: "listItem", nodeId: "<anyItemId>"}, kind: "ordered"})`
|
|
212
|
+
|
|
213
|
+
### Add items to an existing list
|
|
214
|
+
|
|
215
|
+
To add a new item adjacent to an existing list item, use `superdoc_list({action: "insert"})`, NOT `superdoc_create({action: "paragraph"})` — the latter creates a standalone paragraph that is not part of the list:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
superdoc_get_content({action: "blocks"}) // find the listItem nodeId you want to insert next to
|
|
219
|
+
superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, position: "after", text: "New item text"})
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**Level inheritance.** The new item inherits the target's nesting level. Insert after a level-0 item → new item is level 0. Insert after a level-2 item → new item is level 2. To change the level, chain `indent` / `outdent` / `set_level` on the nodeId returned in the insert response.
|
|
223
|
+
|
|
224
|
+
**Use the nodeId from the response directly.** `superdoc_list({action: "insert"})` returns `{item: {nodeId: "<id>"}}` — that id is ready for subsequent `indent`, `outdent`, `set_level`, or text edits. You do NOT need to re-fetch blocks between the insert and the follow-up operation.
|
|
225
|
+
|
|
226
|
+
### Add a sub-point under an existing item
|
|
227
|
+
|
|
228
|
+
Insert a peer, then indent it one level:
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
// 1. Insert a peer item after the parent — new item is at the parent's level
|
|
232
|
+
const resp = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<parentItemId>"}, position: "after", text: "Sub-point"})
|
|
233
|
+
|
|
234
|
+
// 2. Indent using the nodeId from resp.item.nodeId
|
|
235
|
+
superdoc_list({action: "indent", target: {kind: "block", nodeType: "listItem", nodeId: "<resp.item.nodeId>"}})
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Build a nested list with mixed levels
|
|
239
|
+
|
|
240
|
+
`lists.create` produces a flat list. Add nesting by chaining `insert` + `indent` / `set_level`, using the nodeId returned by each insert to target the next step:
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
// Starting point: a list item at level 0 ("Parent" with nodeId <parent>)
|
|
244
|
+
|
|
245
|
+
// Sibling at level 0
|
|
246
|
+
const r1 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<parent>"}, position: "after", text: "Sibling"})
|
|
247
|
+
|
|
248
|
+
// Child at level 1 (insert after r1, then indent)
|
|
249
|
+
const r2 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<r1.item.nodeId>"}, position: "after", text: "Child"})
|
|
250
|
+
superdoc_list({action: "indent", target: {kind: "block", nodeType: "listItem", nodeId: "<r2.item.nodeId>"}})
|
|
251
|
+
|
|
252
|
+
// Grandchild at level 3 (insert after r2, then jump to level 3 directly)
|
|
253
|
+
const r3 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<r2.item.nodeId>"}, position: "after", text: "Deep"})
|
|
254
|
+
superdoc_list({action: "set_level", target: {kind: "block", nodeType: "listItem", nodeId: "<r3.item.nodeId>"}, level: 3})
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
`indent` bumps the level by one (bounded 0–8). `set_level` jumps directly to any level 0–8. Markers update automatically based on the list's definition for each level (e.g. `1.` / `a.` / `i.` for an ordered list).
|
|
258
|
+
|
|
259
|
+
### Merge two adjacent lists into one
|
|
260
|
+
|
|
261
|
+
Use `merge` — it handles the common case where two ordered or bulleted lists sit next to each other and should become one continuous list. Absorbed items adopt the absorbing sequence's definition, and any empty paragraphs between the two lists are removed so numbering flows continuously.
|
|
262
|
+
|
|
263
|
+
```
|
|
264
|
+
superdoc_get_content({action: "blocks"}) // find a listItem in either sequence
|
|
265
|
+
// To merge with the previous sequence:
|
|
266
|
+
superdoc_list({action: "merge", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, direction: "withPrevious"})
|
|
267
|
+
// Or with the next sequence:
|
|
268
|
+
superdoc_list({action: "merge", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, direction: "withNext"})
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Split a list into two
|
|
272
|
+
|
|
273
|
+
Use `split` to break one list into two independent lists at a specific item. The target and everything after become a new sequence that restarts numbering at 1:
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
superdoc_list({action: "split", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}})
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
Pass `restartNumbering: false` if you want the new half to keep counting from where the original left off.
|
|
280
|
+
|
|
281
|
+
### Restart numbering at a specific item
|
|
282
|
+
|
|
283
|
+
For ordered lists. To make item N restart from a chosen number (commonly 1):
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
superdoc_list({action: "set_value", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, value: 1})
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Pass `value: null` to clear a previously-set restart override and let the item resume natural numbering.
|
|
290
|
+
|
|
291
|
+
### Continue numbering across a break
|
|
292
|
+
|
|
293
|
+
For ordered lists. When two sibling sequences should be numbered as one (e.g. numbering jumps back to 1 and you want it to continue from where the previous list left off), target the FIRST item of the second sequence:
|
|
294
|
+
|
|
295
|
+
```
|
|
296
|
+
superdoc_list({action: "continue_previous", target: {kind: "block", nodeType: "listItem", nodeId: "<firstItemOfSecondList>"}})
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Fails with `NO_COMPATIBLE_PREVIOUS` or `INCOMPATIBLE_DEFINITIONS` if no prior sequence shares the same abstract definition. In that case, use `merge` instead — it handles mismatched definitions, removes empty gap paragraphs, and produces one continuous list.
|
|
300
|
+
|
|
301
|
+
### Insert content into a document (new or existing)
|
|
302
|
+
|
|
303
|
+
Markdown insert creates block structure but uses default formatting. You MUST follow up with formatting so inserted content looks like it belongs in the document.
|
|
304
|
+
|
|
305
|
+
**Step 1: Understand the document context** from the get_content blocks response. Before inserting anything, analyze:
|
|
306
|
+
- What kind of document is this? (contract, letter, certificate, report, etc.)
|
|
307
|
+
- How are titles/headings styled? (centered? left? bold? underlined? what fontSize?)
|
|
308
|
+
- Are titles UPPERCASE? (e.g., "EMPLOYMENT AGREEMENT", "RECITALS" → your heading must also be UPPERCASE)
|
|
309
|
+
- How is body text styled? (fontFamily, fontSize, alignment, color)
|
|
310
|
+
- What formatting conventions does the document follow?
|
|
311
|
+
|
|
312
|
+
Your inserted content must be indistinguishable from the existing content. If titles are ALL CAPS centered 10pt, your heading text must also be ALL CAPS centered 10pt. If body text is justified 12pt, your paragraphs must be justified 12pt.
|
|
313
|
+
|
|
314
|
+
**Step 2: Insert content with markdown:**
|
|
315
|
+
|
|
316
|
+
```
|
|
317
|
+
superdoc_edit({action: "insert", type: "markdown",
|
|
318
|
+
target: {kind: "block", nodeType: "paragraph", nodeId: "<first-block-nodeId>"},
|
|
319
|
+
placement: "before",
|
|
320
|
+
value: "# Executive Summary\n\nThis agreement sets forth the principal terms..."})
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Step 3: Format ALL inserted blocks in ONE superdoc_mutations call.** Each format.apply step accepts `inline`, `alignment`, and `scope: "block"`.
|
|
324
|
+
|
|
325
|
+
Use `scope: "block"` so formatting covers the entire paragraph (not just the matched text). The text pattern only needs to identify which block. Copy the exact property values from the existing blocks in the get_content response. Do NOT invent values.
|
|
326
|
+
|
|
327
|
+
Example: document blocks show fontFamily, fontSize: 10, color, titles centered:
|
|
328
|
+
```
|
|
329
|
+
superdoc_mutations({action: "apply", atomic: true, steps: [
|
|
330
|
+
{id: "f1", op: "format.apply", where: {by: "select", select: {type: "text", pattern: "Executive Summary"}, require: "first"}, args: {inline: {fontFamily: "Times New Roman, serif", fontSize: 10, color: "#000000"}, alignment: "center", scope: "block"}},
|
|
331
|
+
{id: "f2", op: "format.apply", where: {by: "select", select: {type: "text", pattern: "This agreement sets forth"}, require: "first"}, args: {inline: {fontFamily: "Times New Roman, serif", fontSize: 10, color: "#000000"}, scope: "block"}}
|
|
332
|
+
]})
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
Total: 3 calls (read + insert + format-all-in-one-batch). Never more.
|
|
336
|
+
|
|
337
|
+
### Batch multiple text edits atomically
|
|
338
|
+
|
|
339
|
+
Use superdoc_mutations for 2+ text changes, format changes, or a combination:
|
|
340
|
+
|
|
341
|
+
```
|
|
342
|
+
superdoc_get_content({action: "blocks", includeText: true})
|
|
343
|
+
superdoc_mutations({
|
|
344
|
+
action: "apply", atomic: true, changeMode: "direct",
|
|
345
|
+
steps: [
|
|
346
|
+
{id: "s1", op: "text.rewrite", where: {by: "block", nodeType: "paragraph", nodeId: "<paragraphNodeId>"}, args: {replacement: {text: "Updated full paragraph text."}}},
|
|
347
|
+
{id: "s2", op: "text.delete", where: {by: "select", select: {type: "text", pattern: " (deprecated)"}, require: "all"}, args: {}},
|
|
348
|
+
{id: "s3", op: "text.insert", where: {by: "select", select: {type: "text", pattern: "Section Title"}, require: "first"}, args: {position: "after", content: {text: " (Updated)"}}}
|
|
349
|
+
]
|
|
350
|
+
})
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Use `by:"block"` for whole-paragraph / whole-clause rewrites. Use `by:"select"` only for substring edits, discovery, or insertion relative to a sentence fragment.
|
|
354
|
+
|
|
355
|
+
Selectors resolve at compile time (before execution). This means format.apply steps CANNOT target content created by create steps in the same batch — the new content does not exist yet when selectors compile. Split creates and formatting into separate batches.
|
|
356
|
+
|
|
357
|
+
Never create two steps targeting overlapping text in the same block. Combine them into a single text.rewrite instead.
|
|
358
|
+
|
|
359
|
+
### Tables: cross-tool workflows
|
|
360
|
+
|
|
361
|
+
Tool-local rules (which action to pick, locator shapes, color formats) live in the `superdoc_table` description itself. The rules below cover workflows that **cross tools** — that's the part the model gets wrong without explicit guidance.
|
|
362
|
+
|
|
363
|
+
**1. After `set_cell_text`, format the new cell to match its siblings.**
|
|
364
|
+
`set_cell_text` writes plain text with no formatting. To match the rest of the table:
|
|
365
|
+
|
|
366
|
+
```
|
|
367
|
+
// Read a sibling cell's text style first (or any body paragraph if the table is fresh):
|
|
368
|
+
superdoc_get_content({action: "blocks", includeText: true})
|
|
369
|
+
|
|
370
|
+
// Apply matching inline style to the new cell's paragraph:
|
|
371
|
+
superdoc_format({action: "inline", ref: "<new-cell-paragraph-ref>",
|
|
372
|
+
inline: {fontFamily, fontSize, color, bold: false}})
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
If sibling cells show a bold-prefix pattern like `"Label: value"`, replicate it on the new cell using `superdoc_search` + `superdoc_format` (or one `superdoc_mutations` batch with `format.apply` steps using `where:{by:"select", ...}`).
|
|
376
|
+
|
|
377
|
+
**2. "Style the whole table" crosses `superdoc_table` and `superdoc_format`.**
|
|
378
|
+
Borders / shading / cnf flags / spacing live on `superdoc_table`. **Cell-text alignment and font color/weight live on `superdoc_format`** (they're paragraph- and run-level). A complete table-styling pass calls both:
|
|
379
|
+
|
|
380
|
+
```
|
|
381
|
+
// Table-level (superdoc_table):
|
|
382
|
+
set_borders applyTo:"all" with explicit color
|
|
383
|
+
set_shading on the header cells with the accent color
|
|
384
|
+
set_style_options { headerRow: true }
|
|
385
|
+
|
|
386
|
+
// Cell-text level (superdoc_format, per cell paragraph):
|
|
387
|
+
set_alignment on header (center) and body (left or right)
|
|
388
|
+
inline { color, bold } on header cells
|
|
389
|
+
|
|
390
|
+
// Batch many cell-level format calls via superdoc_mutations format.apply.
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Discover the document's palette by reading `superdoc_get_content({action: "blocks"})` and reusing colors from existing tables/headings. When none are obvious, default to `1F3864` (corporate blue) or `444444` (dark grey) for accents and `F2F2F2` / `E7E6E6` for banding. Never use `auto` when a concrete color is implied.
|
|
394
|
+
|
|
395
|
+
**3. After a structural change to a styled table, re-apply the existing styling.**
|
|
396
|
+
Triggers: `insert_row`, `insert_column`, `delete_row`, `delete_column`, `merge_cells`, `unmerge_cells` — but NOT `set_cell_text` or `set_cell` (those don't disturb borders/shading). Read the current borders/shading/cnf flags via `superdoc_get_content({action: "blocks"})` before the change, then re-run the same `set_borders` / `set_shading` / `set_style_options` calls with the SAME values after. Goal is consistency, not redesign. Skip on a freshly created table — a new table starts un-styled.
|
|
397
|
+
|
|
398
|
+
**4. Convert a list to a table.**
|
|
399
|
+
Three steps:
|
|
400
|
+
1. `superdoc_create({action: "table", rows: N, columns: M, at: ...})`
|
|
401
|
+
2. Populate cells with `superdoc_table({action: "set_cell_text", ...})` — one call per cell.
|
|
402
|
+
3. **Delete the source list** with one `superdoc_list` call:
|
|
403
|
+
|
|
404
|
+
```
|
|
405
|
+
superdoc_list({action: "delete", target: {kind: "block", nodeType: "listItem", nodeId: "<any-item-id>"}})
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Wrong paths (all leave bullets/empty paragraphs behind): `text.delete`, `superdoc_edit` action `delete` on text refs, `lists.detach`, `lists.convertToText`. Only `superdoc_list` action `delete` removes the whole list.
|
|
409
|
+
|
|
410
|
+
### Add a comment on specific text
|
|
411
|
+
|
|
412
|
+
```
|
|
413
|
+
superdoc_search({select: {type: "text", pattern: "target phrase"}, require: "first"})
|
|
414
|
+
superdoc_comment({
|
|
415
|
+
action: "create",
|
|
416
|
+
text: "Please review this section.",
|
|
417
|
+
target: {kind: "text", blockId: "<items[0].blocks[0].blockId>", range: {start: <items[0].blocks[0].range.start>, end: <items[0].blocks[0].range.end>}}
|
|
418
|
+
})
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
Only pass `action`, `text`, and `target` when creating a new top-level comment. For threaded replies, add `parentId`.
|
|
422
|
+
|
|
423
|
+
### Accept or reject tracked changes
|
|
424
|
+
|
|
425
|
+
```
|
|
426
|
+
superdoc_track_changes({action: "list"})
|
|
427
|
+
// Review changes, then accept or reject
|
|
428
|
+
superdoc_track_changes({action: "decide", decision: "accept", target: {id: "<changeId>"}})
|
|
429
|
+
// Or accept all at once
|
|
430
|
+
superdoc_track_changes({action: "decide", decision: "accept", target: {scope: "all"}})
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Match existing document formatting (CRITICAL)
|
|
434
|
+
|
|
435
|
+
When creating content "like" or "similar to" existing content:
|
|
436
|
+
|
|
437
|
+
1. Read blocks to get exact formatting properties of the reference content
|
|
438
|
+
2. Use the same nodeType. Title blocks are often bold+underline paragraphs, not heading nodes. Check the blocks data.
|
|
439
|
+
3. Copy ALL formatting exactly: bold, underline, fontSize, fontFamily, color, alignment
|
|
440
|
+
|
|
441
|
+
### Choosing formatting values (CRITICAL)
|
|
442
|
+
|
|
443
|
+
When formatting newly created content, use the right source:
|
|
444
|
+
|
|
445
|
+
- **Body text** (paragraphs, lorem ipsum, regular content): Read fontFamily, fontSize, color from non-empty, non-title paragraphs with alignment "justify" or "left". Always set `bold: false` and `underline: false` for body text. Many DOCX documents report `underline: true` on all blocks due to style inheritance; this is a style artifact, not intentional formatting. Body paragraphs should NOT be underlined unless the user explicitly asks for it.
|
|
446
|
+
- **Headings/titles**: Read from existing heading or title blocks (centered, bold, possibly underline). Scale fontSize up from body text.
|
|
447
|
+
- **Signature/form fields**: Use justify or left alignment
|
|
448
|
+
- When the user says "heading", use `action: "heading"` with a level, even if the document uses styled paragraphs as titles.
|
|
449
|
+
|
|
450
|
+
## Constraints
|
|
451
|
+
|
|
452
|
+
- **Format calls must be sequential.** Each format call bumps the document revision and invalidates all outstanding refs. Do NOT issue multiple superdoc_format calls in parallel. Format one block, then re-fetch if needed for the next block.
|
|
453
|
+
- **set_alignment target must be `{kind: "block", nodeType, nodeId}`.** NEVER use `{kind: "block", start: {kind: "nodeEdge", ...}}` or any selection-like structure. Only the flat block target with nodeType and nodeId is accepted.
|
|
454
|
+
- **Always format ALL created items.** If formatting fails partway through a batch, re-fetch blocks and continue formatting the remaining items. Do not stop after a partial failure.
|
|
455
|
+
- **Search patterns are plain text.** Do not include `#`, `**`, or formatting markers.
|
|
456
|
+
- **`select.type` must be "text" or "node".** To find headings: `{type: "node", nodeType: "heading"}`, NOT `{type: "heading"}`.
|
|
457
|
+
- **`within` scopes to a single block**, not a section. To find text in a section, search the full document.
|
|
458
|
+
- **Table cells are separate blocks.** Search for individual cell values, not patterns spanning multiple cells.
|
|
459
|
+
- **Do NOT combine `limit`/`offset` with `require: "first"` or `require: "exactlyOne"`.** Use `require: "any"` with `limit` for paginated results.
|
|
460
|
+
- **Do NOT hardcode formatting values.** Always read from blocks data and replicate.
|
|
461
|
+
- **Do NOT copy heading/title formatting onto body paragraphs.** Read from body text blocks (alignment "justify" or "left"), not title blocks.
|
|
462
|
+
- **Pass structured objects, not JSON-encoded strings.** Fields like `at`, `target`, and `inline` expect objects, not serialized JSON strings.
|
|
463
|
+
- **Only pass `dryRun` when the action's schema explicitly lists it.** Do not assume every action accepts it. Prefer a real call over a preview for destructive actions unless dryRun is documented for that action.
|
|
464
|
+
- **If blocks still report `underline: true` after you explicitly removed it, treat it as a style inheritance artifact.** Do not retry formatting to fix it.
|
|
465
|
+
- **On "Unknown field" errors, drop the unrecognized field and retry.** Use the narrowest working call shape rather than guessing alternative field names.
|
|
466
|
+
- **Table styling crosses two tools.** Borders / shading / cnf flags / spacing are on `superdoc_table`; cell-text alignment and font color/weight are on `superdoc_format` (paragraph- and run-level). A "style the whole table" pass calls both. See the Tables: cross-tool workflows section for the full recipe.
|
|
467
|
+
- **To delete a list, use `superdoc_list` action `delete`.** Pass any list-item nodeId. Never use `text.delete`, `superdoc_edit` action `delete`, `lists.detach`, or `lists.convertToText` for "remove the list" — they leave empty list-item paragraphs behind.
|