sdoc-editor-cli 0.9.2 → 0.10.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/README.md +467 -467
- package/bin/sdoc.js +0 -0
- package/dist/examples/operations/delete-block.json +17 -17
- package/dist/examples/operations/delete-section.json +12 -12
- package/dist/examples/operations/insert-block.json +19 -19
- package/dist/examples/operations/insert-section.json +21 -21
- package/dist/examples/operations/move-block.json +21 -21
- package/dist/examples/operations/move-section.json +16 -16
- package/dist/examples/operations/rename-block-id.json +13 -13
- package/dist/examples/operations/rename-heading.json +13 -13
- package/dist/examples/operations/replace-block.json +21 -21
- package/dist/examples/operations/set-document-title.json +17 -17
- package/dist/examples/operations/set-heading-level.json +13 -13
- package/dist/examples/operations/update-block-attrs.json +13 -13
- package/dist/examples/operations/update-document-metadata.json +15 -15
- package/dist/examples/operations/update-document-settings.json +16 -16
- package/dist/schemas/sdoc.operations.schema.json +385 -385
- package/dist/schemas/sdoc.schema.json +559 -559
- package/dist/sdoc.js +299 -185
- package/package.json +30 -30
package/README.md
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
# sdoc-editor-cli
|
|
2
|
-
|
|
3
|
-
`sdoc-editor-cli` is the preview-first command-line interface for inspecting,
|
|
4
|
-
validating, creating, and safely changing Structured Doc Editor `.sdoc` and
|
|
5
|
-
legacy `.tiptap.json` documents. It requires Node.js 22.22.2 or newer.
|
|
6
|
-
|
|
7
|
-
The package is
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
npm
|
|
81
|
-
sdoc --version
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
The remaining examples use `sdoc` for readability.
|
|
85
|
-
replace
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
## Help and output
|
|
89
|
-
|
|
90
|
-
```powershell
|
|
91
|
-
sdoc --help
|
|
92
|
-
sdoc help apply
|
|
93
|
-
sdoc inspect --help
|
|
94
|
-
```
|
|
95
|
-
|
|
1
|
+
# sdoc-editor-cli
|
|
2
|
+
|
|
3
|
+
`sdoc-editor-cli` is the preview-first command-line interface for inspecting,
|
|
4
|
+
validating, creating, and safely changing Structured Doc Editor `.sdoc` and
|
|
5
|
+
legacy `.tiptap.json` documents. It requires Node.js 22.22.2 or newer.
|
|
6
|
+
|
|
7
|
+
The official package is published to the public npm registry as
|
|
8
|
+
[`sdoc-editor-cli`](https://www.npmjs.com/package/sdoc-editor-cli). Its installed
|
|
9
|
+
command is `sdoc`. The separate registry package named `sdoc` is unrelated.
|
|
10
|
+
|
|
11
|
+
## Install and run
|
|
12
|
+
|
|
13
|
+
Try the current stable release without adding a project dependency:
|
|
14
|
+
|
|
15
|
+
```powershell
|
|
16
|
+
npx sdoc-editor-cli@latest --help
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For a project-pinned version, install the official package as a development
|
|
20
|
+
dependency and invoke it by its exact package name:
|
|
21
|
+
|
|
22
|
+
```powershell
|
|
23
|
+
npm install --save-dev sdoc-editor-cli
|
|
24
|
+
npx sdoc-editor-cli --version
|
|
25
|
+
npx sdoc-editor-cli capabilities --json
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Update that dependency explicitly when the project is ready to adopt the
|
|
29
|
+
current stable release:
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
npm install --save-dev sdoc-editor-cli@latest
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
For repeatable CI or Agent automation, commit `package.json` and the lockfile,
|
|
36
|
+
run `npm ci`, and add a project script:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"scripts": {
|
|
41
|
+
"sdoc": "sdoc"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Pass CLI arguments after npm's `--` separator:
|
|
47
|
+
|
|
48
|
+
```powershell
|
|
49
|
+
npm run sdoc -- capabilities --json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`npm run` uses the project-local binary and fails when the dependency is
|
|
53
|
+
missing; it does not fetch a command from the registry.
|
|
54
|
+
|
|
55
|
+
> **Package name collision warning:** Use `sdoc-editor-cli` with `npx`. Do not
|
|
56
|
+
> run `npx sdoc` or install the public package named `sdoc`; it is unrelated to
|
|
57
|
+
> Structured Doc Editor.
|
|
58
|
+
|
|
59
|
+
The CLI does not replace itself automatically. Use the explicit npm update
|
|
60
|
+
command above, or let the owning project's dependency automation update its
|
|
61
|
+
manifest and lockfile.
|
|
62
|
+
|
|
63
|
+
### Version-pinned GitHub Release fallback
|
|
64
|
+
|
|
65
|
+
Every tagged release also attaches a versioned `sdoc-editor-cli-*.tgz` to
|
|
66
|
+
[GitHub Releases](https://github.com/SWBaek/sdoc-editor/releases/latest). Use
|
|
67
|
+
this path when an installation must be pinned to a downloaded release asset.
|
|
68
|
+
After downloading the selected tarball, install it from the project that owns
|
|
69
|
+
the dependency:
|
|
70
|
+
|
|
71
|
+
```powershell
|
|
72
|
+
npm install --save-dev .\sdoc-editor-cli-X.Y.Z.tgz
|
|
73
|
+
npx sdoc-editor-cli --version
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
For frequent interactive use across projects, a global install provides the
|
|
77
|
+
short `sdoc` command:
|
|
78
|
+
|
|
79
|
+
```powershell
|
|
80
|
+
npm install --global sdoc-editor-cli
|
|
81
|
+
sdoc --version
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The remaining examples use `sdoc` for readability. After a project-local
|
|
85
|
+
install, replace that prefix with `npx sdoc-editor-cli`, or use the `npm run
|
|
86
|
+
sdoc --` script above. A global installation can run `sdoc` directly.
|
|
87
|
+
|
|
88
|
+
## Help and output
|
|
89
|
+
|
|
90
|
+
```powershell
|
|
91
|
+
sdoc --help
|
|
92
|
+
sdoc help apply
|
|
93
|
+
sdoc inspect --help
|
|
94
|
+
```
|
|
95
|
+
|
|
96
96
|
JSON is the default and stable machine-readable output. `--json` states that
|
|
97
97
|
choice explicitly. `--human` provides concise interactive output and is not a
|
|
98
98
|
stable machine API; its wording and layout may change between releases. Never
|
|
@@ -134,7 +134,7 @@ sdoc capabilities --human
|
|
|
134
134
|
"contract": "sdoc.cli.response/1",
|
|
135
135
|
"ok": true,
|
|
136
136
|
"command": "capabilities",
|
|
137
|
-
"cliVersion": "0.9.
|
|
137
|
+
"cliVersion": "0.9.3",
|
|
138
138
|
"contracts": {
|
|
139
139
|
"document": "sdoc/1.0",
|
|
140
140
|
"operations": "sdoc.operations/1",
|
|
@@ -158,35 +158,35 @@ revision of the exact source bytes, metadata, outline, references,
|
|
|
158
158
|
referenceable nodes, targetable blocks, and an optional selected target. The
|
|
159
159
|
revision includes a UTF-8 BOM when present and changes after
|
|
160
160
|
representation-only edits. Existing no-projection calls retain this behavior.
|
|
161
|
-
|
|
162
|
-
```powershell
|
|
163
|
-
sdoc inspect document.sdoc --json
|
|
164
|
-
sdoc inspect document.sdoc --target-id intro --json
|
|
165
|
-
sdoc inspect document.sdoc --target-path /1/0 --json
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
Use the returned `revision`, IDs, paths, node types, and digests to construct an
|
|
169
|
-
operation request. Top-level `metadata` reports the current title, author,
|
|
170
|
-
version, timestamps, and document setting overrides. `--target-path` uses a
|
|
171
|
-
slash-delimited content path. For example, `/1/0` selects
|
|
172
|
-
`doc.content[1].content[0]`. A selected result has this shape:
|
|
173
|
-
|
|
174
|
-
```json
|
|
175
|
-
{
|
|
176
|
-
"target": {
|
|
177
|
-
"path": [1, 0],
|
|
178
|
-
"node": { "type": "paragraph" },
|
|
179
|
-
"digest": "sha256:...",
|
|
180
|
-
"operationTarget": {
|
|
181
|
-
"kind": "snapshot",
|
|
182
|
-
"path": [1, 0],
|
|
183
|
-
"nodeType": "paragraph",
|
|
184
|
-
"digest": "sha256:..."
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
```
|
|
189
|
-
|
|
161
|
+
|
|
162
|
+
```powershell
|
|
163
|
+
sdoc inspect document.sdoc --json
|
|
164
|
+
sdoc inspect document.sdoc --target-id intro --json
|
|
165
|
+
sdoc inspect document.sdoc --target-path /1/0 --json
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Use the returned `revision`, IDs, paths, node types, and digests to construct an
|
|
169
|
+
operation request. Top-level `metadata` reports the current title, author,
|
|
170
|
+
version, timestamps, and document setting overrides. `--target-path` uses a
|
|
171
|
+
slash-delimited content path. For example, `/1/0` selects
|
|
172
|
+
`doc.content[1].content[0]`. A selected result has this shape:
|
|
173
|
+
|
|
174
|
+
```json
|
|
175
|
+
{
|
|
176
|
+
"target": {
|
|
177
|
+
"path": [1, 0],
|
|
178
|
+
"node": { "type": "paragraph" },
|
|
179
|
+
"digest": "sha256:...",
|
|
180
|
+
"operationTarget": {
|
|
181
|
+
"kind": "snapshot",
|
|
182
|
+
"path": [1, 0],
|
|
183
|
+
"nodeType": "paragraph",
|
|
184
|
+
"digest": "sha256:..."
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
190
|
Copy `target.operationTarget` directly instead of assembling a snapshot
|
|
191
191
|
target. Every `blocks[]` entry also includes its canonical `operationTarget`.
|
|
192
192
|
Referenceable nodes receive an ID target; other blocks receive a snapshot
|
|
@@ -227,85 +227,85 @@ projection. Catalog, section, and document results can return
|
|
|
227
227
|
`page.nextCursor`; pass it back with the same projection/query until
|
|
228
228
|
`page.complete` is true. Cursors bind the exact source bytes and query scope.
|
|
229
229
|
They are opaque integrity tokens, not authentication credentials.
|
|
230
|
-
|
|
231
|
-
### `validate`
|
|
232
|
-
|
|
233
|
-
Checks the persisted document contract and semantic invariants without writing:
|
|
234
|
-
|
|
235
|
-
```powershell
|
|
236
|
-
sdoc validate document.sdoc --json
|
|
237
|
-
sdoc validate legacy.tiptap.json --human
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
### `apply`
|
|
241
|
-
|
|
230
|
+
|
|
231
|
+
### `validate`
|
|
232
|
+
|
|
233
|
+
Checks the persisted document contract and semantic invariants without writing:
|
|
234
|
+
|
|
235
|
+
```powershell
|
|
236
|
+
sdoc validate document.sdoc --json
|
|
237
|
+
sdoc validate legacy.tiptap.json --human
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### `apply`
|
|
241
|
+
|
|
242
242
|
Reads a complete `sdoc.operations/1` request from a UTF-8 JSON file or stdin.
|
|
243
243
|
Malformed UTF-8 is rejected before JSON parsing, locking, or document writes.
|
|
244
244
|
A UTF-8 BOM and non-ASCII JSON content are accepted. Preview is the default;
|
|
245
245
|
only `--write` can modify the named document.
|
|
246
|
-
|
|
247
|
-
```powershell
|
|
248
|
-
sdoc apply document.sdoc --operations operations.json --json
|
|
249
|
-
sdoc apply document.sdoc --operations operations.json --dry-run --json
|
|
250
|
-
sdoc apply document.sdoc --operations operations.json --write --json
|
|
251
|
-
Get-Content -Raw -Encoding utf8 operations.json |
|
|
252
|
-
sdoc apply document.sdoc --operations - --write --json
|
|
253
|
-
```
|
|
254
|
-
|
|
255
|
-
`--write` takes a sibling lock, re-reads the file, verifies its revision, and
|
|
256
|
-
atomically replaces it. A no-op is not written. Do not combine `--write` and
|
|
257
|
-
`--dry-run`.
|
|
258
|
-
|
|
259
|
-
The sibling `<document>.lock` records structured owner metadata with a format
|
|
260
|
-
version, process ID, random ownership token, hostname, and creation time. If a
|
|
261
|
-
write finds an existing lock, the CLI reclaims it automatically only when all
|
|
262
|
-
of these conditions are true:
|
|
263
|
-
|
|
264
|
-
```json
|
|
265
|
-
{"version":1,"pid":1234,"token":"0123456789abcdef0123456789abcdef","hostname":"workstation","createdAt":"2026-08-06T12:00:00.000Z"}
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
- the metadata is recognized and belongs to the current host;
|
|
269
|
-
- the owner process can be conclusively shown to have exited; and
|
|
270
|
-
- the lock is at least 60 seconds old.
|
|
271
|
-
|
|
272
|
-
Recovery atomically moves the stale lock aside, verifies that its owner did not
|
|
273
|
-
change during the move, and then retries normal exclusive acquisition. The
|
|
274
|
-
document is re-read and its revision is checked only after the new lock is
|
|
275
|
-
owned, and the CLI re-checks its ownership token before atomic publication.
|
|
276
|
-
|
|
277
|
-
Live owners, owners on another host, and legacy, malformed, or unsupported
|
|
278
|
-
metadata are never removed automatically. Wait for a known writer to finish.
|
|
279
|
-
For an abandoned lock that cannot be reclaimed automatically, remove the
|
|
280
|
-
`.lock` file manually only after confirming no writer is active on any reported
|
|
281
|
-
host, then re-inspect the document and rebuild the operation from its current
|
|
282
|
-
revision before retrying `--write`.
|
|
283
|
-
|
|
284
|
-
### `rename-heading`
|
|
285
|
-
|
|
286
|
-
Convenience command for a single `renameHeading` operation:
|
|
287
|
-
|
|
288
|
-
```powershell
|
|
289
|
-
$inspection = sdoc inspect document.sdoc --json | ConvertFrom-Json
|
|
290
|
-
sdoc rename-heading document.sdoc --id intro --title "Updated heading" `
|
|
291
|
-
--expected-revision $inspection.revision --json
|
|
292
|
-
sdoc rename-heading document.sdoc --id intro --title "Updated heading" `
|
|
293
|
-
--expected-revision $inspection.revision --write --json
|
|
294
|
-
```
|
|
295
|
-
|
|
296
|
-
The preview and a later independent write can have different
|
|
297
|
-
`outputRevision` values because each semantic change supplies a new
|
|
298
|
-
`meta.modified` time. Always treat the write result as authoritative.
|
|
299
|
-
|
|
300
|
-
### `set-document-title`
|
|
301
|
-
|
|
246
|
+
|
|
247
|
+
```powershell
|
|
248
|
+
sdoc apply document.sdoc --operations operations.json --json
|
|
249
|
+
sdoc apply document.sdoc --operations operations.json --dry-run --json
|
|
250
|
+
sdoc apply document.sdoc --operations operations.json --write --json
|
|
251
|
+
Get-Content -Raw -Encoding utf8 operations.json |
|
|
252
|
+
sdoc apply document.sdoc --operations - --write --json
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
`--write` takes a sibling lock, re-reads the file, verifies its revision, and
|
|
256
|
+
atomically replaces it. A no-op is not written. Do not combine `--write` and
|
|
257
|
+
`--dry-run`.
|
|
258
|
+
|
|
259
|
+
The sibling `<document>.lock` records structured owner metadata with a format
|
|
260
|
+
version, process ID, random ownership token, hostname, and creation time. If a
|
|
261
|
+
write finds an existing lock, the CLI reclaims it automatically only when all
|
|
262
|
+
of these conditions are true:
|
|
263
|
+
|
|
264
|
+
```json
|
|
265
|
+
{"version":1,"pid":1234,"token":"0123456789abcdef0123456789abcdef","hostname":"workstation","createdAt":"2026-08-06T12:00:00.000Z"}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- the metadata is recognized and belongs to the current host;
|
|
269
|
+
- the owner process can be conclusively shown to have exited; and
|
|
270
|
+
- the lock is at least 60 seconds old.
|
|
271
|
+
|
|
272
|
+
Recovery atomically moves the stale lock aside, verifies that its owner did not
|
|
273
|
+
change during the move, and then retries normal exclusive acquisition. The
|
|
274
|
+
document is re-read and its revision is checked only after the new lock is
|
|
275
|
+
owned, and the CLI re-checks its ownership token before atomic publication.
|
|
276
|
+
|
|
277
|
+
Live owners, owners on another host, and legacy, malformed, or unsupported
|
|
278
|
+
metadata are never removed automatically. Wait for a known writer to finish.
|
|
279
|
+
For an abandoned lock that cannot be reclaimed automatically, remove the
|
|
280
|
+
`.lock` file manually only after confirming no writer is active on any reported
|
|
281
|
+
host, then re-inspect the document and rebuild the operation from its current
|
|
282
|
+
revision before retrying `--write`.
|
|
283
|
+
|
|
284
|
+
### `rename-heading`
|
|
285
|
+
|
|
286
|
+
Convenience command for a single `renameHeading` operation:
|
|
287
|
+
|
|
288
|
+
```powershell
|
|
289
|
+
$inspection = sdoc inspect document.sdoc --json | ConvertFrom-Json
|
|
290
|
+
sdoc rename-heading document.sdoc --id intro --title "Updated heading" `
|
|
291
|
+
--expected-revision $inspection.revision --json
|
|
292
|
+
sdoc rename-heading document.sdoc --id intro --title "Updated heading" `
|
|
293
|
+
--expected-revision $inspection.revision --write --json
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
The preview and a later independent write can have different
|
|
297
|
+
`outputRevision` values because each semantic change supplies a new
|
|
298
|
+
`meta.modified` time. Always treat the write result as authoritative.
|
|
299
|
+
|
|
300
|
+
### `set-document-title`
|
|
301
|
+
|
|
302
302
|
Convenience command for one `setDocumentTitle` operation. `--title` and
|
|
303
303
|
`--expected-revision` are required. Without `--id`, the command changes only
|
|
304
304
|
`meta.title`. With the persistent or provisional ID of an H1, it changes
|
|
305
305
|
`meta.title` and that explicit title heading atomically. The CLI never guesses
|
|
306
306
|
a title heading and never renames the file.
|
|
307
|
-
|
|
308
|
-
```powershell
|
|
307
|
+
|
|
308
|
+
```powershell
|
|
309
309
|
$inspection = sdoc inspect document.sdoc --json | ConvertFrom-Json
|
|
310
310
|
sdoc set-document-title document.sdoc --title "Metadata title" `
|
|
311
311
|
--expected-revision $inspection.revision --write --json
|
|
@@ -317,25 +317,25 @@ sdoc set-document-title document.sdoc --title "Updated document" --id title-h1 `
|
|
|
317
317
|
Use `--discard-formatting` only when replacing marked or non-text content in
|
|
318
318
|
the selected H1 is intentional. It requires `--id`; without an H1 target there
|
|
319
319
|
is no formatting to discard.
|
|
320
|
-
|
|
321
|
-
### `create`
|
|
322
|
-
|
|
323
|
-
Creates a schema-valid `.sdoc` without overwriting an existing path. The
|
|
324
|
-
default template is `builtin:blank`; the default title is the output filename.
|
|
325
|
-
|
|
326
|
-
```powershell
|
|
327
|
-
sdoc create report.sdoc --title "Quarterly Report" --json
|
|
328
|
-
sdoc create report.sdoc --template builtin:technical-report --dry-run --json
|
|
329
|
-
sdoc create design.sdoc --template builtin:design-specification --json
|
|
330
|
-
sdoc create verification.sdoc --template builtin:verification-report --json
|
|
331
|
-
sdoc create report.sdoc --template .\templates\company-report.sdoc --json
|
|
332
|
-
```
|
|
333
|
-
|
|
320
|
+
|
|
321
|
+
### `create`
|
|
322
|
+
|
|
323
|
+
Creates a schema-valid `.sdoc` without overwriting an existing path. The
|
|
324
|
+
default template is `builtin:blank`; the default title is the output filename.
|
|
325
|
+
|
|
326
|
+
```powershell
|
|
327
|
+
sdoc create report.sdoc --title "Quarterly Report" --json
|
|
328
|
+
sdoc create report.sdoc --template builtin:technical-report --dry-run --json
|
|
329
|
+
sdoc create design.sdoc --template builtin:design-specification --json
|
|
330
|
+
sdoc create verification.sdoc --template builtin:verification-report --json
|
|
331
|
+
sdoc create report.sdoc --template .\templates\company-report.sdoc --json
|
|
332
|
+
```
|
|
333
|
+
|
|
334
334
|
An explicit file template must be a valid UTF-8 JSON `.sdoc`; malformed UTF-8
|
|
335
335
|
is rejected before a destination is created. A UTF-8 BOM is accepted. Creation
|
|
336
336
|
removes persisted document identity and template-only metadata while
|
|
337
337
|
preserving supported settings, node IDs, and links.
|
|
338
|
-
|
|
338
|
+
|
|
339
339
|
## Public schemas and operation contract
|
|
340
340
|
|
|
341
341
|
The package includes:
|
|
@@ -350,276 +350,276 @@ The package includes:
|
|
|
350
350
|
Repository copies live at `sdoc.operations.schema.json`,
|
|
351
351
|
`sdoc.read.schema.json`, `sdoc.schema.json`,
|
|
352
352
|
`cli/schemas/sdoc.cli.response.schema.json`, and `examples/operations/`.
|
|
353
|
-
|
|
354
|
-
Every request has this envelope:
|
|
355
|
-
|
|
356
|
-
```json
|
|
357
|
-
{
|
|
358
|
-
"contract": "sdoc.operations/1",
|
|
359
|
-
"expected": {
|
|
360
|
-
"revision": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
361
|
-
},
|
|
362
|
-
"operations": []
|
|
363
|
-
}
|
|
364
|
-
```
|
|
365
|
-
|
|
366
|
-
In published examples, the all-zero revision means "replace with the exact
|
|
367
|
-
`revision` returned by `inspect`." The all-one snapshot digest means "replace
|
|
368
|
-
with that block's exact `digest` returned by the same inspection." IDs ending
|
|
369
|
-
in `-from-inspect` are also placeholders. These values are syntactically valid
|
|
370
|
-
so schema tools can validate every example, but they are not usable against a
|
|
371
|
-
real document until replaced.
|
|
372
|
-
|
|
373
|
-
Do not add `expected.documentId` in CLI requests. Although inspection may
|
|
374
|
-
report a document ID, the file-only CLI cannot establish a trusted external
|
|
375
|
-
document identity and rejects that precondition as unverifiable. Revision is
|
|
376
|
-
the CLI concurrency contract.
|
|
377
|
-
|
|
378
|
-
### Targets
|
|
379
|
-
|
|
380
|
-
Referenceable `heading`, `image`, `table`, and `mathBlock` nodes use persistent
|
|
381
|
-
or revision-scoped provisional IDs:
|
|
382
|
-
|
|
383
|
-
```json
|
|
384
|
-
{ "kind": "id", "id": "intro", "expectedType": "heading" }
|
|
385
|
-
```
|
|
386
|
-
|
|
387
|
-
Other mutable blocks, including `paragraph`, `codeBlock`, and `diagram`, use a
|
|
388
|
-
protected snapshot locator:
|
|
389
|
-
|
|
390
|
-
```json
|
|
391
|
-
{
|
|
392
|
-
"kind": "snapshot",
|
|
393
|
-
"path": [1],
|
|
394
|
-
"nodeType": "paragraph",
|
|
395
|
-
"digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111"
|
|
396
|
-
}
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
Prefer the ready-to-use `operationTarget` returned by `inspect` over copying
|
|
400
|
-
these fields manually. A `diagram` is snapshot-targeted; it is not a
|
|
401
|
-
persistent-ID node.
|
|
402
|
-
|
|
403
|
-
Snapshot targets and provisional IDs are revision-scoped. Re-inspect after any
|
|
404
|
-
source-byte change. A batch resolves all targets before applying its first
|
|
405
|
-
operation, so earlier operations cannot redirect later targets.
|
|
406
|
-
|
|
407
|
-
Destinations are `{ "position": "before"|"after", "target": ... }` or
|
|
408
|
-
`{ "position": "section-end", "target": ... }`. `section-end` targets a
|
|
409
|
-
heading and appends inside that section.
|
|
410
|
-
|
|
411
|
-
### The 14 operations
|
|
412
|
-
|
|
413
|
-
Each operation below has a complete file in `dist/examples/operations/`:
|
|
414
|
-
|
|
415
|
-
| Operation | Required fields | Purpose |
|
|
416
|
-
|---|---|---|
|
|
417
|
-
| `renameHeading` | `target`, `title` | Rename a heading; optional `discardFormatting` permits replacing rich heading content |
|
|
418
|
-
| `insertBlock` | `destination`, `block` | Insert a non-heading Tiptap block |
|
|
419
|
-
| `insertSection` | `target`, `title` | Insert a child section by default, or a same-level sibling with `position: "before"|"after"`; optional `id` and `blocks` |
|
|
420
|
-
| `replaceBlock` | `target`, `block` | Replace a block with the same node type while preserving identity |
|
|
421
|
-
| `updateBlockAttrs` | `target`, `attrs` | Merge block attributes |
|
|
422
|
-
| `moveBlock` | `target`, `destination` | Move a non-heading block |
|
|
423
|
-
| `deleteBlock` | `target` | Delete a non-heading block |
|
|
424
|
-
| `moveSection` | `target`, `destination` | Move a heading and its complete descendant section |
|
|
425
|
-
| `deleteSection` | `target` | Delete a heading and its complete descendant section |
|
|
426
|
-
| `setHeadingLevel` | `target`, `level` | Set a heading level and shift descendant headings by the same delta while preserving IDs |
|
|
427
|
-
| `renameBlockId` | `target`, `newId` | Rename a heading or table ID and rewrite matching internal links |
|
|
428
|
-
| `setDocumentTitle` | `title` | Set `meta.title`; optional `headingTarget` atomically updates an explicit H1 |
|
|
429
|
-
| `updateDocumentMetadata` | `patch` | Set or remove (`null`) the allowed `author` and `version` fields |
|
|
430
|
-
| `updateDocumentSettings` | `patch` | Set or remove (`null`) portable document setting overrides |
|
|
431
|
-
|
|
432
|
-
Operations are validated and applied atomically as one batch. Headings must be
|
|
433
|
-
moved or deleted with section operations. `created`, `modified`, document
|
|
434
|
-
identity, template metadata, arbitrary metadata, and filenames cannot be
|
|
435
|
-
changed by these operations. Portable settings are:
|
|
436
|
-
`headingNumbering`, `headingDecoration`, `headingH1Color` through
|
|
437
|
-
`headingH6Color`, `captionStyle`, `captionNumbering`, `equationNumbering`,
|
|
438
|
-
`crossRefIncludeCaption`, `pdfScale`, `selfContained`, `slideBreakLevel`,
|
|
439
|
-
`slideTransition`, and `showTitleSlide`. Local path settings
|
|
440
|
-
`slideCssPath`, `htmlCssPath`, and `outputDir` are deliberately excluded.
|
|
441
|
-
|
|
442
|
-
`insertSection` keeps its existing child behavior when `position` is omitted or
|
|
443
|
-
set to `"child"`: the new heading is one level deeper and is appended at the
|
|
444
|
-
target section boundary. Set `position` to `"before"` or `"after"` to insert a
|
|
445
|
-
same-level sibling before the target heading or after its complete descendant
|
|
446
|
-
section. This is the supported CLI route for building several peer H1 sections
|
|
447
|
-
without editing raw JSON; the new heading receives the target heading's level
|
|
448
|
-
and its requested persistent ID.
|
|
449
|
-
|
|
450
|
-
`setHeadingLevel` changes an existing heading to level 1-6 without changing its
|
|
451
|
-
persistent ID. Every descendant heading in that section moves by the same
|
|
452
|
-
level delta, preserving the section's relative hierarchy; the operation is
|
|
453
|
-
rejected with `INVALID_HEADING_LEVEL` or `SECTION_LEVEL_OUT_OF_RANGE` when the
|
|
454
|
-
requested level or any resulting descendant level falls outside 1-6. Inspect
|
|
455
|
-
again after the write to confirm the resulting outline paths and parentage.
|
|
456
|
-
|
|
457
|
-
`renameBlockId` requires an existing ID target and a unique non-empty `newId`.
|
|
458
|
-
It preserves the node and heading level, updates internal `#old-id` links in the
|
|
459
|
-
same atomic batch, and rejects duplicate IDs. A newly assigned ID cannot be used
|
|
460
|
-
as another target in that same request because all operation targets are
|
|
461
|
-
resolved from the inspected input revision before mutation begins.
|
|
462
|
-
|
|
463
|
-
### One inspection, one atomic batch
|
|
464
|
-
|
|
465
|
-
Do not inspect once per operation. One revision can guard a batch of up to 100
|
|
466
|
-
operations. This example inspects once, prepares three document-level changes,
|
|
467
|
-
previews them, and then writes the same batch:
|
|
468
|
-
|
|
469
|
-
```powershell
|
|
470
|
-
$inspection = sdoc inspect document.sdoc --target-id title-h1 --json |
|
|
471
|
-
ConvertFrom-Json
|
|
472
|
-
$request = [ordered]@{
|
|
473
|
-
contract = 'sdoc.operations/1'
|
|
474
|
-
expected = @{ revision = $inspection.revision }
|
|
475
|
-
operations = @(
|
|
476
|
-
@{
|
|
477
|
-
op = 'setDocumentTitle'
|
|
478
|
-
title = 'Release Plan'
|
|
479
|
-
headingTarget = $inspection.target.operationTarget
|
|
480
|
-
}
|
|
481
|
-
@{ op = 'updateDocumentMetadata'; patch = @{ author = 'Documentation Team'; version = '2.0' } }
|
|
482
|
-
@{ op = 'updateDocumentSettings'; patch = @{ headingNumbering = $true; captionStyle = 'modern' } }
|
|
483
|
-
)
|
|
484
|
-
}
|
|
485
|
-
$request | ConvertTo-Json -Depth 100 |
|
|
486
|
-
Set-Content -Encoding utf8 operations.json
|
|
487
|
-
sdoc apply document.sdoc --operations operations.json --json
|
|
488
|
-
sdoc apply document.sdoc --operations operations.json --write --json
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
All targets in a mixed content/metadata batch must come from that same
|
|
492
|
-
inspection. Re-inspect after a successful write before preparing another
|
|
493
|
-
batch; no re-inspection is needed between operations inside one batch.
|
|
494
|
-
|
|
495
|
-
### Supported node and target catalog
|
|
496
|
-
|
|
497
|
-
The packaged `sdoc.schema.json` is authoritative. This concise catalog covers
|
|
498
|
-
the operation-relevant node types and required attributes:
|
|
499
|
-
|
|
500
|
-
| Nodes | Required attributes | Target kind and notes |
|
|
501
|
-
|---|---|---|
|
|
502
|
-
| `heading` | `attrs.level` (1-6) | ID target; rename with `renameHeading`, and move/delete as a complete section |
|
|
503
|
-
| `paragraph`, `blockquote`, `bulletList`, `orderedList`, `taskList`, `taskItem` | None | Snapshot target |
|
|
504
|
-
| `codeBlock` | None (`attrs.language` optional) | Snapshot target |
|
|
505
|
-
| `table` | None | ID target |
|
|
506
|
-
| `tableCell`, `tableHeader` | None | Snapshot target; span, width, and alignment attrs are optional |
|
|
507
|
-
| `image` | None | ID target; a new `src`, when present, must be portable |
|
|
508
|
-
| `mathBlock` | `attrs.latex` | ID target |
|
|
509
|
-
| `diagram` | `attrs.language`, `attrs.code` | Snapshot target; stores source such as Mermaid, PlantUML, or D2 |
|
|
510
|
-
| `horizontalRule`, `hardBreak`, `callout` | None | Snapshot target; callout `variant` is optional |
|
|
511
|
-
| `listItem`, `tableRow` | None | Structural container, not an operation block target |
|
|
512
|
-
| `text`, `mathInline` | `mathInline.attrs.latex` only | Inline content, not an operation target |
|
|
513
|
-
|
|
514
|
-
`updateBlockAttrs` accepts only the attrs defined for that node type.
|
|
515
|
-
`replaceBlock` must preserve the node type, while headings require the
|
|
516
|
-
heading/section operations.
|
|
517
|
-
|
|
518
|
-
Portable image assets use `./images/...`. Draw.io content is an `image` node
|
|
519
|
-
whose `src` is under `./drawio/` and ends in `.drawio.svg`; it is not a
|
|
520
|
-
`diagram` node. The CLI validates document structure and portable references
|
|
521
|
-
but does not render diagrams, create or copy asset files, or fetch assets from
|
|
522
|
-
the network.
|
|
523
|
-
|
|
524
|
-
### Diagram authoring and host rendering
|
|
525
|
-
|
|
526
|
-
For CLI and AI-operator workflows, the source of truth is the `diagram` node:
|
|
527
|
-
`diagram.attrs.language` plus `diagram.attrs.code`. A schema-valid node with
|
|
528
|
-
those attributes is sufficient to author and preserve a diagram in `.sdoc`;
|
|
529
|
-
the CLI owns structural validation and source preservation, not rendering.
|
|
530
|
-
|
|
531
|
-
Rendering belongs to the Structured Doc Editor host/viewer:
|
|
532
|
-
|
|
533
|
-
- Mermaid renders locally in the host.
|
|
534
|
-
- PlantUML, D2, and Graphviz use the host's online preview path only after the
|
|
535
|
-
user grants the required first-use consent.
|
|
536
|
-
- If consent is declined or rendering is unavailable, the diagram source
|
|
537
|
-
remains valid and preserved in the document.
|
|
538
|
-
|
|
539
|
-
Do not install local D2, Graphviz, PlantUML, or other renderers merely because
|
|
540
|
-
the CLI does not render a diagram. Local renderers are optional tools outside
|
|
541
|
-
the CLI authoring contract; AI operators should create or update the diagram
|
|
542
|
-
source node and leave rendering to the host unless the user explicitly asks
|
|
543
|
-
for a separate local-renderer workflow.
|
|
544
|
-
|
|
545
|
-
## Legacy documents
|
|
546
|
-
|
|
547
|
-
Legacy raw Tiptap JSON can be inspected and validated without an upgrade flag.
|
|
548
|
-
Every mutation, including preview, requires `--upgrade-legacy`. Persisting the
|
|
549
|
-
in-place envelope upgrade additionally requires `--write`:
|
|
550
|
-
|
|
551
|
-
```powershell
|
|
552
|
-
sdoc apply legacy.tiptap.json --operations operations.json --upgrade-legacy --json
|
|
553
|
-
sdoc apply legacy.tiptap.json --operations operations.json --upgrade-legacy --write --json
|
|
554
|
-
```
|
|
555
|
-
|
|
556
|
-
This changes the named file in place to an SDOC envelope but does not rename
|
|
557
|
-
its `.tiptap.json` extension. Back up or copy the file to a `.sdoc` path first
|
|
558
|
-
when preserving the legacy filename matters.
|
|
559
|
-
|
|
560
|
-
## PowerShell automation
|
|
561
|
-
|
|
562
|
-
Write non-ASCII JSON explicitly as UTF-8 and keep stdout separate from stderr:
|
|
563
|
-
|
|
564
|
-
```powershell
|
|
565
|
-
$request | ConvertTo-Json -Depth 100 |
|
|
566
|
-
Set-Content -Encoding utf8 operations.json
|
|
567
|
-
|
|
568
|
-
$resultJson = sdoc apply document.sdoc --operations operations.json --json 2>error.json
|
|
569
|
-
if ($LASTEXITCODE -ne 0) {
|
|
570
|
-
$errorResult = Get-Content -Raw -Encoding utf8 error.json | ConvertFrom-Json
|
|
571
|
-
throw "$($errorResult.diagnostics[0].code): $($errorResult.diagnostics[0].message)"
|
|
572
|
-
}
|
|
573
|
-
$result = $resultJson | ConvertFrom-Json
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
## Exit codes
|
|
577
|
-
|
|
578
|
-
| Code | Meaning | Representative diagnostic |
|
|
579
|
-
|---:|---|---|
|
|
580
|
-
| 0 | Success | No diagnostic |
|
|
581
|
-
| 2 | CLI argument or operation request error | `CLI_CONFLICTING_OPTIONS`, `CLI_MISSING_OPERATIONS` |
|
|
582
|
-
| 3 | Document, template, invariant, or legacy-upgrade error | `LEGACY_UPGRADE_REQUIRED` |
|
|
583
|
-
| 4 | Stale revision or precondition conflict | `STALE_REVISION` |
|
|
584
|
-
| 5 | File I/O error | `CLI_READ_FAILED` |
|
|
585
|
-
|
|
353
|
+
|
|
354
|
+
Every request has this envelope:
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"contract": "sdoc.operations/1",
|
|
359
|
+
"expected": {
|
|
360
|
+
"revision": "sha256:0000000000000000000000000000000000000000000000000000000000000000"
|
|
361
|
+
},
|
|
362
|
+
"operations": []
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
In published examples, the all-zero revision means "replace with the exact
|
|
367
|
+
`revision` returned by `inspect`." The all-one snapshot digest means "replace
|
|
368
|
+
with that block's exact `digest` returned by the same inspection." IDs ending
|
|
369
|
+
in `-from-inspect` are also placeholders. These values are syntactically valid
|
|
370
|
+
so schema tools can validate every example, but they are not usable against a
|
|
371
|
+
real document until replaced.
|
|
372
|
+
|
|
373
|
+
Do not add `expected.documentId` in CLI requests. Although inspection may
|
|
374
|
+
report a document ID, the file-only CLI cannot establish a trusted external
|
|
375
|
+
document identity and rejects that precondition as unverifiable. Revision is
|
|
376
|
+
the CLI concurrency contract.
|
|
377
|
+
|
|
378
|
+
### Targets
|
|
379
|
+
|
|
380
|
+
Referenceable `heading`, `image`, `table`, and `mathBlock` nodes use persistent
|
|
381
|
+
or revision-scoped provisional IDs:
|
|
382
|
+
|
|
383
|
+
```json
|
|
384
|
+
{ "kind": "id", "id": "intro", "expectedType": "heading" }
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Other mutable blocks, including `paragraph`, `codeBlock`, and `diagram`, use a
|
|
388
|
+
protected snapshot locator:
|
|
389
|
+
|
|
390
|
+
```json
|
|
391
|
+
{
|
|
392
|
+
"kind": "snapshot",
|
|
393
|
+
"path": [1],
|
|
394
|
+
"nodeType": "paragraph",
|
|
395
|
+
"digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111"
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Prefer the ready-to-use `operationTarget` returned by `inspect` over copying
|
|
400
|
+
these fields manually. A `diagram` is snapshot-targeted; it is not a
|
|
401
|
+
persistent-ID node.
|
|
402
|
+
|
|
403
|
+
Snapshot targets and provisional IDs are revision-scoped. Re-inspect after any
|
|
404
|
+
source-byte change. A batch resolves all targets before applying its first
|
|
405
|
+
operation, so earlier operations cannot redirect later targets.
|
|
406
|
+
|
|
407
|
+
Destinations are `{ "position": "before"|"after", "target": ... }` or
|
|
408
|
+
`{ "position": "section-end", "target": ... }`. `section-end` targets a
|
|
409
|
+
heading and appends inside that section.
|
|
410
|
+
|
|
411
|
+
### The 14 operations
|
|
412
|
+
|
|
413
|
+
Each operation below has a complete file in `dist/examples/operations/`:
|
|
414
|
+
|
|
415
|
+
| Operation | Required fields | Purpose |
|
|
416
|
+
|---|---|---|
|
|
417
|
+
| `renameHeading` | `target`, `title` | Rename a heading; optional `discardFormatting` permits replacing rich heading content |
|
|
418
|
+
| `insertBlock` | `destination`, `block` | Insert a non-heading Tiptap block |
|
|
419
|
+
| `insertSection` | `target`, `title` | Insert a child section by default, or a same-level sibling with `position: "before"|"after"`; optional `id` and `blocks` |
|
|
420
|
+
| `replaceBlock` | `target`, `block` | Replace a block with the same node type while preserving identity |
|
|
421
|
+
| `updateBlockAttrs` | `target`, `attrs` | Merge block attributes |
|
|
422
|
+
| `moveBlock` | `target`, `destination` | Move a non-heading block |
|
|
423
|
+
| `deleteBlock` | `target` | Delete a non-heading block |
|
|
424
|
+
| `moveSection` | `target`, `destination` | Move a heading and its complete descendant section |
|
|
425
|
+
| `deleteSection` | `target` | Delete a heading and its complete descendant section |
|
|
426
|
+
| `setHeadingLevel` | `target`, `level` | Set a heading level and shift descendant headings by the same delta while preserving IDs |
|
|
427
|
+
| `renameBlockId` | `target`, `newId` | Rename a heading or table ID and rewrite matching internal links |
|
|
428
|
+
| `setDocumentTitle` | `title` | Set `meta.title`; optional `headingTarget` atomically updates an explicit H1 |
|
|
429
|
+
| `updateDocumentMetadata` | `patch` | Set or remove (`null`) the allowed `author` and `version` fields |
|
|
430
|
+
| `updateDocumentSettings` | `patch` | Set or remove (`null`) portable document setting overrides |
|
|
431
|
+
|
|
432
|
+
Operations are validated and applied atomically as one batch. Headings must be
|
|
433
|
+
moved or deleted with section operations. `created`, `modified`, document
|
|
434
|
+
identity, template metadata, arbitrary metadata, and filenames cannot be
|
|
435
|
+
changed by these operations. Portable settings are:
|
|
436
|
+
`headingNumbering`, `headingDecoration`, `headingH1Color` through
|
|
437
|
+
`headingH6Color`, `captionStyle`, `captionNumbering`, `equationNumbering`,
|
|
438
|
+
`crossRefIncludeCaption`, `pdfScale`, `selfContained`, `slideBreakLevel`,
|
|
439
|
+
`slideTransition`, and `showTitleSlide`. Local path settings
|
|
440
|
+
`slideCssPath`, `htmlCssPath`, and `outputDir` are deliberately excluded.
|
|
441
|
+
|
|
442
|
+
`insertSection` keeps its existing child behavior when `position` is omitted or
|
|
443
|
+
set to `"child"`: the new heading is one level deeper and is appended at the
|
|
444
|
+
target section boundary. Set `position` to `"before"` or `"after"` to insert a
|
|
445
|
+
same-level sibling before the target heading or after its complete descendant
|
|
446
|
+
section. This is the supported CLI route for building several peer H1 sections
|
|
447
|
+
without editing raw JSON; the new heading receives the target heading's level
|
|
448
|
+
and its requested persistent ID.
|
|
449
|
+
|
|
450
|
+
`setHeadingLevel` changes an existing heading to level 1-6 without changing its
|
|
451
|
+
persistent ID. Every descendant heading in that section moves by the same
|
|
452
|
+
level delta, preserving the section's relative hierarchy; the operation is
|
|
453
|
+
rejected with `INVALID_HEADING_LEVEL` or `SECTION_LEVEL_OUT_OF_RANGE` when the
|
|
454
|
+
requested level or any resulting descendant level falls outside 1-6. Inspect
|
|
455
|
+
again after the write to confirm the resulting outline paths and parentage.
|
|
456
|
+
|
|
457
|
+
`renameBlockId` requires an existing ID target and a unique non-empty `newId`.
|
|
458
|
+
It preserves the node and heading level, updates internal `#old-id` links in the
|
|
459
|
+
same atomic batch, and rejects duplicate IDs. A newly assigned ID cannot be used
|
|
460
|
+
as another target in that same request because all operation targets are
|
|
461
|
+
resolved from the inspected input revision before mutation begins.
|
|
462
|
+
|
|
463
|
+
### One inspection, one atomic batch
|
|
464
|
+
|
|
465
|
+
Do not inspect once per operation. One revision can guard a batch of up to 100
|
|
466
|
+
operations. This example inspects once, prepares three document-level changes,
|
|
467
|
+
previews them, and then writes the same batch:
|
|
468
|
+
|
|
469
|
+
```powershell
|
|
470
|
+
$inspection = sdoc inspect document.sdoc --target-id title-h1 --json |
|
|
471
|
+
ConvertFrom-Json
|
|
472
|
+
$request = [ordered]@{
|
|
473
|
+
contract = 'sdoc.operations/1'
|
|
474
|
+
expected = @{ revision = $inspection.revision }
|
|
475
|
+
operations = @(
|
|
476
|
+
@{
|
|
477
|
+
op = 'setDocumentTitle'
|
|
478
|
+
title = 'Release Plan'
|
|
479
|
+
headingTarget = $inspection.target.operationTarget
|
|
480
|
+
}
|
|
481
|
+
@{ op = 'updateDocumentMetadata'; patch = @{ author = 'Documentation Team'; version = '2.0' } }
|
|
482
|
+
@{ op = 'updateDocumentSettings'; patch = @{ headingNumbering = $true; captionStyle = 'modern' } }
|
|
483
|
+
)
|
|
484
|
+
}
|
|
485
|
+
$request | ConvertTo-Json -Depth 100 |
|
|
486
|
+
Set-Content -Encoding utf8 operations.json
|
|
487
|
+
sdoc apply document.sdoc --operations operations.json --json
|
|
488
|
+
sdoc apply document.sdoc --operations operations.json --write --json
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
All targets in a mixed content/metadata batch must come from that same
|
|
492
|
+
inspection. Re-inspect after a successful write before preparing another
|
|
493
|
+
batch; no re-inspection is needed between operations inside one batch.
|
|
494
|
+
|
|
495
|
+
### Supported node and target catalog
|
|
496
|
+
|
|
497
|
+
The packaged `sdoc.schema.json` is authoritative. This concise catalog covers
|
|
498
|
+
the operation-relevant node types and required attributes:
|
|
499
|
+
|
|
500
|
+
| Nodes | Required attributes | Target kind and notes |
|
|
501
|
+
|---|---|---|
|
|
502
|
+
| `heading` | `attrs.level` (1-6) | ID target; rename with `renameHeading`, and move/delete as a complete section |
|
|
503
|
+
| `paragraph`, `blockquote`, `bulletList`, `orderedList`, `taskList`, `taskItem` | None | Snapshot target |
|
|
504
|
+
| `codeBlock` | None (`attrs.language` optional) | Snapshot target |
|
|
505
|
+
| `table` | None | ID target |
|
|
506
|
+
| `tableCell`, `tableHeader` | None | Snapshot target; span, width, and alignment attrs are optional |
|
|
507
|
+
| `image` | None | ID target; a new `src`, when present, must be portable |
|
|
508
|
+
| `mathBlock` | `attrs.latex` | ID target |
|
|
509
|
+
| `diagram` | `attrs.language`, `attrs.code` | Snapshot target; stores source such as Mermaid, PlantUML, or D2 |
|
|
510
|
+
| `horizontalRule`, `hardBreak`, `callout` | None | Snapshot target; callout `variant` is optional |
|
|
511
|
+
| `listItem`, `tableRow` | None | Structural container, not an operation block target |
|
|
512
|
+
| `text`, `mathInline` | `mathInline.attrs.latex` only | Inline content, not an operation target |
|
|
513
|
+
|
|
514
|
+
`updateBlockAttrs` accepts only the attrs defined for that node type.
|
|
515
|
+
`replaceBlock` must preserve the node type, while headings require the
|
|
516
|
+
heading/section operations.
|
|
517
|
+
|
|
518
|
+
Portable image assets use `./images/...`. Draw.io content is an `image` node
|
|
519
|
+
whose `src` is under `./drawio/` and ends in `.drawio.svg`; it is not a
|
|
520
|
+
`diagram` node. The CLI validates document structure and portable references
|
|
521
|
+
but does not render diagrams, create or copy asset files, or fetch assets from
|
|
522
|
+
the network.
|
|
523
|
+
|
|
524
|
+
### Diagram authoring and host rendering
|
|
525
|
+
|
|
526
|
+
For CLI and AI-operator workflows, the source of truth is the `diagram` node:
|
|
527
|
+
`diagram.attrs.language` plus `diagram.attrs.code`. A schema-valid node with
|
|
528
|
+
those attributes is sufficient to author and preserve a diagram in `.sdoc`;
|
|
529
|
+
the CLI owns structural validation and source preservation, not rendering.
|
|
530
|
+
|
|
531
|
+
Rendering belongs to the Structured Doc Editor host/viewer:
|
|
532
|
+
|
|
533
|
+
- Mermaid renders locally in the host.
|
|
534
|
+
- PlantUML, D2, and Graphviz use the host's online preview path only after the
|
|
535
|
+
user grants the required first-use consent.
|
|
536
|
+
- If consent is declined or rendering is unavailable, the diagram source
|
|
537
|
+
remains valid and preserved in the document.
|
|
538
|
+
|
|
539
|
+
Do not install local D2, Graphviz, PlantUML, or other renderers merely because
|
|
540
|
+
the CLI does not render a diagram. Local renderers are optional tools outside
|
|
541
|
+
the CLI authoring contract; AI operators should create or update the diagram
|
|
542
|
+
source node and leave rendering to the host unless the user explicitly asks
|
|
543
|
+
for a separate local-renderer workflow.
|
|
544
|
+
|
|
545
|
+
## Legacy documents
|
|
546
|
+
|
|
547
|
+
Legacy raw Tiptap JSON can be inspected and validated without an upgrade flag.
|
|
548
|
+
Every mutation, including preview, requires `--upgrade-legacy`. Persisting the
|
|
549
|
+
in-place envelope upgrade additionally requires `--write`:
|
|
550
|
+
|
|
551
|
+
```powershell
|
|
552
|
+
sdoc apply legacy.tiptap.json --operations operations.json --upgrade-legacy --json
|
|
553
|
+
sdoc apply legacy.tiptap.json --operations operations.json --upgrade-legacy --write --json
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
This changes the named file in place to an SDOC envelope but does not rename
|
|
557
|
+
its `.tiptap.json` extension. Back up or copy the file to a `.sdoc` path first
|
|
558
|
+
when preserving the legacy filename matters.
|
|
559
|
+
|
|
560
|
+
## PowerShell automation
|
|
561
|
+
|
|
562
|
+
Write non-ASCII JSON explicitly as UTF-8 and keep stdout separate from stderr:
|
|
563
|
+
|
|
564
|
+
```powershell
|
|
565
|
+
$request | ConvertTo-Json -Depth 100 |
|
|
566
|
+
Set-Content -Encoding utf8 operations.json
|
|
567
|
+
|
|
568
|
+
$resultJson = sdoc apply document.sdoc --operations operations.json --json 2>error.json
|
|
569
|
+
if ($LASTEXITCODE -ne 0) {
|
|
570
|
+
$errorResult = Get-Content -Raw -Encoding utf8 error.json | ConvertFrom-Json
|
|
571
|
+
throw "$($errorResult.diagnostics[0].code): $($errorResult.diagnostics[0].message)"
|
|
572
|
+
}
|
|
573
|
+
$result = $resultJson | ConvertFrom-Json
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
## Exit codes
|
|
577
|
+
|
|
578
|
+
| Code | Meaning | Representative diagnostic |
|
|
579
|
+
|---:|---|---|
|
|
580
|
+
| 0 | Success | No diagnostic |
|
|
581
|
+
| 2 | CLI argument or operation request error | `CLI_CONFLICTING_OPTIONS`, `CLI_MISSING_OPERATIONS` |
|
|
582
|
+
| 3 | Document, template, invariant, or legacy-upgrade error | `LEGACY_UPGRADE_REQUIRED` |
|
|
583
|
+
| 4 | Stale revision or precondition conflict | `STALE_REVISION` |
|
|
584
|
+
| 5 | File I/O error | `CLI_READ_FAILED` |
|
|
585
|
+
|
|
586
586
|
On failure, inspect `diagnostics[].code` rather than matching human-readable
|
|
587
587
|
messages. The machine-readable `category` is one of `argument`, `document`,
|
|
588
588
|
`conflict`, `io`, or `internal`. Categories describe the failure source and do
|
|
589
589
|
not replace exit codes; in particular, an `IoError` is categorized as `io`
|
|
590
590
|
while retaining its existing exit code.
|
|
591
|
-
|
|
592
|
-
## Diagnostic recovery
|
|
593
|
-
|
|
594
|
-
Diagnostic messages are explanatory text, not a parsing contract. Automations
|
|
595
|
-
must branch on `diagnostics[].code` from explicit `--json` output.
|
|
596
|
-
|
|
597
|
-
| Diagnostic code(s) | Likely cause | Recovery |
|
|
598
|
-
|---|---|---|
|
|
591
|
+
|
|
592
|
+
## Diagnostic recovery
|
|
593
|
+
|
|
594
|
+
Diagnostic messages are explanatory text, not a parsing contract. Automations
|
|
595
|
+
must branch on `diagnostics[].code` from explicit `--json` output.
|
|
596
|
+
|
|
597
|
+
| Diagnostic code(s) | Likely cause | Recovery |
|
|
598
|
+
|---|---|---|
|
|
599
599
|
| `CLI_UNKNOWN_*`, `CLI_MISSING_*`, `CLI_CONFLICTING_OPTIONS`, `CLI_OPTION_REQUIRES_ID` | Misspelled command/flag, omitted value, or incompatible/dependent flags | Run the command-specific `--help`, correct the invocation, and retry |
|
|
600
600
|
| `CLI_INVALID_TARGET_PATH` | `--target-path` is not a slash-delimited non-negative integer path | Copy the path from `inspect.blocks[].path` and format it like `/1/0` |
|
|
601
601
|
| `CLI_INVALID_POSITIVE_INTEGER`, `CLI_INVALID_PROJECTION`, `CLI_INVALID_CATALOG` | A projected read option has a malformed number or unsupported selector | Use `sdoc inspect --help` and pass a documented projection/catalog and canonical positive integer |
|
|
602
602
|
| `CLI_PROJECTION_REQUIRED`, `CLI_PROJECTION_REQUIRES_TARGET`, `CLI_PROJECTION_FORBIDS_TARGET`, `CLI_PROJECTION_OPTION_NOT_SUPPORTED` | Projected read flags are missing their projection, target, or valid projection-specific combination | Follow the projection option matrix above; legacy targets remain valid only when no read-only flags are supplied |
|
|
603
603
|
| `CLI_INVALID_UTF8` | Operation file or stdin bytes are not valid UTF-8 | Re-encode the complete JSON request as UTF-8 and retry; the document and sibling lock are untouched |
|
|
604
604
|
| `CLI_INVALID_JSON`, `INVALID_OPERATION_REQUEST`, `INVALID_OPERATION` | Malformed operations JSON or a request that does not match `sdoc.operations/1` | Validate against the packaged operation schema; inspect `operationIndex` when present |
|
|
605
|
-
| `MALFORMED_JSON`, `DOCUMENT_SCHEMA_INVALID`, `UNSUPPORTED_VERSION` | The input is not valid UTF-8 JSON, violates the document schema, or uses an unsupported SDOC version | Repair or migrate the source; do not force a write |
|
|
606
|
-
| `LEGACY_UPGRADE_REQUIRED` | A legacy `.tiptap.json` mutation omitted the explicit upgrade flag | Re-run with `--upgrade-legacy`, preview first, then add `--write` if intended |
|
|
605
|
+
| `MALFORMED_JSON`, `DOCUMENT_SCHEMA_INVALID`, `UNSUPPORTED_VERSION` | The input is not valid UTF-8 JSON, violates the document schema, or uses an unsupported SDOC version | Repair or migrate the source; do not force a write |
|
|
606
|
+
| `LEGACY_UPGRADE_REQUIRED` | A legacy `.tiptap.json` mutation omitted the explicit upgrade flag | Re-run with `--upgrade-legacy`, preview first, then add `--write` if intended |
|
|
607
607
|
| `STALE_REVISION` | The document bytes changed after inspection | Re-inspect the current file and rebuild the whole request from that revision |
|
|
608
608
|
| `INVALID_READ_CURSOR`, `STALE_READ_CURSOR`, `READ_CURSOR_SCOPE_MISMATCH` | A cursor is malformed, the exact source bytes changed, or it belongs to another projection/query | Restart the projection from its first page using the current document; never edit or reuse a cursor across queries |
|
|
609
609
|
| `PROJECTION_ITEM_TOO_LARGE` | The next complete catalog entry or subtree cannot fit the requested byte/node budget | Increase the reported limiting budget; projection pages never split a complete item |
|
|
610
|
-
| `TARGET_NOT_FOUND`, `TARGET_NOT_BLOCK`, `TARGET_TYPE_MISMATCH`, `TARGET_DIGEST_MISMATCH` | A selected path/ID is absent, is not a block, changed type, or no longer matches its snapshot | Re-inspect and use the returned `operationTarget`; do not weaken the precondition |
|
|
611
|
-
| `SECTION_OPERATION_REQUIRED`, `HEADING_TARGET_REQUIRED`, `SECTION_TARGET_REQUIRED`, `TITLE_H1_TARGET_REQUIRED` | A block operation was used for a heading, or a title target was not H1 | Use the matching heading/section operation and an inspected heading target |
|
|
612
|
-
| `INVALID_HEADING_LEVEL`, `SECTION_LEVEL_OUT_OF_RANGE` | A requested heading level is outside 1-6, or shifting the section would push a descendant outside that range | Choose a valid target level that keeps every descendant heading within 1-6 |
|
|
613
|
-
| `FORMATTED_HEADING` | Replacing a rich heading would discard marks or inline nodes | Preserve it, or explicitly use `discardFormatting` / `--discard-formatting` |
|
|
614
|
-
| `ATTRIBUTE_NOT_ALLOWED`, `NODE_TYPE_CHANGE` | An attr is not allowed for the node, or replacement changes its type | Consult the node catalog/schema and keep replacements type-compatible |
|
|
615
|
-
| `ID_RENAME_NOT_SUPPORTED`, `ID_RENAME_REQUIRES_EXISTING_ID`, `INVALID_NEW_ID` | An ID rename targeted an unsupported or provisional node, or supplied an invalid new ID | Target an inspected heading/table with an existing persistent ID and choose a unique non-reserved ID |
|
|
616
|
-
| `NEW_NONPORTABLE_ASSET`, `NEW_DANGLING_REFERENCE`, `NEW_UNSAFE_LINK` | The batch introduces an invalid asset path, missing internal target, or unsafe link | Use `./images/...` or `./drawio/*.drawio.svg`, create referenced IDs, and use a safe URL |
|
|
610
|
+
| `TARGET_NOT_FOUND`, `TARGET_NOT_BLOCK`, `TARGET_TYPE_MISMATCH`, `TARGET_DIGEST_MISMATCH` | A selected path/ID is absent, is not a block, changed type, or no longer matches its snapshot | Re-inspect and use the returned `operationTarget`; do not weaken the precondition |
|
|
611
|
+
| `SECTION_OPERATION_REQUIRED`, `HEADING_TARGET_REQUIRED`, `SECTION_TARGET_REQUIRED`, `TITLE_H1_TARGET_REQUIRED` | A block operation was used for a heading, or a title target was not H1 | Use the matching heading/section operation and an inspected heading target |
|
|
612
|
+
| `INVALID_HEADING_LEVEL`, `SECTION_LEVEL_OUT_OF_RANGE` | A requested heading level is outside 1-6, or shifting the section would push a descendant outside that range | Choose a valid target level that keeps every descendant heading within 1-6 |
|
|
613
|
+
| `FORMATTED_HEADING` | Replacing a rich heading would discard marks or inline nodes | Preserve it, or explicitly use `discardFormatting` / `--discard-formatting` |
|
|
614
|
+
| `ATTRIBUTE_NOT_ALLOWED`, `NODE_TYPE_CHANGE` | An attr is not allowed for the node, or replacement changes its type | Consult the node catalog/schema and keep replacements type-compatible |
|
|
615
|
+
| `ID_RENAME_NOT_SUPPORTED`, `ID_RENAME_REQUIRES_EXISTING_ID`, `INVALID_NEW_ID` | An ID rename targeted an unsupported or provisional node, or supplied an invalid new ID | Target an inspected heading/table with an existing persistent ID and choose a unique non-reserved ID |
|
|
616
|
+
| `NEW_NONPORTABLE_ASSET`, `NEW_DANGLING_REFERENCE`, `NEW_UNSAFE_LINK` | The batch introduces an invalid asset path, missing internal target, or unsafe link | Use `./images/...` or `./drawio/*.drawio.svg`, create referenced IDs, and use a safe URL |
|
|
617
617
|
| `DUPLICATE_ID` | The document contains conflicting persistent IDs | Assign unique IDs before retrying |
|
|
618
618
|
| `CLI_TEMPLATE_INVALID` | An explicit template has malformed UTF-8, invalid JSON, or violates the template contract | Repair or re-encode the template as UTF-8; creation leaves the destination absent |
|
|
619
|
-
| `CLI_TARGET_EXISTS` | `create` would overwrite an existing file | Choose a new path; the CLI never overwrites during creation |
|
|
620
|
-
| `CLI_LOCK_UNAVAILABLE` | Another writer holds the sibling lock, or its owner cannot be reclaimed safely | Wait for a known writer to finish. Remove an abandoned lock manually only after confirming no writer is active, then re-inspect before retrying |
|
|
621
|
-
| `CLI_READ_FAILED`, `CLI_ATOMIC_WRITE_FAILED` | Filesystem access or atomic replacement failed | Check path, permissions, free space, and filesystem support; verify the file before retrying |
|
|
622
|
-
|
|
623
|
-
Warnings such as `NONPORTABLE_ASSET`, `DANGLING_REFERENCE`, `UNSAFE_LINK`, and
|
|
624
|
-
`LEGACY_FILE_EXTENSION_RETAINED` describe pre-existing or retained conditions.
|
|
625
|
-
They do not authorize a later mutation to add or increase the same violation.
|
|
619
|
+
| `CLI_TARGET_EXISTS` | `create` would overwrite an existing file | Choose a new path; the CLI never overwrites during creation |
|
|
620
|
+
| `CLI_LOCK_UNAVAILABLE` | Another writer holds the sibling lock, or its owner cannot be reclaimed safely | Wait for a known writer to finish. Remove an abandoned lock manually only after confirming no writer is active, then re-inspect before retrying |
|
|
621
|
+
| `CLI_READ_FAILED`, `CLI_ATOMIC_WRITE_FAILED` | Filesystem access or atomic replacement failed | Check path, permissions, free space, and filesystem support; verify the file before retrying |
|
|
622
|
+
|
|
623
|
+
Warnings such as `NONPORTABLE_ASSET`, `DANGLING_REFERENCE`, `UNSAFE_LINK`, and
|
|
624
|
+
`LEGACY_FILE_EXTENSION_RETAINED` describe pre-existing or retained conditions.
|
|
625
|
+
They do not authorize a later mutation to add or increase the same violation.
|