bun-docx 0.15.0 → 0.16.0

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.
Files changed (3) hide show
  1. package/README.md +91 -13
  2. package/dist/index.js +5228 -1891
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -90,11 +90,15 @@ docx wc FILE [LOCATOR] [--accepted | --baseline | --current] [--json]
90
90
  docx outline FILE [--style-prefix S] [--json]
91
91
  docx styles FILE [--used] [--at STYLEID] [--json] # the style catalog (not in the body) — what --style NAMEs exist
92
92
  docx styles --catalog [--json] # built-in styles you can apply on demand (Title, Heading1–9, Quote, …), no FILE needed
93
+ docx styles set FILE --at STYLEID [--bold --color HEX --size PT --font NAME --space-before PT --indent-left IN …] # restyle every paragraph/run that uses the style
94
+ docx styles create FILE STYLEID [--type paragraph|character] [--name "…"] [--based-on STYLEID] [--next STYLEID] [formatting] # define a new custom style
93
95
  docx render FILE [--out DIR] [--engine word|libreoffice|auto] [--dpi N] [--pages 1-N] [--format png|jpg]
94
96
 
95
97
  docx comments list FILE [--include-resolved] [--thread cN]
96
98
  docx footnotes list FILE
97
99
  docx endnotes list FILE
100
+ docx headers list FILE
101
+ docx footers list FILE
98
102
  docx images list FILE
99
103
  docx hyperlinks list FILE
100
104
  docx track-changes list FILE
@@ -111,15 +115,37 @@ normal edits in place, `read --ast` is the lossless view, and `docx sections` /
111
115
  (only when a value differs from the document default, so a plain document stays
112
116
  clean):
113
117
 
118
+ - **Per-paragraph style/spacing/indent** — the most common annotation — rides a
119
+ `<!-- docx:p pN style="Caption" align="center" space-after="6pt" line-spacing="1" indent-left="0.25in" -->`
120
+ note, emitted deviation-only (only the attrs that differ from the style/document
121
+ default). Each attribute maps to the matching `edit`/`insert` flag (`--style`,
122
+ `--alignment`, `--space-before`/`--space-after`, `--line-spacing`,
123
+ `--indent-left`/`--indent-right`/`--first-line`/`--hanging`), so an agent reads a
124
+ value and re-applies it. The paragraph's locator rides this note as its leading `pN`
125
+ token, so an annotated paragraph does NOT also get a bare `<!-- pN -->` (only
126
+ undeviating paragraphs get the bare locator). Full properties are in `read --ast`.
114
127
  - **Section breaks** render as `<!-- docx:section sN cols="2" type="continuous" -->`
115
128
  on their own line — never a bare `---` (that's a thematic break, and emitting it
116
129
  for a section silently turned layout into border paragraphs). A hand-authored
117
130
  `---` now unambiguously means a thematic break.
118
- - **Page geometry** rides a leading `<!-- docx:page orientation="landscape"
131
+ - **Page geometry** rides a leading `<!-- docx:page sN orientation="landscape"
119
132
  size="…in" margins="…in" text-width="…in" -->` note when the page deviates from
120
- US-Letter-portrait-1″ — `text-width` is the usable column width. Exact twips are
121
- in `read --ast` (on each section break: `pageWidth`/`pageHeight`/`pageOrientation`/
122
- `margin*`).
133
+ US-Letter-portrait-1″ — `text-width` is the usable column width, and the leading
134
+ `sN` is the section to re-apply against. A `varies="by-section"` attribute is added
135
+ when a later section's page setup differs from the leading one — and in that case
136
+ the note fires **even if page 1 is plain default Letter-portrait-1″** (it then shows
137
+ just `text-width` + `varies="by-section"`), warning that the geometry shown describes
138
+ only the leading section; use `read --ast` for every section's exact geometry.
139
+ Exact twips are in `read --ast` (on each
140
+ section break: `pageWidth`/`pageHeight`/`pageOrientation`/`margin*`). Set it for the
141
+ WHOLE document with `docx sections --orientation/--size/--margins` (no `--at` →
142
+ every section gets it, so a multi-section doc doesn't leave the trailing section
143
+ behind), one section with `docx sections --at sN …`, or at `create` time; under
144
+ track-changes it records as one `<w:sectPrChange>` per section (accept/reject in Word).
145
+ Changing margins/size also **auto-realigns right-edge tab columns** (résumé
146
+ dates/locations): a LEFT tab calibrated to the old margins would overflow and wrap
147
+ at the new width, so page setup converts each to a RIGHT tab flush at the new
148
+ margin and reports how many it fixed — no second `--tabs right` step needed.
123
149
  - **Tables** carry a leading `<!-- docx:table t0 widths="1,2,3in" borders="double" -->`
124
150
  when columns are uneven or borders deviate from the default, plus a per-cell
125
151
  `<!-- docx:cell t0:r0c0 gridSpan="2" vMerge="continue" shading="FFE699" -->`
@@ -130,18 +156,38 @@ clean):
130
156
  `float`/`wrap`/`align`/`overflow` only when they deviate (an inline, in-bounds
131
157
  image shows just its size). `overflow` flags an image wider than the usable text
132
158
  column (`ImageRun.floating`/`wrap`/`align` + EMU extents in `read --ast`).
159
+ - **Headers / footers** surface as `<!-- docx:header text="Quarterly Report" -->`
160
+ / `<!-- docx:footer text="Page {page} of {pages}" -->` notes (the `type` attr
161
+ appears only for `first`/`even`). Fields read as tokens — `{page}` `{pages}`
162
+ `{date}` `{time}` `{styleref:NAME}` `{filename}` `{title}` `{author}` (`{time}`
163
+ read-only). A marginal that's
164
+ the same on every section rides the top; one that **differs by section** renders at
165
+ that section's **start** (alongside the `docx:section` note, which also renders at
166
+ the section's start with `applies-to="… (below)"`), so each hint reads right before
167
+ the content it governs. The text lives in the comment attribute
168
+ so the importer drops it (it can't re-inject into the body); full entries are in
169
+ `read --ast` under `headers`/`footers` (`Marginal[]`). Set with `docx
170
+ headers`/`docx footers`.
171
+ - **Track-changes state** rides a head `<!-- docx:track-changes on -->` line when the
172
+ document's tracking toggle is enabled (deviation-only — off emits nothing), so an
173
+ agent sees that subsequent edits will be redlined without inspecting `settings.xml`.
174
+ Toggle it with `docx track-changes FILE on|off`; the three tracked-change read views
175
+ (`--accepted`/`--current`/`--baseline`) are covered under the review loop below.
133
176
 
134
177
  ### Mutate (change FILE in place; `--dry-run`, `-v` everywhere; `-o PATH` on every mutator except `create`, whose positional FILE is already the output)
135
178
 
136
179
  ```sh
137
- docx create FILE [--title T] [--author A] [--text "..." | --text-file PATH | --from PATH.md | --from -] [--force]
180
+ docx create FILE [--title T] [--author A] [--text "..." | --text-file PATH | --from PATH.md | --from -] [--orientation O] [--size SIZE] [--margins M] [--header "..."] [--footer "..." | --page-numbers] [--force]
138
181
  docx insert FILE (--after | --before) LOCATOR <content> # LOCATOR = pN | tN | sN | tN:rRcC:pK
139
182
  docx insert FILE (--at-start | --at-end) <content> # no locator — prepend / append to the document
140
183
  docx edit FILE --at LOCATOR <content> # LOCATOR = pN | pN:S-E | pN-pM | sN | eqN | tN:rRcC:pK[:S-E]
141
- docx delete FILE --at LOCATOR # LOCATOR = pN | pN-pM | tN | sN
142
- docx sections FILE --at LOCATOR --columns N [--type T] # LOCATOR = pN-pM | pN (wrap a range in N columns) | sN (recount an existing section). Multi-column layout lives HERE, not in insert.
184
+ docx delete FILE --at LOCATOR # LOCATOR = pN | pN-pM | tN | sN | tN:rRcC:pK (cell paragraph)
185
+ docx sections FILE [--at LOCATOR] [--columns N] [--type T] [--orientation O] [--size SIZE] [--margins M] # LOCATOR = pN-pM | pN (wrap a range in N columns) | sN (edit one section's columns/type/page geometry). Multi-column layout AND page setup live HERE. PAGE GEOMETRY (margins/orientation/size) with NO --at applies to the WHOLE document (every section); --at sN targets one. Columns/type need --at.
143
186
  docx styles set-default-font FILE "Font Name" [--size N] [--all] # document-wide font: sets styles.xml docDefaults + theme major/minor; --all also repoints styles/runs that pin their own font
144
187
  docx replace FILE PATTERN REPLACEMENT [--regex] [--ignore-case] [--all] [--limit N] [--current | --baseline] [--exact] [--track] [--dry-run]
188
+ # Keeps the run's formatting (bold/font) and any tabs — the no-rebuild way to fill a
189
+ # formatted/tabbed template line (e.g. "**Org Name**⇥Date"); don't hand-build --runs to refill it.
190
+ # A span edit (find → edit --at pN:S-E --text NEW) does the same by locator.
145
191
 
146
192
  # Batch — apply many changes from ONE read (no re-reading between edits). Keys
147
193
  # on each JSONL line mirror the command's flags; all locators address the doc as
@@ -157,12 +203,23 @@ docx delete FILE --batch drop.jsonl # { at } per line — whole blocks (
157
203
  # insert/edit content selectors (run "docx insert --help" / "docx edit --help" for the full list):
158
204
  # --text "..." [--style NAME] [--alignment A] [--color HEX] [--bold] [--italic] [--url URL]
159
205
  # (a newline in --text becomes a line break <w:br/>, a tab becomes <w:tab/> — verse/addresses stay line-per-line)
206
+ # paragraph spacing/indent (insert + edit, alone or with content, per-entry in --batch, across a range):
207
+ # --space-before PT --space-after PT --line-spacing N(=1|1.5|2|single|double, or 15pt / "15pt atLeast")
208
+ # --indent-left IN --indent-right IN --first-line IN --hanging IN (points / inches; first-line ⊥ hanging;
209
+ # left/right/first-line accept a negative value to outdent into the margin; hanging stays non-negative)
210
+ # Under track-changes these record a tracked <w:pPrChange> (accept/reject in Word) — even when they ride
211
+ # along with --text; read surfaces them as a deviation-only <!-- docx:p … space-after="6pt" --> hint.
160
212
  # edit --tabs right fix a line whose tabbed-over content WRAPS (read flags it as `docx:layout … warn`,
161
213
  # and prints ONE consolidated fix-all summary at the top): swaps the fragile LEFT tab for a RIGHT tab
162
214
  # flush at the margin so a long value (e.g. a city) never wraps. Rides along with --text, works
163
215
  # per-entry in --batch, and on a RANGE (edit --at pN-pM --tabs right) cures every tab line at once.
164
- # edit --text "" rejected (it would leave an invisible blank paragraph) use `delete` to remove the
165
- # line, or `--runs '[]'` to blank it but keep an empty spacer.
216
+ # edit --text "" REMOVES the line (same as `delete`; a table cell's last paragraph is blanked, not
217
+ # deleted, so the cell stays valid). In --batch, `{"at":"pN","text":""}` or `{"at":"pN","delete":true}`
218
+ # removes a line — so a form-fill is ONE sweep: fill the cells with values, drop the leftover
219
+ # placeholder lines. Use `--runs '[]'` to blank a paragraph but keep an empty spacer. (Empty
220
+ # `--text` can't ride along with --clear/run-formatting/--style/--alignment/--tabs — those exit
221
+ # with a USAGE error; use `--runs '[]'` to keep a formatted empty spacer instead.) A SPAN's
222
+ # `--text ""` (pN:S-E) still deletes just those characters.
166
223
  # --runs '[{"type":"text","text":"X","bold":true}]'
167
224
  # --text-file PATH # (insert/create) LITERAL multi-paragraph text, NOT parsed — every char verbatim,
168
225
  # each newline = a new paragraph. For prose GFM would corrupt: "3. note" stays "3.", *x* / [t](u) / bare URLs / {++x++} untouched.
@@ -170,6 +227,13 @@ docx delete FILE --batch drop.jsonl # { at } per line — whole blocks (
170
227
  # --code "..." | --code-file PATH [--language LANG]
171
228
  # --equation "x^2 + y^2" [--display] (insert; edit also accepts --inline)
172
229
  # --clear bold,italic,highlight,color,size,font,…|all (edit; strip run formatting, keep text)
230
+ # --bold --italic --underline --strike --color HEX --highlight NAME --shade HEX --font NAME --size PT
231
+ # --caps --smallcaps --superscript --subscript (edit; SET run formatting on EXISTING text —
232
+ # the inverse of --clear. Alone they format a span/paragraph/range in place; with --text they
233
+ # fill AND format. Like --clear, applied directly — not recorded as a tracked change.)
234
+ # NOTE: in a single no-content call (or one --batch entry) these run-format SET flags and the
235
+ # paragraph properties (--style/--alignment/--space-*/--line-spacing/--indent-*/--first-line/
236
+ # --hanging/--tabs) can't ride together — use separate calls/entries, or add --text to set both.
173
237
  # --task checked|unchecked | --list bullet|ordered [--list-level N] (insert)
174
238
  # --task checked|unchecked (edit, flip in place)
175
239
  # --table --rows N --cols N [--widths "A,B,C"] [--table-width V] [--borders S] [--layout L] (insert)
@@ -190,6 +254,17 @@ docx endnotes add FILE --at pN[:offset] (--text "..." | --runs JSON | --mark
190
254
  docx endnotes edit FILE --at enN (--text "..." | --runs JSON | --markdown TEXT)
191
255
  docx endnotes delete FILE --at enN
192
256
 
257
+ # Headers & footers (one shared impl — "marginals"). Default placement is every
258
+ # page, all sections (--at sN targets one). Content: ONE primary source, except
259
+ # --text + one field = two-zone (text left, field right at a content-edge tab).
260
+ docx headers set FILE [--at sN] [--type default|first|even | --first-page | --even | --odd] \
261
+ [--text "..."] [--align left|center|right] \
262
+ [--page-number [--of-pages] | --date [--date-format FMT] | --style-ref STYLE | --field filename|title|author] \
263
+ [--track] [--author NAME]
264
+ docx headers clear FILE [--at sN] [--type T | --first-page | --even | --odd]
265
+ docx footers set FILE … # identical flags, kind=footer (e.g. --page-number --of-pages → "Page X of Y")
266
+ docx footers clear FILE …
267
+
193
268
  docx images extract FILE --to DIR [--at imgN] # --to = output directory; --at picks one image
194
269
  docx images replace FILE --at imgN --with ./new.png
195
270
  docx images delete FILE --at imgN
@@ -235,13 +310,13 @@ The CLI is built for non-interactive agents. **Exit code is the success signal**
235
310
  | Command class | Default stdout on success | `--verbose` |
236
311
  | ------------- | ------------------------- | ----------- |
237
312
  | **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 |
238
- | **Mutator with no new handle** — `edit`, `delete`, `replace`, `create`, `comments resolve/delete`, `images replace/delete`, `hyperlinks replace/delete`, `footnotes/endnotes edit/delete`, `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 |
313
+ | **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 |
239
314
  | `find` | matched span locators, one per line (no matches → nothing, exit `0`) | `--json` → `{ totalMatches, query, view, matches:[…], normalizedQuery? }` |
240
315
  | `wc` | the bare count (whole-doc adds a tab-separated `sections` column, like `wc`) | `--json` → `{ words, scope, view, sections? }` |
241
316
  | `outline` | indented `LOCATOR⇥TEXT` tree (two spaces per level) | `--json` → nested `[{ id, locator, level, style, text, children }]` |
242
- | `read` | GFM Markdown, each paragraph trailed by `<!-- pN -->` | `--ast` → the JSON AST body (`docx info schema`) |
317
+ | `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`) |
243
318
  | `render` | image paths, one per line | `--verbose` → `{ok, operation, path, engine, output, pages}` |
244
- | `* list` (all six `list` verbs) | a **bare JSON array**; each item's `id` is its `--at` handle | — |
319
+ | `* list` (all eight `list` verbs) | a **bare JSON array**; each item's `id` is its `--at` handle | — |
245
320
 
246
321
  `--dry-run` always prints a preview object (no `ok`) and writes nothing; it wins over `-o/--output`.
247
322
 
@@ -254,6 +329,7 @@ Locators come in two flavors. **Positional block ids** (`pN`, `tN`, `sN`) are de
254
329
  | `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 |
255
330
  | `cN` (comment) | `docx comments list FILE` | `comments reply/resolve/delete --at` |
256
331
  | `fnN` / `enN` (foot/endnote) | `docx footnotes list FILE` / `docx endnotes list FILE` | `footnotes/endnotes edit/delete --at` |
332
+ | `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` |
257
333
  | `imgN` (image) | `docx images list FILE` | `images extract/replace/delete --at` |
258
334
  | `linkN` (hyperlink) | `docx hyperlinks list FILE` | `hyperlinks replace/delete --at` |
259
335
  | `tcN` (tracked change) | `docx track-changes list FILE` | `track-changes accept/reject --at` |
@@ -285,7 +361,7 @@ cN imgN linkN fnN enN tcN eqN entity ids (comment / image / hyper
285
361
  | ---- | ----------- |
286
362
  | `pN`, `tN`, `sN`, `tN:rRcC:pK` (blocks) | `read --from/--to`, `insert --after/--before`, `wc`, `comments add` |
287
363
  | `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) |
288
- | `pN`, `pN-pM`, `tN`, `sN` | `delete --at` |
364
+ | `pN`, `pN-pM`, `tN`, `sN`, `tN:rRcC:pK` | `delete --at` |
289
365
  | `pN:S-E`, `pN:S-pM:E`, `tN:rRcC:pK:S-E` (spans) | `comments add --at`, `hyperlinks add --at` (single paragraph), `find`/`wc` results |
290
366
  | `pN[:offset]` (point) | `footnotes/endnotes add --at` |
291
367
  | `cN` / `fnN` / `enN` / `imgN` / `linkN` / `tcN` (entities) | the matching noun's `--at` (the `c`/`fn`/`en`/`img`/`link`/`tc` prefix is optional) |
@@ -342,6 +418,8 @@ docx track-changes accept doc.docx --at tc0 --at tc2 # or --all
342
418
 
343
419
  **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.
344
420
 
421
+ **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.
422
+
345
423
  **Run formatting beyond bold/italic.** Properties markdown has no native syntax for — text color, theme color, highlight, shading, underline (all 18 styles + color), super/subscript, small/all caps, font, and size — are emitted as the **HTML a markdown reader actually renders**, so the output looks right in GitHub, VS Code, Obsidian, and browsers (Pandoc `[text]{…}` spans render as literal brackets in all of those). `read` emits semantic tags where they exist — `<mark>overdue</mark>`, `<sup>x</sup>`, `<sub>2</sub>` — a `<span style="color:#C00000">…</span>` for the CSS-expressible properties, and `data-*` attributes for the OOXML-only ones CSS can't express (theme colors, underline styles); `insert/edit --markdown` parses them back losslessly, and a leading `<!-- docx:base font="Arial" size="8pt" -->` note declares the document's dominant font/size once so the body isn't buried in per-run repetition. Bold/italic/strike/code/links stay native (`**`/`*`/`~~`/`` ` ``/`[](…)`). Because the inline-surgery transform scans whole sibling sequences, a CriticMarkup marker or span can straddle other formatting — `{++**bold insertion**++}` is tracked correctly. An invalid enum value (e.g. a bogus highlight name) fails with a clear error rather than silently vanishing. Inserted plain content inherits the surrounding paragraph's font/size so it blends in.
346
424
 
347
425
  **Visual verification.** `docx render` is the only command that needs an external runtime: it drives Microsoft Word (macOS via `osascript`, Windows via PowerShell COM — the ground-truth renderer) or LibreOffice (`soffice`, cross-platform) to produce a PDF, then rasterizes in-process via the bundled `@hyzyla/pdfium` WASM package — no poppler/pdftoppm/ImageMagick needed. Agents that consume PNGs use this to verify edits, diff accept/reject before-vs-after, or generate screenshots.