bun-docx 0.3.0 → 0.4.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 (2) hide show
  1. package/dist/index.js +462 -36
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -17712,9 +17712,72 @@ var init_insert = __esm(() => {
17712
17712
  init_jsx_dev_runtime();
17713
17713
  });
17714
17714
 
17715
- // src/cli/read/index.ts
17716
- var exports_read = {};
17717
- __export(exports_read, {
17715
+ // src/cli/outline/build.ts
17716
+ function buildOutline(doc, options = {}) {
17717
+ const stylePrefix = options.stylePrefix ?? "Heading";
17718
+ const root = {
17719
+ id: "",
17720
+ locator: "",
17721
+ level: 0,
17722
+ style: "",
17723
+ text: "",
17724
+ children: []
17725
+ };
17726
+ const stack = [root];
17727
+ for (const paragraph of headingParagraphs(doc.blocks, stylePrefix)) {
17728
+ const level = headingLevel(paragraph.style, stylePrefix);
17729
+ if (level === null)
17730
+ continue;
17731
+ while ((stack[stack.length - 1]?.level ?? 0) >= level)
17732
+ stack.pop();
17733
+ const parent = stack[stack.length - 1] ?? root;
17734
+ const entry = {
17735
+ id: paragraph.id,
17736
+ locator: paragraph.id,
17737
+ level,
17738
+ style: paragraph.style ?? "",
17739
+ text: paragraphText(paragraph),
17740
+ children: []
17741
+ };
17742
+ parent.children.push(entry);
17743
+ stack.push(entry);
17744
+ }
17745
+ return root.children;
17746
+ }
17747
+ function headingLevel(style, stylePrefix) {
17748
+ if (!style)
17749
+ return null;
17750
+ if (!style.startsWith(stylePrefix))
17751
+ return null;
17752
+ const remainder = style.slice(stylePrefix.length).trim();
17753
+ if (remainder === "")
17754
+ return 1;
17755
+ const parsed = Number(remainder);
17756
+ if (!Number.isInteger(parsed) || parsed < 1)
17757
+ return null;
17758
+ return parsed;
17759
+ }
17760
+ function* headingParagraphs(blocks, stylePrefix) {
17761
+ for (const block of blocks) {
17762
+ if (block.type !== "paragraph")
17763
+ continue;
17764
+ if (headingLevel(block.style, stylePrefix) === null)
17765
+ continue;
17766
+ yield block;
17767
+ }
17768
+ }
17769
+ function paragraphText(paragraph) {
17770
+ let out = "";
17771
+ for (const run19 of paragraph.runs) {
17772
+ if (run19.type === "text")
17773
+ out += run19.text;
17774
+ }
17775
+ return out;
17776
+ }
17777
+
17778
+ // src/cli/outline/index.ts
17779
+ var exports_outline = {};
17780
+ __export(exports_outline, {
17718
17781
  run: () => run19
17719
17782
  });
17720
17783
  import { parseArgs as parseArgs16 } from "util";
@@ -17725,12 +17788,13 @@ async function run19(args) {
17725
17788
  args,
17726
17789
  allowPositionals: true,
17727
17790
  options: {
17728
- text: { type: "boolean" },
17791
+ "style-prefix": { type: "string" },
17729
17792
  help: { type: "boolean", short: "h" }
17730
17793
  }
17731
17794
  });
17732
- } catch (e) {
17733
- return fail("USAGE", e instanceof Error ? e.message : String(e), HELP19);
17795
+ } catch (parseError) {
17796
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
17797
+ return fail("USAGE", message, HELP19);
17734
17798
  }
17735
17799
  if (parsed.values.help) {
17736
17800
  await writeStdout(HELP19);
@@ -17739,6 +17803,77 @@ async function run19(args) {
17739
17803
  const path = parsed.positionals[0];
17740
17804
  if (!path)
17741
17805
  return fail("USAGE", "Missing FILE argument", HELP19);
17806
+ const stylePrefix = parsed.values["style-prefix"] ?? "Heading";
17807
+ if (stylePrefix.length === 0) {
17808
+ return fail("USAGE", "--style-prefix cannot be empty");
17809
+ }
17810
+ const view = await openOrFail(path);
17811
+ if (typeof view === "number")
17812
+ return view;
17813
+ const outline = buildOutline(view.doc, { stylePrefix });
17814
+ await respond({
17815
+ ok: true,
17816
+ operation: "outline",
17817
+ path,
17818
+ stylePrefix,
17819
+ outline
17820
+ });
17821
+ return EXIT.OK;
17822
+ }
17823
+ var HELP19 = `docx outline \u2014 list headings as a hierarchical tree
17824
+
17825
+ Usage:
17826
+ docx outline FILE [options]
17827
+
17828
+ Options:
17829
+ --style-prefix S paragraph-style prefix that marks a heading (default: "Heading")
17830
+ -h, --help show this help
17831
+
17832
+ Walks top-level paragraphs whose style starts with the prefix and parses the
17833
+ trailing number as the heading level (e.g. "Heading1" \u2192 1, "Heading 2" \u2192 2).
17834
+ Paragraphs nested inside table cells are skipped \u2014 outlines reflect the
17835
+ document's structural skeleton, not embedded labels. Lower levels nest under
17836
+ higher ones; missing intermediate levels nest directly (H1 \u2192 H3 is fine).
17837
+
17838
+ Output is JSON: an array of entries, each shaped like
17839
+ { id, locator, level, style, text, children }.
17840
+
17841
+ Examples:
17842
+ docx outline doc.docx
17843
+ docx outline doc.docx --style-prefix "Section"
17844
+ docx outline doc.docx | jq '.outline[].text'
17845
+ `;
17846
+ var init_outline = __esm(() => {
17847
+ init_respond();
17848
+ });
17849
+
17850
+ // src/cli/read/index.ts
17851
+ var exports_read = {};
17852
+ __export(exports_read, {
17853
+ run: () => run20
17854
+ });
17855
+ import { parseArgs as parseArgs17 } from "util";
17856
+ async function run20(args) {
17857
+ let parsed;
17858
+ try {
17859
+ parsed = parseArgs17({
17860
+ args,
17861
+ allowPositionals: true,
17862
+ options: {
17863
+ text: { type: "boolean" },
17864
+ help: { type: "boolean", short: "h" }
17865
+ }
17866
+ });
17867
+ } catch (e) {
17868
+ return fail("USAGE", e instanceof Error ? e.message : String(e), HELP20);
17869
+ }
17870
+ if (parsed.values.help) {
17871
+ await writeStdout(HELP20);
17872
+ return EXIT.OK;
17873
+ }
17874
+ const path = parsed.positionals[0];
17875
+ if (!path)
17876
+ return fail("USAGE", "Missing FILE argument", HELP20);
17742
17877
  const view = await openOrFail(path);
17743
17878
  if (typeof view === "number")
17744
17879
  return view;
@@ -17746,7 +17881,7 @@ async function run19(args) {
17746
17881
  await respond(view.doc);
17747
17882
  return EXIT.OK;
17748
17883
  }
17749
- var HELP19 = `docx read \u2014 print AST as JSON
17884
+ var HELP20 = `docx read \u2014 print AST as JSON
17750
17885
 
17751
17886
  Usage:
17752
17887
  docx read FILE [options]
@@ -17829,14 +17964,14 @@ function rebuildContainer(container, baseOffset, span, replacement, runPropertie
17829
17964
  if (placed)
17830
17965
  return;
17831
17966
  placed = true;
17832
- const run20 = /* @__PURE__ */ jsxDEV(ReplacementRun, {
17967
+ const run21 = /* @__PURE__ */ jsxDEV(ReplacementRun, {
17833
17968
  runProperties,
17834
17969
  text: replacement
17835
17970
  }, undefined, false, undefined, this);
17836
17971
  newChildren.push(tracked && isParagraph ? /* @__PURE__ */ jsxDEV(Ins, {
17837
17972
  meta: mintMeta(tracked),
17838
- children: run20
17839
- }, undefined, false, undefined, this) : run20);
17973
+ children: run21
17974
+ }, undefined, false, undefined, this) : run21);
17840
17975
  };
17841
17976
  for (const child of container.children) {
17842
17977
  if (child.tag === "w:r") {
@@ -17896,14 +18031,14 @@ function rebuildAcrossBoundaries(paragraph, span, replacement, runProperties, tr
17896
18031
  if (placed)
17897
18032
  return;
17898
18033
  placed = true;
17899
- const run20 = /* @__PURE__ */ jsxDEV(ReplacementRun, {
18034
+ const run21 = /* @__PURE__ */ jsxDEV(ReplacementRun, {
17900
18035
  runProperties,
17901
18036
  text: replacement
17902
18037
  }, undefined, false, undefined, this);
17903
18038
  newChildren.push(tracked ? /* @__PURE__ */ jsxDEV(Ins, {
17904
18039
  meta: mintMeta(tracked),
17905
- children: run20
17906
- }, undefined, false, undefined, this) : run20);
18040
+ children: run21
18041
+ }, undefined, false, undefined, this) : run21);
17907
18042
  };
17908
18043
  for (const child of paragraph.children) {
17909
18044
  if (child.tag === "w:r") {
@@ -18027,8 +18162,8 @@ function sumInnerRunLengths(wrapper) {
18027
18162
  function mintMeta(tracked) {
18028
18163
  return { ...tracked.meta, revisionId: tracked.allocator.next() };
18029
18164
  }
18030
- function convertRunTextToDelText(run20) {
18031
- for (const child of run20.children) {
18165
+ function convertRunTextToDelText(run21) {
18166
+ for (const child of run21.children) {
18032
18167
  if (child.tag === "w:t")
18033
18168
  child.tag = "w:delText";
18034
18169
  }
@@ -18057,13 +18192,13 @@ var init_replace_span = __esm(() => {
18057
18192
  // src/cli/replace/index.ts
18058
18193
  var exports_replace2 = {};
18059
18194
  __export(exports_replace2, {
18060
- run: () => run20
18195
+ run: () => run21
18061
18196
  });
18062
- import { parseArgs as parseArgs17 } from "util";
18063
- async function run20(args) {
18197
+ import { parseArgs as parseArgs18 } from "util";
18198
+ async function run21(args) {
18064
18199
  let parsed;
18065
18200
  try {
18066
- parsed = parseArgs17({
18201
+ parsed = parseArgs18({
18067
18202
  args,
18068
18203
  allowPositionals: true,
18069
18204
  options: {
@@ -18078,21 +18213,21 @@ async function run20(args) {
18078
18213
  });
18079
18214
  } catch (parseError) {
18080
18215
  const message = parseError instanceof Error ? parseError.message : String(parseError);
18081
- return fail("USAGE", message, HELP20);
18216
+ return fail("USAGE", message, HELP21);
18082
18217
  }
18083
18218
  if (parsed.values.help) {
18084
- await writeStdout(HELP20);
18219
+ await writeStdout(HELP21);
18085
18220
  return EXIT.OK;
18086
18221
  }
18087
18222
  const path = parsed.positionals[0];
18088
18223
  const pattern = parsed.positionals[1];
18089
18224
  const replacement = parsed.positionals[2];
18090
18225
  if (!path)
18091
- return fail("USAGE", "Missing FILE argument", HELP20);
18226
+ return fail("USAGE", "Missing FILE argument", HELP21);
18092
18227
  if (pattern == null)
18093
- return fail("USAGE", "Missing PATTERN argument", HELP20);
18228
+ return fail("USAGE", "Missing PATTERN argument", HELP21);
18094
18229
  if (replacement == null) {
18095
- return fail("USAGE", "Missing REPLACEMENT argument", HELP20);
18230
+ return fail("USAGE", "Missing REPLACEMENT argument", HELP21);
18096
18231
  }
18097
18232
  const ignoreCase = Boolean(parsed.values["ignore-case"]);
18098
18233
  const useRegex = Boolean(parsed.values.regex);
@@ -18194,7 +18329,7 @@ async function run20(args) {
18194
18329
  });
18195
18330
  return EXIT.OK;
18196
18331
  }
18197
- var HELP20 = `docx replace \u2014 substitute text spans (sed for docx)
18332
+ var HELP21 = `docx replace \u2014 substitute text spans (sed for docx)
18198
18333
 
18199
18334
  Usage:
18200
18335
  docx replace FILE PATTERN REPLACEMENT [options]
@@ -18237,13 +18372,13 @@ var init_replace2 = __esm(() => {
18237
18372
  // src/cli/track-changes/index.tsx
18238
18373
  var exports_track_changes = {};
18239
18374
  __export(exports_track_changes, {
18240
- run: () => run21
18375
+ run: () => run22
18241
18376
  });
18242
- import { parseArgs as parseArgs18 } from "util";
18243
- async function run21(args) {
18377
+ import { parseArgs as parseArgs19 } from "util";
18378
+ async function run22(args) {
18244
18379
  let parsed;
18245
18380
  try {
18246
- parsed = parseArgs18({
18381
+ parsed = parseArgs19({
18247
18382
  args,
18248
18383
  allowPositionals: true,
18249
18384
  options: {
@@ -18254,18 +18389,18 @@ async function run21(args) {
18254
18389
  });
18255
18390
  } catch (parseError) {
18256
18391
  const message = parseError instanceof Error ? parseError.message : String(parseError);
18257
- return fail("USAGE", message, HELP21);
18392
+ return fail("USAGE", message, HELP22);
18258
18393
  }
18259
18394
  if (parsed.values.help) {
18260
- await writeStdout(HELP21);
18395
+ await writeStdout(HELP22);
18261
18396
  return EXIT.OK;
18262
18397
  }
18263
18398
  const path = parsed.positionals[0];
18264
18399
  if (!path)
18265
- return fail("USAGE", "Missing FILE argument", HELP21);
18400
+ return fail("USAGE", "Missing FILE argument", HELP22);
18266
18401
  const mode = parsed.positionals[1];
18267
18402
  if (mode !== "on" && mode !== "off") {
18268
- return fail("USAGE", `Expected "on" or "off", got: ${mode ?? "<nothing>"}`, HELP21);
18403
+ return fail("USAGE", `Expected "on" or "off", got: ${mode ?? "<nothing>"}`, HELP22);
18269
18404
  }
18270
18405
  const view = await openOrFail(path);
18271
18406
  if (typeof view === "number")
@@ -18317,7 +18452,7 @@ async function run21(args) {
18317
18452
  });
18318
18453
  return EXIT.OK;
18319
18454
  }
18320
- var HELP21 = `docx track-changes \u2014 toggle the document's tracked-changes mode
18455
+ var HELP22 = `docx track-changes \u2014 toggle the document's tracked-changes mode
18321
18456
 
18322
18457
  Usage:
18323
18458
  docx track-changes FILE on|off [options]
@@ -18344,10 +18479,297 @@ var init_track_changes2 = __esm(() => {
18344
18479
  init_respond();
18345
18480
  init_jsx_dev_runtime();
18346
18481
  });
18482
+
18483
+ // src/cli/wc/count.ts
18484
+ function countWords(text) {
18485
+ const matches = text.match(/\S+/g);
18486
+ return matches?.length ?? 0;
18487
+ }
18488
+ function paragraphText2(paragraph) {
18489
+ let out = "";
18490
+ for (const run23 of paragraph.runs) {
18491
+ if (run23.type === "text")
18492
+ out += run23.text;
18493
+ }
18494
+ return out;
18495
+ }
18496
+ function countWordsInBlocks(blocks) {
18497
+ let total = 0;
18498
+ for (const block of blocks) {
18499
+ if (block.type === "paragraph") {
18500
+ total += countWords(paragraphText2(block));
18501
+ } else if (block.type === "table") {
18502
+ for (const row of block.rows) {
18503
+ for (const cell of row.cells) {
18504
+ total += countWordsInBlocks(cell.blocks);
18505
+ }
18506
+ }
18507
+ }
18508
+ }
18509
+ return total;
18510
+ }
18511
+ function findBlockById(blocks, blockId) {
18512
+ for (const block of blocks) {
18513
+ if (block.id === blockId)
18514
+ return block;
18515
+ if (block.type === "table") {
18516
+ for (const row of block.rows) {
18517
+ for (const cell of row.cells) {
18518
+ const inner = findBlockById(cell.blocks, blockId);
18519
+ if (inner)
18520
+ return inner;
18521
+ }
18522
+ }
18523
+ }
18524
+ }
18525
+ return null;
18526
+ }
18527
+ function countWordsInParagraphSpan(paragraph, start, end) {
18528
+ const text = paragraphText2(paragraph);
18529
+ const clampedEnd = Math.min(Math.max(end, 0), text.length);
18530
+ const clampedStart = Math.min(Math.max(start, 0), clampedEnd);
18531
+ return countWords(text.slice(clampedStart, clampedEnd));
18532
+ }
18533
+ function countWordsInRange(paragraphsInOrder, startBlockId, startOffset, endBlockId, endOffset) {
18534
+ const startIndex = paragraphsInOrder.findIndex((p) => p.id === startBlockId);
18535
+ const endIndex = paragraphsInOrder.findIndex((p) => p.id === endBlockId);
18536
+ if (startIndex === -1 || endIndex === -1)
18537
+ return 0;
18538
+ if (endIndex < startIndex)
18539
+ return 0;
18540
+ if (startIndex === endIndex) {
18541
+ const paragraph = paragraphsInOrder[startIndex];
18542
+ if (!paragraph)
18543
+ return 0;
18544
+ return countWordsInParagraphSpan(paragraph, startOffset, endOffset);
18545
+ }
18546
+ let total = 0;
18547
+ const first = paragraphsInOrder[startIndex];
18548
+ if (first) {
18549
+ total += countWordsInParagraphSpan(first, startOffset, paragraphText2(first).length);
18550
+ }
18551
+ for (let index = startIndex + 1;index < endIndex; index++) {
18552
+ const middle = paragraphsInOrder[index];
18553
+ if (middle)
18554
+ total += countWords(paragraphText2(middle));
18555
+ }
18556
+ const last = paragraphsInOrder[endIndex];
18557
+ if (last)
18558
+ total += countWordsInParagraphSpan(last, 0, endOffset);
18559
+ return total;
18560
+ }
18561
+ function flattenParagraphs(blocks) {
18562
+ const out = [];
18563
+ for (const block of blocks) {
18564
+ if (block.type === "paragraph") {
18565
+ out.push(block);
18566
+ continue;
18567
+ }
18568
+ if (block.type === "table") {
18569
+ for (const row of block.rows) {
18570
+ for (const cell of row.cells) {
18571
+ out.push(...flattenParagraphs(cell.blocks));
18572
+ }
18573
+ }
18574
+ }
18575
+ }
18576
+ return out;
18577
+ }
18578
+
18579
+ // src/cli/wc/index.ts
18580
+ var exports_wc = {};
18581
+ __export(exports_wc, {
18582
+ run: () => run23
18583
+ });
18584
+ import { parseArgs as parseArgs20 } from "util";
18585
+ async function run23(args) {
18586
+ let parsed;
18587
+ try {
18588
+ parsed = parseArgs20({
18589
+ args,
18590
+ allowPositionals: true,
18591
+ options: {
18592
+ help: { type: "boolean", short: "h" }
18593
+ }
18594
+ });
18595
+ } catch (parseError) {
18596
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
18597
+ return fail("USAGE", message, HELP23);
18598
+ }
18599
+ if (parsed.values.help) {
18600
+ await writeStdout(HELP23);
18601
+ return EXIT.OK;
18602
+ }
18603
+ const path = parsed.positionals[0];
18604
+ const locatorInput = parsed.positionals[1];
18605
+ if (!path)
18606
+ return fail("USAGE", "Missing FILE argument", HELP23);
18607
+ const view = await openOrFail(path);
18608
+ if (typeof view === "number")
18609
+ return view;
18610
+ if (!locatorInput) {
18611
+ await respond({
18612
+ ok: true,
18613
+ operation: "wc",
18614
+ path,
18615
+ scope: "document",
18616
+ words: countWordsInBlocks(view.doc.blocks)
18617
+ });
18618
+ return EXIT.OK;
18619
+ }
18620
+ let locator;
18621
+ try {
18622
+ locator = parseLocator(locatorInput);
18623
+ } catch (error) {
18624
+ if (error instanceof LocatorParseError) {
18625
+ return fail("INVALID_LOCATOR", error.message);
18626
+ }
18627
+ throw error;
18628
+ }
18629
+ if (locator.kind === "comment" || locator.kind === "image") {
18630
+ return fail("USAGE", `Locator ${locatorInput} addresses a ${locator.kind}, not text`, "wc accepts paragraph, span, range, table, and cell locators.");
18631
+ }
18632
+ const blocks = view.doc.blocks;
18633
+ if (locator.kind === "block") {
18634
+ const block = findBlockById(blocks, locator.blockId);
18635
+ if (!block) {
18636
+ return fail("BLOCK_NOT_FOUND", `Block not found: ${locator.blockId}`);
18637
+ }
18638
+ const words = block.type === "paragraph" ? countWords(paragraphText2(block)) : block.type === "table" ? countWordsInBlocks([block]) : 0;
18639
+ await respond({
18640
+ ok: true,
18641
+ operation: "wc",
18642
+ path,
18643
+ locator: locatorInput,
18644
+ scope: block.type,
18645
+ words
18646
+ });
18647
+ return EXIT.OK;
18648
+ }
18649
+ if (locator.kind === "blockSpan") {
18650
+ const block = findBlockById(blocks, locator.blockId);
18651
+ if (!block || block.type !== "paragraph") {
18652
+ return fail("BLOCK_NOT_FOUND", `Paragraph not found: ${locator.blockId}`);
18653
+ }
18654
+ await respond({
18655
+ ok: true,
18656
+ operation: "wc",
18657
+ path,
18658
+ locator: locatorInput,
18659
+ scope: "paragraphSpan",
18660
+ words: countWordsInParagraphSpan(block, locator.start, locator.end)
18661
+ });
18662
+ return EXIT.OK;
18663
+ }
18664
+ if (locator.kind === "range") {
18665
+ const paragraphs = flattenParagraphs(blocks);
18666
+ const startExists = paragraphs.some((p) => p.id === locator.start.blockId);
18667
+ const endExists = paragraphs.some((p) => p.id === locator.end.blockId);
18668
+ if (!startExists) {
18669
+ return fail("BLOCK_NOT_FOUND", `Paragraph not found: ${locator.start.blockId}`);
18670
+ }
18671
+ if (!endExists) {
18672
+ return fail("BLOCK_NOT_FOUND", `Paragraph not found: ${locator.end.blockId}`);
18673
+ }
18674
+ await respond({
18675
+ ok: true,
18676
+ operation: "wc",
18677
+ path,
18678
+ locator: locatorInput,
18679
+ scope: "range",
18680
+ words: countWordsInRange(paragraphs, locator.start.blockId, locator.start.offset, locator.end.blockId, locator.end.offset)
18681
+ });
18682
+ return EXIT.OK;
18683
+ }
18684
+ if (locator.kind === "cell") {
18685
+ const cellPath = `${locator.tableId}:r${locator.row}c${locator.col}`;
18686
+ if (!locator.inner) {
18687
+ const cellBlocks = findCellBlocks(blocks, locator);
18688
+ if (!cellBlocks) {
18689
+ return fail("BLOCK_NOT_FOUND", `Cell not found: ${cellPath}`);
18690
+ }
18691
+ await respond({
18692
+ ok: true,
18693
+ operation: "wc",
18694
+ path,
18695
+ locator: locatorInput,
18696
+ scope: "cell",
18697
+ words: countWordsInBlocks(cellBlocks)
18698
+ });
18699
+ return EXIT.OK;
18700
+ }
18701
+ const innerBlockId = locator.inner.kind === "block" ? locator.inner.blockId : locator.inner.kind === "blockSpan" ? locator.inner.blockId : null;
18702
+ if (!innerBlockId) {
18703
+ return fail("USAGE", `Unsupported inner locator for cell: ${locatorInput}`, "Inside a cell, wc accepts pK or pK:S-E only.");
18704
+ }
18705
+ const composedId = `${cellPath}:${innerBlockId}`;
18706
+ const block = findBlockById(blocks, composedId);
18707
+ if (!block || block.type !== "paragraph") {
18708
+ return fail("BLOCK_NOT_FOUND", `Paragraph not found: ${composedId}`);
18709
+ }
18710
+ const words = locator.inner.kind === "blockSpan" ? countWordsInParagraphSpan(block, locator.inner.start, locator.inner.end) : countWords(paragraphText2(block));
18711
+ await respond({
18712
+ ok: true,
18713
+ operation: "wc",
18714
+ path,
18715
+ locator: locatorInput,
18716
+ scope: locator.inner.kind === "blockSpan" ? "paragraphSpan" : "paragraph",
18717
+ words
18718
+ });
18719
+ return EXIT.OK;
18720
+ }
18721
+ return fail("USAGE", `Unsupported locator: ${locatorInput}`);
18722
+ }
18723
+ function findCellBlocks(blocks, locator) {
18724
+ const block = findBlockById(blocks, locator.tableId);
18725
+ if (!block || block.type !== "table")
18726
+ return null;
18727
+ const row = block.rows[locator.row];
18728
+ if (!row)
18729
+ return null;
18730
+ const cell = row.cells[locator.col];
18731
+ if (!cell)
18732
+ return null;
18733
+ return cell.blocks;
18734
+ }
18735
+ var HELP23 = `docx wc \u2014 count words in a document or a locator-addressed slice
18736
+
18737
+ Usage:
18738
+ docx wc FILE [LOCATOR] [options]
18739
+
18740
+ Locators (optional; default: whole document):
18741
+ pN whole paragraph N
18742
+ pN:S-E chars S..E within paragraph N
18743
+ pN:S-pM:E cross-paragraph range
18744
+ tN whole table
18745
+ tN:rRcC whole cell
18746
+ tN:rRcC:pK paragraph K inside that cell
18747
+ tN:rRcC:pK:S-E span within a cell paragraph
18748
+
18749
+ Options:
18750
+ -h, --help show this help
18751
+
18752
+ Counting is whitespace-segmented (\\S+) over the joined paragraph text. Hidden
18753
+ content like images/breaks/tabs contributes no words. Tracked-change deletions
18754
+ are counted alongside insertions because their characters are still part of the
18755
+ on-disk text; accept or reject changes first if you need a delta on the
18756
+ "accepted" view.
18757
+
18758
+ Examples:
18759
+ docx wc doc.docx
18760
+ docx wc doc.docx p3
18761
+ docx wc doc.docx p3:0-120
18762
+ docx wc doc.docx p5:10-p9:42
18763
+ docx wc doc.docx t0:r1c0
18764
+ `;
18765
+ var init_wc = __esm(() => {
18766
+ init_core();
18767
+ init_respond();
18768
+ });
18347
18769
  // package.json
18348
18770
  var package_default = {
18349
18771
  name: "bun-docx",
18350
- version: "0.3.0",
18772
+ version: "0.4.0",
18351
18773
  description: "CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity.",
18352
18774
  keywords: [
18353
18775
  "docx",
@@ -18422,6 +18844,8 @@ Commands:
18422
18844
  delete FILE Remove a block or run range
18423
18845
  find FILE QUERY Find text spans, return locators
18424
18846
  replace FILE PATTERN REPL Substitute text spans (sed for docx)
18847
+ wc FILE [LOCATOR] Count words in the doc or a slice
18848
+ outline FILE List headings as a hierarchical tree
18425
18849
  comments \u2026 Add, reply, resolve, delete, list comments
18426
18850
  images \u2026 Extract, replace, list images
18427
18851
  track-changes FILE on|off Toggle tracked-changes mode
@@ -18453,9 +18877,11 @@ var COMMANDS = {
18453
18877
  images: () => Promise.resolve().then(() => (init_images(), exports_images)),
18454
18878
  info: () => Promise.resolve().then(() => (init_info(), exports_info)),
18455
18879
  insert: () => Promise.resolve().then(() => (init_insert(), exports_insert)),
18880
+ outline: () => Promise.resolve().then(() => (init_outline(), exports_outline)),
18456
18881
  read: () => Promise.resolve().then(() => (init_read2(), exports_read)),
18457
18882
  replace: () => Promise.resolve().then(() => (init_replace2(), exports_replace2)),
18458
- "track-changes": () => Promise.resolve().then(() => (init_track_changes2(), exports_track_changes))
18883
+ "track-changes": () => Promise.resolve().then(() => (init_track_changes2(), exports_track_changes)),
18884
+ wc: () => Promise.resolve().then(() => (init_wc(), exports_wc))
18459
18885
  };
18460
18886
  async function main(argv) {
18461
18887
  const args = argv.slice(2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bun-docx",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "CLI for AI agents (Claude, Codex) to read, edit, and comment on .docx files with full format fidelity.",
5
5
  "keywords": [
6
6
  "docx",