bun-docx 0.19.2 → 0.20.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.
Files changed (3) hide show
  1. package/README.md +21 -0
  2. package/dist/index.js +93 -95
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,6 +8,27 @@
8
8
  - Agents address text by **stable locators** with character offsets (`p3:5-20`); humans see normal Word formatting on disk.
9
9
  - Custom styles, theme colors, embedded objects — all of it survives. The CLI mutates XML in place rather than re-emitting from a lossy model.
10
10
 
11
+ ## Why docx-cli?
12
+
13
+ The default way agents edit Word docs is to unzip the `.docx` and hand-write the OOXML inside. That takes a strong model to get right, burns tokens, and routinely produces a file Word won't open. `docx-cli` hands the agent plain commands plus an annotated-Markdown read view, so it never has to reason about the XML.
14
+
15
+ We measured it — a controlled A/B bake-off: **six real document tasks** (fill an NDA, fill an invoice, restyle a résumé, redline a contract, finalize a contract, author a journal), the same starting files, and one independent judge grading every result from the **Word-rendered pages**. Three runs per arm at each of two model tiers:
16
+
17
+ | | Haiku (weak, cheap) | | Sonnet (strong) | |
18
+ | :------------------------ | ------------------: | --------------------: | --------------: | --------------------: |
19
+ | | **docx-cli** | default skill | **docx-cli** | default skill |
20
+ | Tasks solved (of 6) | **4.3** (4–5) | 0.7 (0–1) | **6.0** (6–6) | 4.0 (4–4) |
21
+ | Rendered correctly (of 6) | 6 | 3.7 | 6.0 | 4.7 |
22
+ | Outright-broken documents | **0** | ~1/run (up to 2) | **0** | 0 |
23
+ | Input tokens | **2.4M** | 6.1M (2.6×) | **1.6M** | 3.6M (2.2×) |
24
+ | Wall-clock | **924 s** | 1,882 s (2.0× slower) | **1,175 s** | 2,029 s (1.7× slower) |
25
+
26
+ - **The correctness gap is widest on the cheap Haiku tier (~6×)**, and a frontier model never closes it — the default skill caps at 4/6, losing the contract redline and the résumé every Sonnet run.
27
+ - **The cost and speed penalties are model-independent** — ~2.2–2.6× more tokens and ~1.7–2× slower at *both* tiers, with token/time ranges that never overlap.
28
+ - **Word couldn't reliably open the default skill's work** — it failed to open 5 of 36 of its outputs; all 36 of docx-cli's opened on the first try.
29
+
30
+ Full methodology, per-task rubric, and side-by-side renders: **[the bake-off writeup](https://kklimuk.github.io/docx-cli/)**.
31
+
11
32
  ## Install
12
33
 
13
34
  **npm** — the simplest path (requires Bun >= 1.3):
package/dist/index.js CHANGED
@@ -4435,6 +4435,20 @@ class XmlNode2 {
4435
4435
  }
4436
4436
  return out;
4437
4437
  }
4438
+ collectTextWithBreaks() {
4439
+ if (this.isText)
4440
+ return this.text ?? "";
4441
+ if (this.tag === "w:br" || this.tag === "w:cr")
4442
+ return `
4443
+ `;
4444
+ if (this.tag === "w:tab")
4445
+ return "\t";
4446
+ let out = "";
4447
+ for (const child of this.children) {
4448
+ out += child.collectTextWithBreaks();
4449
+ }
4450
+ return out;
4451
+ }
4438
4452
  clone() {
4439
4453
  const cloned = new XmlNode2(this.tag, { ...this.attributes });
4440
4454
  if (this.text !== undefined)
@@ -5290,6 +5304,11 @@ function RunElement({ run }) {
5290
5304
  }
5291
5305
  return null;
5292
5306
  }
5307
+ function textToRunElements(text) {
5308
+ return textToRuns(text, {}).map((run) => /* @__PURE__ */ jsxDEV(RunElement, {
5309
+ run
5310
+ }, undefined, false, undefined, this)).filter((node) => node !== null);
5311
+ }
5293
5312
  function applyParagraphOptionsInPlace(rebuilt, options) {
5294
5313
  if (!options.style && !options.alignment && !options.tabs && !options.spacing && !options.indent)
5295
5314
  return;
@@ -17744,14 +17763,7 @@ function noteFirstParagraphRuns({
17744
17763
  return runs;
17745
17764
  if (text2 === undefined)
17746
17765
  return [];
17747
- return [
17748
- /* @__PURE__ */ jsxDEV(w.r, {
17749
- children: /* @__PURE__ */ jsxDEV(w.t, {
17750
- "xml:space": "preserve",
17751
- children: ` ${text2}`
17752
- }, undefined, false, undefined, this)
17753
- }, undefined, false, undefined, this)
17754
- ];
17766
+ return textToRunElements(` ${text2}`);
17755
17767
  }
17756
17768
  function TrackedNoteBody({
17757
17769
  config,
@@ -17761,6 +17773,19 @@ function TrackedNoteBody({
17761
17773
  }) {
17762
17774
  const Note = config.kind === "footnote" ? w.footnote : w.endnote;
17763
17775
  const BodyRef = config.kind === "footnote" ? w.footnoteRef : w.endnoteRef;
17776
+ const insRuns = [
17777
+ /* @__PURE__ */ jsxDEV(w.r, {
17778
+ children: [
17779
+ /* @__PURE__ */ jsxDEV(w.rPr, {
17780
+ children: /* @__PURE__ */ jsxDEV(w.rStyle, {
17781
+ "w-val": config.referenceStyle
17782
+ }, undefined, false, undefined, this)
17783
+ }, undefined, false, undefined, this),
17784
+ /* @__PURE__ */ jsxDEV(BodyRef, {}, undefined, false, undefined, this)
17785
+ ]
17786
+ }, undefined, true, undefined, this),
17787
+ ...textToRunElements(` ${text2}`)
17788
+ ];
17764
17789
  return /* @__PURE__ */ jsxDEV(Note, {
17765
17790
  "w-id": id,
17766
17791
  children: /* @__PURE__ */ jsxDEV(w.p, {
@@ -17772,25 +17797,8 @@ function TrackedNoteBody({
17772
17797
  }, undefined, false, undefined, this),
17773
17798
  /* @__PURE__ */ jsxDEV(Ins, {
17774
17799
  meta,
17775
- children: [
17776
- /* @__PURE__ */ jsxDEV(w.r, {
17777
- children: [
17778
- /* @__PURE__ */ jsxDEV(w.rPr, {
17779
- children: /* @__PURE__ */ jsxDEV(w.rStyle, {
17780
- "w-val": config.referenceStyle
17781
- }, undefined, false, undefined, this)
17782
- }, undefined, false, undefined, this),
17783
- /* @__PURE__ */ jsxDEV(BodyRef, {}, undefined, false, undefined, this)
17784
- ]
17785
- }, undefined, true, undefined, this),
17786
- /* @__PURE__ */ jsxDEV(w.r, {
17787
- children: /* @__PURE__ */ jsxDEV(w.t, {
17788
- "xml:space": "preserve",
17789
- children: ` ${text2}`
17790
- }, undefined, false, undefined, this)
17791
- }, undefined, false, undefined, this)
17792
- ]
17793
- }, undefined, true, undefined, this)
17800
+ children: insRuns
17801
+ }, undefined, false, undefined, this)
17794
17802
  ]
17795
17803
  }, undefined, true, undefined, this)
17796
17804
  }, undefined, false, undefined, this);
@@ -17831,15 +17839,9 @@ function wrapNoteBodyAsEdited(noteBody, newText, insMeta, delMeta) {
17831
17839
  continue;
17832
17840
  }
17833
17841
  if (!inserted) {
17834
- const newRun = /* @__PURE__ */ jsxDEV(w.r, {
17835
- children: /* @__PURE__ */ jsxDEV(w.t, {
17836
- "xml:space": "preserve",
17837
- children: newText
17838
- }, undefined, false, undefined, this)
17839
- }, undefined, false, undefined, this);
17840
17842
  result.push(/* @__PURE__ */ jsxDEV(Ins, {
17841
17843
  meta: insMeta,
17842
- children: newRun
17844
+ children: textToRunElements(newText)
17843
17845
  }, undefined, false, undefined, this));
17844
17846
  inserted = true;
17845
17847
  }
@@ -17849,15 +17851,9 @@ function wrapNoteBodyAsEdited(noteBody, newText, insMeta, delMeta) {
17849
17851
  }, undefined, false, undefined, this));
17850
17852
  }
17851
17853
  if (!inserted) {
17852
- const newRun = /* @__PURE__ */ jsxDEV(w.r, {
17853
- children: /* @__PURE__ */ jsxDEV(w.t, {
17854
- "xml:space": "preserve",
17855
- children: newText
17856
- }, undefined, false, undefined, this)
17857
- }, undefined, false, undefined, this);
17858
17854
  result.push(/* @__PURE__ */ jsxDEV(Ins, {
17859
17855
  meta: insMeta,
17860
- children: newRun
17856
+ children: textToRunElements(newText)
17861
17857
  }, undefined, false, undefined, this));
17862
17858
  }
17863
17859
  paragraph.children = result;
@@ -17885,6 +17881,7 @@ function isBareWhitespaceRun(run) {
17885
17881
  return saw;
17886
17882
  }
17887
17883
  var init_emit3 = __esm(() => {
17884
+ init_blocks();
17888
17885
  init_jsx();
17889
17886
  init_track_changes();
17890
17887
  init_emit2();
@@ -21138,6 +21135,9 @@ function nodeText(node) {
21138
21135
  out += child2.collectText();
21139
21136
  else if (child2.tag === "w:tab")
21140
21137
  out += "\t";
21138
+ else if (child2.tag === "w:br" || child2.tag === "w:cr")
21139
+ out += `
21140
+ `;
21141
21141
  }
21142
21142
  return out;
21143
21143
  }
@@ -23700,7 +23700,7 @@ class CommentsView {
23700
23700
  const author = child2.getAttribute("w:author") ?? "";
23701
23701
  const date = child2.getAttribute("w:date") ?? "";
23702
23702
  const initials = child2.getAttribute("w:initials");
23703
- const text2 = child2.collectText();
23703
+ const text2 = child2.collectTextWithBreaks();
23704
23704
  const anchor = anchors.get(commentId) ?? {
23705
23705
  startBlockId: "",
23706
23706
  startOffset: 0,
@@ -24218,7 +24218,7 @@ class NotesView {
24218
24218
  const numericId = child2.getAttribute("w:id");
24219
24219
  if (numericId == null)
24220
24220
  continue;
24221
- const text2 = child2.collectText().replace(/\s+/g, " ").trim();
24221
+ const text2 = child2.collectTextWithBreaks().trim();
24222
24222
  out.push({ id: `${config.idPrefix}${numericId}`, text: text2 });
24223
24223
  }
24224
24224
  return out;
@@ -35447,12 +35447,7 @@ function CommentBody({
35447
35447
  children: /* @__PURE__ */ jsxDEV(w.p, {
35448
35448
  "w14:paraId": options.paraId,
35449
35449
  "w14:textId": "00000000",
35450
- children: /* @__PURE__ */ jsxDEV(w.r, {
35451
- children: /* @__PURE__ */ jsxDEV(w.t, {
35452
- "xml:space": "preserve",
35453
- children: options.text
35454
- }, undefined, false, undefined, this)
35455
- }, undefined, false, undefined, this)
35450
+ children: textToRunElements(options.text)
35456
35451
  }, undefined, false, undefined, this)
35457
35452
  }, undefined, false, undefined, this);
35458
35453
  }
@@ -35519,6 +35514,7 @@ function containsCommentReference(run, numericId) {
35519
35514
  var SpanOutOfRangeError;
35520
35515
  var init_markers = __esm(() => {
35521
35516
  init_comments();
35517
+ init_blocks();
35522
35518
  init_jsx();
35523
35519
  init_parser();
35524
35520
  init_jsx_dev_runtime();
@@ -74332,7 +74328,7 @@ function buildContentParagraph(spec, contentWidth) {
74332
74328
  }, undefined, false, undefined, this)
74333
74329
  }, undefined, false, undefined, this)
74334
74330
  }, undefined, false, undefined, this),
74335
- textRun(spec.text),
74331
+ textToRunElements(spec.text),
74336
74332
  /* @__PURE__ */ jsxDEV(w.r, {
74337
74333
  children: /* @__PURE__ */ jsxDEV(w.tab, {}, undefined, false, undefined, this)
74338
74334
  }, undefined, false, undefined, this),
@@ -74357,7 +74353,7 @@ function buildContentParagraph(spec, contentWidth) {
74357
74353
  alignment ? /* @__PURE__ */ jsxDEV(w.pPr, {
74358
74354
  children: alignment
74359
74355
  }, undefined, false, undefined, this) : null,
74360
- hasText ? textRun(spec.text) : null
74356
+ hasText ? textToRunElements(spec.text) : null
74361
74357
  ]
74362
74358
  }, undefined, true, undefined, this);
74363
74359
  }
@@ -74368,22 +74364,14 @@ function alignChild(align) {
74368
74364
  "w-val": align
74369
74365
  }, undefined, false, undefined, this);
74370
74366
  }
74371
- function textRun(text6) {
74372
- return /* @__PURE__ */ jsxDEV(w.r, {
74373
- children: /* @__PURE__ */ jsxDEV(w.t, {
74374
- "xml:space": "preserve",
74375
- children: text6
74376
- }, undefined, false, undefined, this)
74377
- }, undefined, false, undefined, this);
74378
- }
74379
74367
  function fieldRuns(field) {
74380
74368
  switch (field.type) {
74381
74369
  case "page":
74382
74370
  if (field.ofPages) {
74383
74371
  return [
74384
- textRun("Page "),
74372
+ ...textToRunElements("Page "),
74385
74373
  fieldSimple(" PAGE ", "1"),
74386
- textRun(" of "),
74374
+ ...textToRunElements(" of "),
74387
74375
  fieldSimple(" NUMPAGES ", "1")
74388
74376
  ];
74389
74377
  }
@@ -74445,6 +74433,7 @@ function relationshipTarget(partName) {
74445
74433
  }
74446
74434
  var NS_W3 = "http://schemas.openxmlformats.org/wordprocessingml/2006/main", NS_R2 = "http://schemas.openxmlformats.org/officeDocument/2006/relationships";
74447
74435
  var init_marginals2 = __esm(() => {
74436
+ init_blocks();
74448
74437
  init_jsx();
74449
74438
  init_sections();
74450
74439
  init_track_changes();
@@ -82687,7 +82676,7 @@ async function resolveBlockOrFail(document4, locator) {
82687
82676
  return document4.body.resolveBlock(locator);
82688
82677
  } catch (err) {
82689
82678
  if (err instanceof LocatorResolveError) {
82690
- return await fail("BLOCK_NOT_FOUND", err.message);
82679
+ return await fail("BLOCK_NOT_FOUND", err.message, STALE_LOCATOR_HINT);
82691
82680
  }
82692
82681
  throw err;
82693
82682
  }
@@ -82737,7 +82726,7 @@ async function resolveBlockRangeOrFail(document4, locator) {
82737
82726
  return await fail("INVALID_LOCATOR", err.message);
82738
82727
  }
82739
82728
  if (err instanceof LocatorResolveError) {
82740
- return await fail("BLOCK_NOT_FOUND", err.message);
82729
+ return await fail("BLOCK_NOT_FOUND", err.message, STALE_LOCATOR_HINT);
82741
82730
  }
82742
82731
  throw err;
82743
82732
  }
@@ -82746,7 +82735,7 @@ var EXIT2, stdout = async (text6) => {
82746
82735
  await Bun.stdout.write(text6);
82747
82736
  }, stderr = async (text6) => {
82748
82737
  await Bun.stderr.write(text6);
82749
- }, sinks, verboseAck = false, SAVE_FLAGS;
82738
+ }, sinks, verboseAck = false, STALE_LOCATOR_HINT = "Locator ids are positional and shift after structural edits (insert/delete/section changes) \u2014 an id from an earlier read may now point elsewhere or be gone. Re-read the file to get current ids, then retry. Applying several changes? Do them from ONE read with --batch (edit/insert/delete/replace/comments) so ids never go stale mid-run.", SAVE_FLAGS;
82750
82739
  var init_respond = __esm(() => {
82751
82740
  init_core2();
82752
82741
  EXIT2 = {
@@ -82797,6 +82786,12 @@ async function rejectShellMangledValue(text6, help, label = "this value") {
82797
82786
  return await fail("USAGE", `${label} contains "${fragment}" \u2014 a number with no integer part, the signature of a "$" amount gutted by the shell (bash turns double-quoted "$300.00" into ".00" and "$10,000" into ",000"). docx would write the corrupted value verbatim.`, `Wrap any "$"-bearing value in SINGLE quotes ('$300.00') so bash leaves it alone, or supply it via --batch FILE (JSONL never touches the shell). If you really mean "${fragment}", write its integer part (0${fragment}) or use --batch. Help:
82798
82787
  ${help}`);
82799
82788
  }
82789
+ function decodeInlineEscapes(value) {
82790
+ if (value === undefined)
82791
+ return;
82792
+ return value.replace(/\\r\\n|\\[nrt]/g, (match) => match === "\\t" ? "\t" : `
82793
+ `);
82794
+ }
82800
82795
  function parseTaskFlag(value) {
82801
82796
  const normalized = value.toLowerCase();
82802
82797
  if (normalized === "checked" || normalized === "true" || normalized === "1")
@@ -83206,7 +83201,7 @@ async function run(args) {
83206
83201
  const atInput = parsed.values.at;
83207
83202
  const anchorInput = parsed.values.anchor;
83208
83203
  const batchInput = parsed.values.batch;
83209
- const text6 = parsed.values.text;
83204
+ const text6 = decodeInlineEscapes(parsed.values.text);
83210
83205
  const occurrenceRaw = parsed.values.occurrence;
83211
83206
  const defaultAuthor = resolveAuthor(parsed.values.author);
83212
83207
  const outputPath = parsed.values.output;
@@ -83690,7 +83685,7 @@ async function run4(args) {
83690
83685
  if (!path2)
83691
83686
  return fail("USAGE", "Missing FILE argument", HELP4);
83692
83687
  const parentInput = parsed.values.at;
83693
- const text6 = parsed.values.text;
83688
+ const text6 = decodeInlineEscapes(parsed.values.text);
83694
83689
  if (!parentInput)
83695
83690
  return fail("USAGE", "Missing --at cN", HELP4);
83696
83691
  if (!text6)
@@ -83764,6 +83759,7 @@ Examples:
83764
83759
  `;
83765
83760
  var init_reply = __esm(() => {
83766
83761
  init_core2();
83762
+ init_parse_helpers();
83767
83763
  init_respond();
83768
83764
  });
83769
83765
 
@@ -84077,12 +84073,7 @@ function DocumentBody({ text: text6 }) {
84077
84073
  children: /* @__PURE__ */ jsxDEV(w.body, {
84078
84074
  children: [
84079
84075
  text6 !== undefined ? /* @__PURE__ */ jsxDEV(w.p, {
84080
- children: /* @__PURE__ */ jsxDEV(w.r, {
84081
- children: /* @__PURE__ */ jsxDEV(w.t, {
84082
- "xml:space": "preserve",
84083
- children: text6
84084
- }, undefined, false, undefined, this)
84085
- }, undefined, false, undefined, this)
84076
+ children: textToRunElements(text6)
84086
84077
  }, undefined, false, undefined, this) : /* @__PURE__ */ jsxDEV(w.p, {}, undefined, false, undefined, this),
84087
84078
  /* @__PURE__ */ jsxDEV(DefaultSectionProperties, {}, undefined, false, undefined, this)
84088
84079
  ]
@@ -84180,6 +84171,7 @@ function serializeWithDeclaration(root2) {
84180
84171
  }
84181
84172
  var DOC_NAMESPACES, CONTENT_TYPES, ROOT_RELS, DOCUMENT_RELS;
84182
84173
  var init_template = __esm(() => {
84174
+ init_blocks();
84183
84175
  init_jsx();
84184
84176
  init_parser();
84185
84177
  init_canonical_parts();
@@ -84260,7 +84252,7 @@ async function run7(args) {
84260
84252
  if (!path2) {
84261
84253
  return fail("USAGE", "Missing FILE argument", HELP7);
84262
84254
  }
84263
- const text6 = parsed.values.text;
84255
+ const text6 = decodeInlineEscapes(parsed.values.text);
84264
84256
  const fromPath = parsed.values.from;
84265
84257
  const textFilePath = parsed.values["text-file"];
84266
84258
  const contentSources = [text6, fromPath, textFilePath].filter((value) => value !== undefined);
@@ -85841,7 +85833,7 @@ async function validateParagraphEdit(values2, paragraphOptions) {
85841
85833
  return { kind: "clear", tags };
85842
85834
  }
85843
85835
  }
85844
- const text6 = values2.text;
85836
+ const text6 = decodeInlineEscapes(values2.text);
85845
85837
  const runsJson = values2.runs;
85846
85838
  const codeInline = values2.code;
85847
85839
  const codeFile = values2["code-file"];
@@ -85895,7 +85887,7 @@ async function validateParagraphEdit(values2, paragraphOptions) {
85895
85887
  }
85896
85888
  return { kind: "task", checked };
85897
85889
  }
85898
- const markdownInline = values2.markdown;
85890
+ const markdownInline = decodeInlineEscapes(values2.markdown);
85899
85891
  const markdownFile = values2["markdown-file"];
85900
85892
  const contentFlags = [
85901
85893
  text6 !== undefined,
@@ -86249,9 +86241,12 @@ General options:
86249
86241
  -h, --help Show this help
86250
86242
 
86251
86243
  Output:
86252
- Prints a one-line confirmation on success (exit 0) \u2014 the edited locator is unchanged, so there's
86253
- nothing to mint. --verbose prints {ok:true, operation, path, locator}.
86254
- Errors print {code, error, hint?} with a nonzero exit. Discover ids with
86244
+ Prints a one-line confirmation on success (exit 0) \u2014 an in-place edit shifts nothing, so the
86245
+ edited locator is unchanged and there's nothing to mint. --verbose prints {ok:true, operation,
86246
+ path, locator}. Errors print {code, error, hint?} with a nonzero exit.
86247
+ Heads up: a locator you hold from BEFORE a structural edit (an insert/delete elsewhere renumbers
86248
+ ids) is stale \u2014 it lands on the wrong block or errors BLOCK_NOT_FOUND. Re-read after any
86249
+ insert/delete, or apply the whole set from one read with --batch (above). Discover ids with
86255
86250
  \`docx read FILE --ast\` (equation ids appear on EquationRun nodes).
86256
86251
 
86257
86252
  Examples:
@@ -86442,9 +86437,9 @@ async function runAddNote(args, kind) {
86442
86437
  if (anchorCount > 1) {
86443
86438
  return fail("USAGE", "--at and --anchor are mutually exclusive", help);
86444
86439
  }
86445
- const text6 = parsed.values.text;
86440
+ const text6 = decodeInlineEscapes(parsed.values.text);
86446
86441
  const runsJson = parsed.values.runs;
86447
- const markdown2 = parsed.values.markdown;
86442
+ const markdown2 = decodeInlineEscapes(parsed.values.markdown);
86448
86443
  const markdownFile = parsed.values["markdown-file"];
86449
86444
  const bodyCount = (text6 !== undefined ? 1 : 0) + (runsJson !== undefined ? 1 : 0) + (markdown2 !== undefined ? 1 : 0) + (markdownFile !== undefined ? 1 : 0);
86450
86445
  if (bodyCount === 0) {
@@ -86868,9 +86863,9 @@ async function runEditNote(args, kind) {
86868
86863
  const config = noteConfig(kind);
86869
86864
  if (!idInput)
86870
86865
  return fail("USAGE", `Missing --at ${config.idPrefix}N`, help);
86871
- const text6 = parsed.values.text;
86866
+ const text6 = decodeInlineEscapes(parsed.values.text);
86872
86867
  const runsJson = parsed.values.runs;
86873
- const markdown2 = parsed.values.markdown;
86868
+ const markdown2 = decodeInlineEscapes(parsed.values.markdown);
86874
86869
  const markdownFile = parsed.values["markdown-file"];
86875
86870
  const bodyCount = (text6 !== undefined ? 1 : 0) + (runsJson !== undefined ? 1 : 0) + (markdown2 !== undefined ? 1 : 0) + (markdownFile !== undefined ? 1 : 0);
86876
86871
  if (bodyCount === 0) {
@@ -87645,7 +87640,7 @@ async function runSetMarginal(args, kind) {
87645
87640
  if (align !== undefined && !isAlign(align)) {
87646
87641
  return fail("USAGE", `Invalid --align: ${align}`, "Valid: left, center, right.");
87647
87642
  }
87648
- const text6 = parsed.values.text;
87643
+ const text6 = decodeInlineEscapes(parsed.values.text);
87649
87644
  if (text6 === undefined && field === undefined) {
87650
87645
  return fail("USAGE", `Nothing to set \u2014 pass --text and/or a field (--page-number/--date/--style-ref/--field).`, helpFor7(kind));
87651
87646
  }
@@ -87740,6 +87735,7 @@ var OPTION_SPEC4, SAVE_FLAGS_HELP = `Output:
87740
87735
  -h, --help Show this help`;
87741
87736
  var init_set = __esm(() => {
87742
87737
  init_core2();
87738
+ init_parse_helpers();
87743
87739
  init_respond();
87744
87740
  init_shared();
87745
87741
  OPTION_SPEC4 = {
@@ -88879,10 +88875,10 @@ async function buildSingleShotOptions(filePath, values2) {
88879
88875
  if (typeof spec === "number")
88880
88876
  return spec;
88881
88877
  if (spec.kind === "text") {
88882
- const rejected = await rejectMarkdownInText(values2.text, HELP20);
88878
+ const rejected = await rejectMarkdownInText(spec.text, HELP20);
88883
88879
  if (typeof rejected === "number")
88884
88880
  return rejected;
88885
- const mangled = await rejectShellMangledValue(values2.text, HELP20, "--text");
88881
+ const mangled = await rejectShellMangledValue(spec.text, HELP20, "--text");
88886
88882
  if (typeof mangled === "number")
88887
88883
  return mangled;
88888
88884
  }
@@ -89022,7 +89018,7 @@ async function chooseContentSpec(values2) {
89022
89018
  }
89023
89019
  async function resolveMarkdownSpec(values2, flag) {
89024
89020
  if (flag === "markdown") {
89025
- const source2 = values2.markdown;
89021
+ const source2 = decodeInlineEscapes(values2.markdown);
89026
89022
  return { kind: "markdown", source: source2 };
89027
89023
  }
89028
89024
  const path2 = values2["markdown-file"];
@@ -89067,7 +89063,7 @@ async function parseImageFlags(values2) {
89067
89063
  const alt = values2.alt;
89068
89064
  if (alt !== undefined)
89069
89065
  out.alt = alt;
89070
- const caption = values2.caption;
89066
+ const caption = decodeInlineEscapes(values2.caption);
89071
89067
  if (caption !== undefined)
89072
89068
  out.caption = caption;
89073
89069
  const widthRaw = values2.width;
@@ -89092,7 +89088,7 @@ function buildTextSpec(values2) {
89092
89088
  const url = values2.url;
89093
89089
  return {
89094
89090
  kind: "text",
89095
- text: values2.text,
89091
+ text: decodeInlineEscapes(values2.text),
89096
89092
  format: {
89097
89093
  color: values2.color,
89098
89094
  bold: values2.bold,
@@ -91608,7 +91604,7 @@ function formatNote(type, pairs, bareTokens = []) {
91608
91604
  return `<!-- ${parts.join(" ")} -->`;
91609
91605
  }
91610
91606
  function htmlAttr(key, value) {
91611
- const escaped = value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
91607
+ const escaped = value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/\n/g, "&#10;").replace(/\r/g, "&#13;").replace(/\t/g, "&#9;");
91612
91608
  return `${key}="${escaped}"`;
91613
91609
  }
91614
91610
  function twipsToInches(twips) {
@@ -98021,7 +98017,7 @@ Examples:
98021
98017
  // package.json
98022
98018
  var package_default = {
98023
98019
  name: "bun-docx",
98024
- version: "0.19.2",
98020
+ version: "0.20.1",
98025
98021
  description: "Read, edit, redline, and comment on Microsoft Word .docx files from the command line \u2014 built for AI agents.",
98026
98022
  keywords: [
98027
98023
  "docx",
@@ -98144,11 +98140,13 @@ Commands (each one-liner names capabilities you'd otherwise miss; see <command>
98144
98140
  It is highly recommended to run "docx info locators" and "docx info schema" (neither needs a FILE) to understand the addressing model and AST.
98145
98141
  Run "docx <command> --help" for command-specific help.
98146
98142
 
98147
- BATCH MANY CHANGES IN ONE READ: filling a form or applying many edits? Don't go
98148
- one-at-a-time \u2014 edit / insert / replace / delete and comments (add/resolve/delete)
98149
- all take --batch FILE.jsonl (one JSON change per line; "-" reads stdin). Every locator
98150
- addresses the document AS READ, so ids stay valid across the whole batch \u2014 one
98151
- read, one write, no re-reading between changes. See "<command> --help".
98143
+ BATCH MANY CHANGES IN ONE READ: locator ids are positional and shift after structural
98144
+ edits (insert/delete/section changes), so going one-at-a-time forces a re-read to refresh
98145
+ ids after each. Miss the change and the next command lands on the wrong block or errors
98146
+ BLOCK_NOT_FOUND. Skip all that: edit / insert / replace / delete and comments
98147
+ (add/resolve/delete) all take --batch FILE.jsonl (one JSON change per line; "-" reads
98148
+ stdin). Every locator addresses the document AS READ, so ids stay valid across the whole
98149
+ batch \u2014 one read, one write, no re-reading between changes. See "<command> --help".
98152
98150
 
98153
98151
  FILL FORMATTED / TABBED LINES WITHOUT REBUILDING RUNS: to fill a template line that
98154
98152
  carries formatting (bold/font) or tab stops \u2014 e.g. "**Org Name**\u21E5Date" \u2014 replace just
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bun-docx",
3
- "version": "0.19.2",
3
+ "version": "0.20.1",
4
4
  "description": "Read, edit, redline, and comment on Microsoft Word .docx files from the command line — built for AI agents.",
5
5
  "keywords": [
6
6
  "docx",