bun-docx 0.18.0 → 0.19.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 CHANGED
@@ -10,28 +10,21 @@
10
10
 
11
11
  ## Install
12
12
 
13
- **Standalone binary** (no Bun required):
13
+ **npm** — the simplest path (requires Bun >= 1.3):
14
14
 
15
15
  ```sh
16
- curl -fsSL https://raw.githubusercontent.com/kklimuk/docx-cli/main/install.sh | sh
16
+ bun add -g bun-docx
17
+ # or run without installing:
18
+ bunx bun-docx read doc.docx
17
19
  ```
18
20
 
19
- Honors `PREFIX` (default `$HOME/.local/bin`) and `VERSION` (default `latest`):
21
+ **Standalone binary** (no Bun required). Every release publishes prebuilt binaries plus a `SHA256SUMS` manifest, and the installer **verifies the binary's SHA-256** before installing:
20
22
 
21
23
  ```sh
22
- PREFIX=/usr/local sh -c "$(curl -fsSL https://raw.githubusercontent.com/kklimuk/docx-cli/main/install.sh)"
23
- VERSION=v0.2.0 sh -c "$(curl -fsSL https://raw.githubusercontent.com/kklimuk/docx-cli/main/install.sh)"
24
+ curl -fsSL https://raw.githubusercontent.com/kklimuk/docx-cli/main/install.sh | sh
24
25
  ```
25
26
 
26
- Pre-built binaries are published for linux/x64, linux/arm64, darwin/x64, darwin/arm64, windows/x64.
27
-
28
- **npm** (requires Bun >= 1.3):
29
-
30
- ```sh
31
- bun add -g bun-docx
32
- # or
33
- bunx bun-docx read doc.docx
34
- ```
27
+ Honors `PREFIX` (default `$HOME/.local/bin`) and `VERSION` (default `latest`). Pre-built for linux/x64, linux/arm64, darwin/x64, darwin/arm64, windows/x64. Prefer to inspect first? Download `docx-<platform>` + `SHA256SUMS` from the [latest release](https://github.com/kklimuk/docx-cli/releases/latest), verify, `chmod +x`, and put it on `PATH`.
35
28
 
36
29
  ## Quick example: filling out an NDA
37
30
 
@@ -68,11 +61,55 @@ docx comments add mnda-filled.docx --at p7:0-30 \
68
61
 
69
62
  Open `mnda-filled.docx` in Word: tracked changes and comments appear in the review pane, ready to accept, reject, or reply. Or run `docx track-changes accept mnda-filled.docx --all` to bake them in from the CLI.
70
63
 
64
+ ## Use as an agent skill
65
+
66
+ docx-cli ships as an [Agent Skill](https://agentskills.io) — one `SKILL.md` that works across Claude Code, Codex, Pi, and the other harnesses that read the open skill format. The skill teaches the locator model and the redline / comment / fill workflows, then defers to `docx <command> --help` at runtime, so it can't go stale.
67
+
68
+ **Why a skill?** docx-cli is built for the _weakest, cheapest_ agents. In our weak-agent benchmark — 6 real document tasks (fill a contract, redline, comment, restyle, author from scratch), graded against Word renders, 3 runs each — Haiku driving docx-cli completed **4.3/6** tasks versus **0.7/6** for the default Claude skill, at roughly **2.5× fewer tokens**; with Sonnet it's **6/6 vs 4/6**, with roughly 2x fewer tokens. And every docx-cli output opened cleanly in Word on the first try — it never emits a file the renderer rejects. (Methodology and harness: [`.claude/skills/weak-agent-test`](.claude/skills/weak-agent-test).)
69
+
70
+ ### Install
71
+
72
+ **Any agent ([skills.sh](https://skills.sh))** — one cross-harness command, installs into whichever agent you're using:
73
+
74
+ ```sh
75
+ npx skills add kklimuk/docx-cli
76
+ ```
77
+
78
+ **Claude Code** — one-line plugin install:
79
+
80
+ ```
81
+ /plugin marketplace add kklimuk/docx-cli
82
+ /plugin install docx-cli@docx-cli
83
+ ```
84
+
85
+ **Codex** — add the marketplace (the plugin's skills auto-discover):
86
+
87
+ ```
88
+ codex plugin marketplace add kklimuk/docx-cli
89
+ ```
90
+
91
+ **Pi** — one-command install (the `pi` manifest in `package.json` pulls in the skill), then invoke `/skill:docx-cli`:
92
+
93
+ ```
94
+ pi install git:github.com/kklimuk/docx-cli # global; add -l for a project (team-shared) install
95
+ # manual alternative: pi --skill /path/to/docx-cli/skills/docx-cli
96
+ ```
97
+
98
+ **Any harness / manual** — drop [`skills/docx-cli/`](skills/docx-cli) into your agent's skills directory (e.g. `~/.claude/skills/` or the cross-tool `~/.agents/skills/`). On first activation the skill's [`scripts/bootstrap.sh`](skills/docx-cli/scripts/bootstrap.sh) installs the `docx` binary (and self-updates a stale one).
99
+
100
+ ### Keeping the skill current
101
+
102
+ The binary is the source of truth: `docx info skill` prints the canonical `SKILL.md` for the installed version, and a CI test fails if the committed copy drifts. Regenerate after any change with:
103
+
104
+ ```
105
+ docx info skill > skills/docx-cli/SKILL.md
106
+ ```
107
+
71
108
  ## `docx <command> --help` is the authoritative contract
72
109
 
73
110
  > **Agents: run `docx <command> --help` before composing a call.** Every command's `--help` is the source of truth for its flags, locator forms, and exact output shape — this README is a map, not the territory. Two more must-reads:
74
111
  >
75
- > - **`docx info locators`** — the canonical locator grammar (`--json` for a machine-readable form). The top-level `docx --help` says it outright: *"It is highly recommended to agents to run `docx info locators` to understand their capabilities."*
112
+ > - **`docx info locators`** — the canonical locator grammar (`--json` for a machine-readable form). The top-level `docx --help` says it outright: _"It is highly recommended to agents to run `docx info locators` to understand their capabilities."_
76
113
  > - **`docx info schema`** — the AST type definitions (`--ts` for TypeScript source) that `read --ast` emits.
77
114
 
78
115
  ## Command reference
@@ -129,7 +166,7 @@ clean):
129
166
  for a section silently turned layout into border paragraphs). A hand-authored
130
167
  `---` now unambiguously means a thematic break.
131
168
  - **Page geometry** rides a leading `<!-- docx:page sN orientation="landscape"
132
- size="…in" margins="…in" text-width="…in" -->` note when the page deviates from
169
+ size="…in" margins="…in" text-width="…in" -->` note when the page deviates from
133
170
  US-Letter-portrait-1″ — `text-width` is the usable column width, and the leading
134
171
  `sN` is the section to re-apply against. A `varies="by-section"` attribute is added
135
172
  when a later section's page setup differs from the leading one — and in that case
@@ -167,7 +204,7 @@ clean):
167
204
  the content it governs. The text lives in the comment attribute
168
205
  so the importer drops it (it can't re-inject into the body); full entries are in
169
206
  `read --ast` under `headers`/`footers` (`Marginal[]`). Set with `docx
170
- headers`/`docx footers`.
207
+ headers`/`docx footers`.
171
208
  - **Track-changes state** rides a head `<!-- docx:track-changes on -->` line when the
172
209
  document's tracking toggle is enabled (deviation-only — off emits nothing), so an
173
210
  agent sees that subsequent edits will be redlined without inspecting `settings.xml`.
@@ -309,33 +346,33 @@ docx track-changes apply FILE [--accept H ...] [--reject H ...]
309
346
  ```
310
347
 
311
348
  > **One rule to memorize: addressing an existing thing is always `--at`.**
312
- > `comments reply/resolve/delete`, `footnotes/endnotes edit/delete`, `images extract/replace/delete`, `hyperlinks replace/delete`, `tables *`, `track-changes accept/reject`, `edit`, and `delete` all take `--at LOCATOR`. The exceptions are positional or directional by nature: `insert` uses `--after`/`--before LOCATOR` (or `--at-start`/`--at-end` for the document boundaries, no locator); `read` slices with `--from`/`--to LOCATOR`; `wc` takes a positional `[LOCATOR]`; `find`/`replace` take a positional `QUERY`/`PATTERN` (and `replace` accepts an optional `--at pN` to *confine* the substitution to one paragraph). `images extract --to DIR` is an *output directory*, not a locator.
349
+ > `comments reply/resolve/delete`, `footnotes/endnotes edit/delete`, `images extract/replace/delete`, `hyperlinks replace/delete`, `tables *`, `track-changes accept/reject`, `edit`, and `delete` all take `--at LOCATOR`. The exceptions are positional or directional by nature: `insert` uses `--after`/`--before LOCATOR` (or `--at-start`/`--at-end` for the document boundaries, no locator); `read` slices with `--from`/`--to LOCATOR`; `wc` takes a positional `[LOCATOR]`; `find`/`replace` take a positional `QUERY`/`PATTERN` (and `replace` accepts an optional `--at pN` to _confine_ the substitution to one paragraph). `images extract --to DIR` is an _output directory_, not a locator.
313
350
 
314
351
  ## Output contract
315
352
 
316
353
  The CLI is built for non-interactive agents. **Exit code is the success signal**, output is data:
317
354
 
318
- | Exit | Meaning | Error codes |
319
- | ---- | ------- | ----------- |
320
- | `0` | success | — |
321
- | `2` | usage / bad locator | `USAGE`, `INVALID_LOCATOR` |
355
+ | Exit | Meaning | Error codes |
356
+ | ---- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
357
+ | `0` | success | — |
358
+ | `2` | usage / bad locator | `USAGE`, `INVALID_LOCATOR` |
322
359
  | `3` | addressed thing not found | `FILE_NOT_FOUND`, `PART_NOT_FOUND`, `BLOCK_NOT_FOUND`, `COMMENT_NOT_FOUND`, `IMAGE_NOT_FOUND`, `HYPERLINK_NOT_FOUND`, `TRACKED_CHANGE_NOT_FOUND`, `MATCH_NOT_FOUND` |
323
- | `1` | general failure | `NOT_A_ZIP`, `TRACKED_CHANGE_CONFLICT`, `TABLE_STRUCTURE`, `IMAGE_SOURCE`, `RENDER_ENGINE`, `RENDER_FAILED`, `UNHANDLED` |
360
+ | `1` | general failure | `NOT_A_ZIP`, `TRACKED_CHANGE_CONFLICT`, `TABLE_STRUCTURE`, `IMAGE_SOURCE`, `RENDER_ENGINE`, `RENDER_FAILED`, `UNHANDLED` |
324
361
 
325
362
  **Errors** print `{code, error, hint?}` JSON to stdout with a nonzero exit — note there is **no `ok` field**; the exit code plus `code` are the unambiguous signal.
326
363
 
327
364
  **The `ok` field appears in exactly one place: the `--verbose` success ack** (`{ok:true, operation, path, …}`). Without `-v`, success output is shaped for the next command:
328
365
 
329
- | Command class | Default stdout on success | `--verbose` |
330
- | ------------- | ------------------------- | ----------- |
331
- | **Mutator that mints a new handle** — `comments add`→`cN`, `comments reply`→`cN`, `footnotes/endnotes add`→`fnN`/`enN`, `hyperlinks add`→`linkN`, `insert`→the new `pN` | the bare locator(s), **one per line** (a multi-block `--markdown` insert prints several) | full `{ok:true,…}` ack |
332
- | **Mutator with no new handle** — `edit`, `delete`, `replace`, `create`, `comments resolve/delete`, `images replace/delete`, `hyperlinks replace/delete`, `footnotes/endnotes edit/delete`, `headers/footers set/clear`, `tables *`, `track-changes accept/reject` & toggle | **one-line confirmation** — `<operation> <target>` (e.g. `edit t1:r0c1:p0`, `edit 7 changes`, `replace 0 occurrences replaced`) (exit `0`) | full `{ok:true,…}` ack |
333
- | `find` | matched span locators, one per line (no matches → nothing, exit `0`) | `--json` → `{ totalMatches, query, view, matches:[…], normalizedQuery? }` |
334
- | `wc` | the bare count (whole-doc adds a tab-separated `sections` column, like `wc`) | `--json` → `{ words, scope, view, sections? }` |
335
- | `outline` | indented `LOCATOR⇥TEXT` tree (two spaces per level) | `--json` → nested `[{ id, locator, level, style, text, children }]` |
336
- | `read` | GFM Markdown; each paragraph carries its `pN` locator once — a trailing bare `<!-- pN -->` on plain paragraphs, or the leading token of its `<!-- docx:p pN … -->` note when one is emitted | `--ast` → the JSON AST body (`docx info schema`) |
337
- | `render` | image paths, one per line | `--verbose` → `{ok, operation, path, engine, output, pages}` |
338
- | `* list` (all eight `list` verbs) | a **bare JSON array**; each item's `id` is its `--at` handle | — |
366
+ | Command class | Default stdout on success | `--verbose` |
367
+ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
368
+ | **Mutator that mints a new handle** — `comments add`→`cN`, `comments reply`→`cN`, `footnotes/endnotes add`→`fnN`/`enN`, `hyperlinks add`→`linkN`, `insert`→the new `pN` | the bare locator(s), **one per line** (a multi-block `--markdown` insert prints several) | full `{ok:true,…}` ack |
369
+ | **Mutator with no new handle** — `edit`, `delete`, `replace`, `create`, `comments resolve/delete`, `images replace/delete`, `hyperlinks replace/delete`, `footnotes/endnotes edit/delete`, `headers/footers set/clear`, `tables *`, `track-changes accept/reject` & toggle | **one-line confirmation** — `<operation> <target>` (e.g. `edit t1:r0c1:p0`, `edit 7 changes`, `replace 0 occurrences replaced`) (exit `0`) | full `{ok:true,…}` ack |
370
+ | `find` | matched span locators, one per line (no matches → nothing, exit `0`) | `--json` → `{ totalMatches, query, view, matches:[…], normalizedQuery? }` |
371
+ | `wc` | the bare count (whole-doc adds a tab-separated `sections` column, like `wc`) | `--json` → `{ words, scope, view, sections? }` |
372
+ | `outline` | indented `LOCATOR⇥TEXT` tree (two spaces per level) | `--json` → nested `[{ id, locator, level, style, text, children }]` |
373
+ | `read` | GFM Markdown; each paragraph carries its `pN` locator once — a trailing bare `<!-- pN -->` on plain paragraphs, or the leading token of its `<!-- docx:p pN … -->` note when one is emitted | `--ast` → the JSON AST body (`docx info schema`) |
374
+ | `render` | image paths, one per line | `--verbose` → `{ok, operation, path, engine, output, pages}` |
375
+ | `* list` (all eight `list` verbs) | a **bare JSON array**; each item's `id` is its `--at` handle | — |
339
376
 
340
377
  `--dry-run` always prints a preview object (no `ok`) and writes nothing; it wins over `-o/--output`.
341
378
 
@@ -343,16 +380,16 @@ The CLI is built for non-interactive agents. **Exit code is the success signal**
343
380
 
344
381
  Locators come in two flavors. **Positional block ids** (`pN`, `tN`, `sN`) are derived from document order and **shift after structural edits** — re-read between non-trivial mutations. **Entity ids** (`cN`, `imgN`, `linkN`, `fnN`, `enN`, `tcN`, `eqN`) are surfaced by a `list` verb (or `read --ast`) and are what you pass to `--at`:
345
382
 
346
- | Id | Discover with | Used by |
347
- | -- | ------------- | ------- |
348
- | `pN` / `tN` / `sN` (block ids) | `docx read FILE` (the `<!-- pN -->` trailers), `docx read FILE --ast`, `docx outline FILE` (heading `pN`s), or `docx render` page images | `read`, `edit`, `insert`, `delete`, `wc`, `find` results |
349
- | `cN` (comment) | `docx comments list FILE` | `comments reply/resolve/delete --at` |
350
- | `fnN` / `enN` (foot/endnote) | `docx footnotes list FILE` / `docx endnotes list FILE` | `footnotes/endnotes edit/delete --at` |
351
- | `hdrN` / `ftrN` (header/footer) | `docx headers list FILE` / `docx footers list FILE` (or `read --ast`) | addressed by section+type, not the id: `headers/footers set --at sN --type T` |
352
- | `imgN` (image) | `docx images list FILE` | `images extract/replace/delete --at` |
353
- | `linkN` (hyperlink) | `docx hyperlinks list FILE` | `hyperlinks replace/delete --at` |
354
- | `tcN` (tracked change) | `docx track-changes list FILE` | `track-changes accept/reject --at` |
355
- | `eqN` (equation) | `docx read FILE --ast` (run `latex` field) | `edit --at eqN --equation` |
383
+ | Id | Discover with | Used by |
384
+ | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
385
+ | `pN` / `tN` / `sN` (block ids) | `docx read FILE` (the `<!-- pN -->` trailers), `docx read FILE --ast`, `docx outline FILE` (heading `pN`s), or `docx render` page images | `read`, `edit`, `insert`, `delete`, `wc`, `find` results |
386
+ | `cN` (comment) | `docx comments list FILE` | `comments reply/resolve/delete --at` |
387
+ | `fnN` / `enN` (foot/endnote) | `docx footnotes list FILE` / `docx endnotes list FILE` | `footnotes/endnotes edit/delete --at` |
388
+ | `hdrN` / `ftrN` (header/footer) | `docx headers list FILE` / `docx footers list FILE` (or `read --ast`) | addressed by section+type, not the id: `headers/footers set --at sN --type T` |
389
+ | `imgN` (image) | `docx images list FILE` | `images extract/replace/delete --at` |
390
+ | `linkN` (hyperlink) | `docx hyperlinks list FILE` | `hyperlinks replace/delete --at` |
391
+ | `tcN` (tracked change) | `docx track-changes list FILE` | `track-changes accept/reject --at` |
392
+ | `eqN` (equation) | `docx read FILE --ast` (run `latex` field) | `edit --at eqN --equation` |
356
393
 
357
394
  Each `list` verb prints a bare JSON array where every item's `id` is exactly the handle you feed back to `--at` — pipe through `jq` to filter (`docx comments list doc.docx | jq '.[] | select(.author=="Jane")'`).
358
395
 
@@ -370,21 +407,21 @@ cN imgN linkN fnN enN tcN eqN entity ids (comment / image / hyper
370
407
  footnote / endnote / tracked-change / equation)
371
408
  ```
372
409
 
373
- **Offset semantics: character offsets are 0-based, start-inclusive, end-exclusive** — `p3:5-20` is the 15 characters at indices 5..19 of paragraph 3. Offsets count the *visible* text of the paragraph in the selected view (accepted by default).
410
+ **Offset semantics: character offsets are 0-based, start-inclusive, end-exclusive** — `p3:5-20` is the 15 characters at indices 5..19 of paragraph 3. Offsets count the _visible_ text of the paragraph in the selected view (accepted by default).
374
411
 
375
412
  **Nested tables chain the same syntax** arbitrarily deep — `t0:r2c1:t0:r0c0:p0` is the first paragraph of the (0,0) cell of the first table nested inside the (2,1) cell of the document's first table.
376
413
 
377
414
  **Not every command accepts every form** — each command's `--at`/`--from`/positional help lists exactly what it takes. The shapes:
378
415
 
379
- | Form | Accepted by |
380
- | ---- | ----------- |
381
- | `pN`, `tN`, `sN`, `tN:rRcC:pK` (blocks) | `read --from/--to`, `insert --after/--before`, `wc`, `comments add` |
382
- | `pN`, `pN:S-E`, `pN-pM`, `sN`, `eqN`, `tN:rRcC:pK`, `tN:rRcC:pK:S-E` | `edit --at` (span/cell forms strip or replace just that range) |
383
- | `pN`, `pN-pM`, `tN`, `sN`, `tN:rRcC:pK` | `delete --at` |
384
- | `pN:S-E`, `pN:S-pM:E`, `tN:rRcC:pK:S-E` (spans) | `comments add --at`, `hyperlinks add --at` (single paragraph), `find`/`wc` results |
385
- | `pN[:offset]` (point) | `footnotes/endnotes add --at` |
386
- | `cN` / `fnN` / `enN` / `imgN` / `linkN` / `tcN` (entities) | the matching noun's `--at` (the `c`/`fn`/`en`/`img`/`link`/`tc` prefix is optional) |
387
- | `tN`, `tN:rR`, `tN:cC`, `tN:rRcC`, `tN:rR1cC1-rR2cC2` | the `tables` verbs |
416
+ | Form | Accepted by |
417
+ | -------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
418
+ | `pN`, `tN`, `sN`, `tN:rRcC:pK` (blocks) | `read --from/--to`, `insert --after/--before`, `wc`, `comments add` |
419
+ | `pN`, `pN:S-E`, `pN-pM`, `sN`, `eqN`, `tN:rRcC:pK`, `tN:rRcC:pK:S-E` | `edit --at` (span/cell forms strip or replace just that range) |
420
+ | `pN`, `pN-pM`, `tN`, `sN`, `tN:rRcC:pK` | `delete --at` |
421
+ | `pN:S-E`, `pN:S-pM:E`, `tN:rRcC:pK:S-E` (spans) | `comments add --at`, `hyperlinks add --at` (single paragraph), `find`/`wc` results |
422
+ | `pN[:offset]` (point) | `footnotes/endnotes add --at` |
423
+ | `cN` / `fnN` / `enN` / `imgN` / `linkN` / `tcN` (entities) | the matching noun's `--at` (the `c`/`fn`/`en`/`img`/`link`/`tc` prefix is optional) |
424
+ | `tN`, `tN:rR`, `tN:cC`, `tN:rRcC`, `tN:rR1cC1-rR2cC2` | the `tables` verbs |
388
425
 
389
426
  ## Common workflows
390
427
 
@@ -427,17 +464,17 @@ docx track-changes accept doc.docx --at tc0 --at tc2 # or --all
427
464
 
428
465
  **Span-aware comments & hyperlinks.** `comments add --at p3:5-20` (and `hyperlinks add`) find the runs containing offsets 5 and 20, split them at the boundaries (preserving `<w:rPr>` on both halves), and insert markers between the slices. Comments authored by older tools that lack `w14:paraId` (required by `commentsExtended.xml`) get a fresh paraId injected automatically on resolve/reply.
429
466
 
430
- **Tracked changes.** With `<w:trackChanges/>` set, `insert`/`edit`/`delete`/`replace` emit native `<w:ins>`/`<w:del>` (attributed via `--author`, `$DOCX_AUTHOR`, or `Reviewer`); pass `--track` to one of those commands (or the `tables` verbs / `images delete`) to track just that invocation even when the doc toggle is off. `edit --at pN --text` runs a word-level diff so unchanged words keep their formatting and only changed words are wrapped — the same shape Word produces mid-tracking. `accept`/`reject` handle run-level ins/del/moveFrom/moveTo, `sectPrChange`, paragraph-mark ins/del, and the table-structural revisions (rowIns/rowDel, cellIns/cellDel, tblGridChange, tcPrChange). OOXML has no tracked-change construct for hyperlink edits or image swaps, so under tracking those emit a `[docx-cli]` audit comment instead of a fake revision (image *deletion* is honest removal — it wraps a real `<w:del>`).
467
+ **Tracked changes.** With `<w:trackChanges/>` set, `insert`/`edit`/`delete`/`replace` emit native `<w:ins>`/`<w:del>` (attributed via `--author`, `$DOCX_AUTHOR`, or `Reviewer`); pass `--track` to one of those commands (or the `tables` verbs / `images delete`) to track just that invocation even when the doc toggle is off. `edit --at pN --text` runs a word-level diff so unchanged words keep their formatting and only changed words are wrapped — the same shape Word produces mid-tracking. `accept`/`reject` handle run-level ins/del/moveFrom/moveTo, `sectPrChange`, paragraph-mark ins/del, and the table-structural revisions (rowIns/rowDel, cellIns/cellDel, tblGridChange, tcPrChange). OOXML has no tracked-change construct for hyperlink edits or image swaps, so under tracking those emit a `[docx-cli]` audit comment instead of a fake revision (image _deletion_ is honest removal — it wraps a real `<w:del>`).
431
468
 
432
469
  **Rich content.** Images insert from a path, `data:` URI, or `http(s)` URL (bounded fetch; HEIC→JPEG transcode; SVG sanitized; non-public/metadata addresses refused at every redirect hop). Equations round-trip OOXML `<m:oMath>` ↔ LaTeX (reconstructed, not legacy plaintext) — authored via temml (LaTeX→MathML) plus an in-house MathML→OMML adapter, no LGPL deps. Code blocks emit one `CodeBlock`-styled paragraph per line with optional lowlight syntax highlighting (37 bundled languages); they collapse back to a GFM fenced block on read. GFM task lists round-trip Word's checkbox content control (and the Word-for-Web Wingdings-glyph variant), surfacing as `taskState` in the AST. Tables operate on a merge-aware logical grid so `gridSpan`/`vMerge` cells map onto physical `<w:tc>`, and structural edits refuse to bisect an existing merge.
433
470
 
434
471
  **Markdown dialect.** `create --from`, `insert/edit --markdown`, and the note bodies all parse the same GFM + math + CriticMarkup + inline-HTML-formatting dialect (remark + remark-gfm + remark-math + an in-house inline-surgery transform), composing the existing OOXML emitters. `read` emits a compatible dialect, so the read → edit → write loop round-trips (lossless for paragraphs, lists, and nested blockquotes; code/tables/math/headings inside a blockquote intentionally escape to top level on import). `read --ast` is the fully lossless JSON form.
435
472
 
436
- **Literal text — the parser-free channel.** When you want prose inserted *exactly* — reviewer notes, quoted excerpts, anything where Markdown would misfire — use `create --text-file PATH` / `insert --text-file PATH` (or `-` for stdin). Every character lands verbatim and each newline starts a new paragraph; nothing is interpreted, so `3. note` stays `3.` (no ordered-list renumber), and `*x*`, `[t](u)`, bare URLs, and `{++x++}` are kept as written. This exists because GFM corruption isn't always escapable: bare URLs autolink with no escape sequence at all, and CriticMarkup eats `{++…++}` regardless of backslashes — so a literal path is the only safe way to author untouched prose.
473
+ **Literal text — the parser-free channel.** When you want prose inserted _exactly_ — reviewer notes, quoted excerpts, anything where Markdown would misfire — use `create --text-file PATH` / `insert --text-file PATH` (or `-` for stdin). Every character lands verbatim and each newline starts a new paragraph; nothing is interpreted, so `3. note` stays `3.` (no ordered-list renumber), and `*x*`, `[t](u)`, bare URLs, and `{++x++}` are kept as written. This exists because GFM corruption isn't always escapable: bare URLs autolink with no escape sequence at all, and CriticMarkup eats `{++…++}` regardless of backslashes — so a literal path is the only safe way to author untouched prose.
437
474
 
438
- **Document-wide font.** `docx styles set-default-font FILE "Times New Roman"` sets the font in the two places a font actually lives — `word/styles.xml` `<w:docDefaults>` (the formal default) *and* the theme font scheme (`word/theme/theme1.xml`, major + minor `<a:latin>`), since real Word docs resolve their fonts *through* the theme and touching only one silently loses to the other. Body text and theme-following headings both adopt it; styles or runs that pin their own font (a code block's monospace, a deliberately-Arial run) are preserved and named in the ack, with `--all` to repoint even those. `--size N` sets the default size on the same write.
475
+ **Document-wide font.** `docx styles set-default-font FILE "Times New Roman"` sets the font in the two places a font actually lives — `word/styles.xml` `<w:docDefaults>` (the formal default) _and_ the theme font scheme (`word/theme/theme1.xml`, major + minor `<a:latin>`), since real Word docs resolve their fonts _through_ the theme and touching only one silently loses to the other. Body text and theme-following headings both adopt it; styles or runs that pin their own font (a code block's monospace, a deliberately-Arial run) are preserved and named in the ack, with `--all` to repoint even those. `--size N` sets the default size on the same write.
439
476
 
440
- **Edit & create styles.** `docx styles set FILE --at Heading1 --color 1F4E79 --size 16 --bold` rewrites the *style definition* in `word/styles.xml`, so every paragraph or run that uses the style picks up the change at once ("make all Heading 1s green") — the same run-/paragraph-formatting flags as `edit` (color/font/size/highlight/underline/caps + alignment/spacing/indentation for paragraph styles). `docx styles create FILE Callout --color C00000 --bold` mints a new paragraph or character style that `insert/edit --style Callout` can then apply. Editing an un-materialized built-in (`--at Heading3` on a doc that never used it) provisions it first; a paragraph with its own *direct* formatting keeps it (the override wins — the definition edit never touches the body). Style edits are **not** tracked changes even under track-changes — matching Word, which applies style-definition edits to `styles.xml` directly with no redline.
477
+ **Edit & create styles.** `docx styles set FILE --at Heading1 --color 1F4E79 --size 16 --bold` rewrites the _style definition_ in `word/styles.xml`, so every paragraph or run that uses the style picks up the change at once ("make all Heading 1s green") — the same run-/paragraph-formatting flags as `edit` (color/font/size/highlight/underline/caps + alignment/spacing/indentation for paragraph styles). `docx styles create FILE Callout --color C00000 --bold` mints a new paragraph or character style that `insert/edit --style Callout` can then apply. Editing an un-materialized built-in (`--at Heading3` on a doc that never used it) provisions it first; a paragraph with its own _direct_ formatting keeps it (the override wins — the definition edit never touches the body). Style edits are **not** tracked changes even under track-changes — matching Word, which applies style-definition edits to `styles.xml` directly with no redline.
441
478
 
442
479
  **List numbering.** `docx lists set FILE --at p12 --start 5` makes a numbered list begin at 5; `--format upper-roman` (or `lower-alpha`/`upper-alpha`/`lower-roman`) switches the glyph; `--restart` splits a fresh list off at that item; `--continue` makes a list pick up the previous list's numbering instead of restarting. `--at` names any item — the change applies to the whole list. The start round-trips through the markdown ordinal (the body reads `5. 6. 7.`); the glyph and any continue link, which GFM can't express, surface as a deviation-only `<!-- docx:list p12 format="upper-roman" -->` / `<!-- docx:list p20 continues -->` hint (dropped on import, like every `docx:` note — `read --ast` carries `list.start`/`list.format` losslessly). Untracked, matching Word, which records no revision for a list-numbering change.
443
480