@farazirfan/costar-server-executor 1.7.37 → 1.7.39

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 (253) hide show
  1. package/dist/agent/agent.d.ts +90 -0
  2. package/dist/agent/agent.d.ts.map +1 -1
  3. package/dist/agent/agent.js +606 -0
  4. package/dist/agent/agent.js.map +1 -1
  5. package/dist/agent/pi-embedded-runner/run.d.ts.map +1 -1
  6. package/dist/agent/pi-embedded-runner/run.js +2 -1
  7. package/dist/agent/pi-embedded-runner/run.js.map +1 -1
  8. package/dist/agent/pi-embedded-runner/system-prompt.d.ts.map +1 -1
  9. package/dist/agent/pi-embedded-runner/system-prompt.js +16 -37
  10. package/dist/agent/pi-embedded-runner/system-prompt.js.map +1 -1
  11. package/dist/agent/pi-embedded-runner/tools.d.ts +4 -1
  12. package/dist/agent/pi-embedded-runner/tools.d.ts.map +1 -1
  13. package/dist/agent/pi-embedded-runner/tools.js +3 -1
  14. package/dist/agent/pi-embedded-runner/tools.js.map +1 -1
  15. package/dist/agent/pi-embedded-runner/types.d.ts +4 -0
  16. package/dist/agent/pi-embedded-runner/types.d.ts.map +1 -1
  17. package/dist/cli/env-loader.d.ts.map +1 -1
  18. package/dist/cli/env-loader.js +1 -0
  19. package/dist/cli/env-loader.js.map +1 -1
  20. package/dist/cli/setup.js +2 -2
  21. package/dist/cli/setup.js.map +1 -1
  22. package/dist/cron/normalize.d.ts +31 -0
  23. package/dist/cron/normalize.d.ts.map +1 -0
  24. package/dist/cron/normalize.js +211 -0
  25. package/dist/cron/normalize.js.map +1 -0
  26. package/dist/cron/scheduler.d.ts +33 -3
  27. package/dist/cron/scheduler.d.ts.map +1 -1
  28. package/dist/cron/scheduler.js +253 -48
  29. package/dist/cron/scheduler.js.map +1 -1
  30. package/dist/heartbeat/runner.d.ts +27 -12
  31. package/dist/heartbeat/runner.d.ts.map +1 -1
  32. package/dist/heartbeat/runner.js +82 -104
  33. package/dist/heartbeat/runner.js.map +1 -1
  34. package/dist/infra/heartbeat-events-filter.d.ts +29 -0
  35. package/dist/infra/heartbeat-events-filter.d.ts.map +1 -0
  36. package/dist/infra/heartbeat-events-filter.js +80 -0
  37. package/dist/infra/heartbeat-events-filter.js.map +1 -0
  38. package/dist/infra/index.d.ts +9 -0
  39. package/dist/infra/index.d.ts.map +1 -0
  40. package/dist/infra/index.js +9 -0
  41. package/dist/infra/index.js.map +1 -0
  42. package/dist/infra/system-events.d.ts +58 -2
  43. package/dist/infra/system-events.d.ts.map +1 -1
  44. package/dist/infra/system-events.js +80 -14
  45. package/dist/infra/system-events.js.map +1 -1
  46. package/dist/server.d.ts.map +1 -1
  47. package/dist/server.js +6 -1
  48. package/dist/server.js.map +1 -1
  49. package/dist/services/platform-keys.d.ts +19 -0
  50. package/dist/services/platform-keys.d.ts.map +1 -0
  51. package/dist/services/platform-keys.js +74 -0
  52. package/dist/services/platform-keys.js.map +1 -0
  53. package/dist/subagent/registry.d.ts +96 -0
  54. package/dist/subagent/registry.d.ts.map +1 -0
  55. package/dist/subagent/registry.js +180 -0
  56. package/dist/subagent/registry.js.map +1 -0
  57. package/dist/tools/complete-turn.d.ts +2 -2
  58. package/dist/tools/complete-turn.js +10 -10
  59. package/dist/tools/complete-turn.js.map +1 -1
  60. package/dist/tools/contacts.d.ts +13 -0
  61. package/dist/tools/contacts.d.ts.map +1 -0
  62. package/dist/tools/contacts.js +80 -0
  63. package/dist/tools/contacts.js.map +1 -0
  64. package/dist/tools/cron.d.ts +17 -2
  65. package/dist/tools/cron.d.ts.map +1 -1
  66. package/dist/tools/cron.js +117 -35
  67. package/dist/tools/cron.js.map +1 -1
  68. package/dist/tools/google-maps.d.ts +6 -6
  69. package/dist/tools/google-maps.d.ts.map +1 -1
  70. package/dist/tools/google-maps.js +207 -262
  71. package/dist/tools/google-maps.js.map +1 -1
  72. package/dist/tools/index.d.ts +17 -7
  73. package/dist/tools/index.d.ts.map +1 -1
  74. package/dist/tools/index.js +40 -9
  75. package/dist/tools/index.js.map +1 -1
  76. package/dist/tools/phone-call.d.ts +11 -0
  77. package/dist/tools/phone-call.d.ts.map +1 -0
  78. package/dist/tools/phone-call.js +151 -0
  79. package/dist/tools/phone-call.js.map +1 -0
  80. package/dist/tools/sessions-spawn.d.ts +33 -0
  81. package/dist/tools/sessions-spawn.d.ts.map +1 -0
  82. package/dist/tools/sessions-spawn.js +164 -0
  83. package/dist/tools/sessions-spawn.js.map +1 -0
  84. package/dist/tools/spotify.d.ts +12 -0
  85. package/dist/tools/spotify.d.ts.map +1 -0
  86. package/dist/tools/spotify.js +251 -0
  87. package/dist/tools/spotify.js.map +1 -0
  88. package/dist/tools/subagents.d.ts +23 -0
  89. package/dist/tools/subagents.d.ts.map +1 -0
  90. package/dist/tools/subagents.js +209 -0
  91. package/dist/tools/subagents.js.map +1 -0
  92. package/dist/tools/whatsapp.d.ts +13 -0
  93. package/dist/tools/whatsapp.d.ts.map +1 -0
  94. package/dist/tools/whatsapp.js +215 -0
  95. package/dist/tools/whatsapp.js.map +1 -0
  96. package/dist/tools/youtube.d.ts +12 -0
  97. package/dist/tools/youtube.d.ts.map +1 -0
  98. package/dist/tools/youtube.js +218 -0
  99. package/dist/tools/youtube.js.map +1 -0
  100. package/dist/utils/asterizk-auth.d.ts +43 -0
  101. package/dist/utils/asterizk-auth.d.ts.map +1 -0
  102. package/dist/utils/asterizk-auth.js +125 -0
  103. package/dist/utils/asterizk-auth.js.map +1 -0
  104. package/dist/web-server.d.ts.map +1 -1
  105. package/dist/web-server.js +132 -0
  106. package/dist/web-server.js.map +1 -1
  107. package/dist/workspace/index.d.ts +3 -4
  108. package/dist/workspace/index.d.ts.map +1 -1
  109. package/dist/workspace/index.js +3 -4
  110. package/dist/workspace/index.js.map +1 -1
  111. package/dist/workspace/templates.d.ts +8 -7
  112. package/dist/workspace/templates.d.ts.map +1 -1
  113. package/dist/workspace/templates.js +18 -127
  114. package/dist/workspace/templates.js.map +1 -1
  115. package/dist/workspace/workspace.d.ts +2 -4
  116. package/dist/workspace/workspace.d.ts.map +1 -1
  117. package/dist/workspace/workspace.js +7 -16
  118. package/dist/workspace/workspace.js.map +1 -1
  119. package/package.json +1 -1
  120. package/public/index.html +231 -0
  121. package/skills/docx/SKILL.md +468 -0
  122. package/skills/docx/scripts/__init__.py +1 -0
  123. package/skills/docx/scripts/accept_changes.py +181 -0
  124. package/skills/docx/scripts/comment.py +347 -0
  125. package/skills/docx/scripts/helpers/__init__.py +0 -0
  126. package/skills/docx/scripts/helpers/merge_runs.py +231 -0
  127. package/skills/docx/scripts/helpers/simplify_redlines.py +240 -0
  128. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  129. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  130. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  131. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  132. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  133. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  134. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  135. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  136. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  137. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  138. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  139. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  140. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  141. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  142. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  143. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  144. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  145. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  146. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  147. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  148. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  149. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  150. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  151. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  152. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  153. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  154. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  155. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  156. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  157. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  158. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  159. package/skills/docx/scripts/ooxml/schemas/mce/mc.xsd +75 -0
  160. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  161. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  162. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  163. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  164. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  165. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  166. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  167. package/skills/docx/scripts/ooxml/scripts/pack.py +159 -0
  168. package/skills/docx/scripts/ooxml/scripts/unpack.py +29 -0
  169. package/skills/docx/scripts/ooxml/scripts/validate.py +106 -0
  170. package/skills/docx/scripts/ooxml/scripts/validation/__init__.py +15 -0
  171. package/skills/docx/scripts/ooxml/scripts/validation/base.py +1023 -0
  172. package/skills/docx/scripts/ooxml/scripts/validation/docx.py +519 -0
  173. package/skills/docx/scripts/ooxml/scripts/validation/pptx.py +315 -0
  174. package/skills/docx/scripts/ooxml/scripts/validation/redlining.py +284 -0
  175. package/skills/docx/scripts/pack.py +166 -0
  176. package/skills/docx/scripts/templates/comments.xml +3 -0
  177. package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  178. package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  179. package/skills/docx/scripts/templates/commentsIds.xml +3 -0
  180. package/skills/docx/scripts/templates/people.xml +3 -0
  181. package/skills/docx/scripts/unpack.py +134 -0
  182. package/skills/longform-video-generation/SKILL.md +298 -0
  183. package/skills/longform-video-generation/references/advanced_techniques.md +474 -0
  184. package/skills/longform-video-generation/references/google_api_guide.md +288 -0
  185. package/skills/longform-video-generation/scripts/video_generator.py +579 -0
  186. package/skills/pdf/FORMS.md +305 -0
  187. package/skills/pdf/REFERENCE.md +612 -0
  188. package/skills/pdf/SKILL.md +293 -0
  189. package/skills/pdf/scripts/check_bounding_boxes.py +70 -0
  190. package/skills/pdf/scripts/check_fillable_fields.py +12 -0
  191. package/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
  192. package/skills/pdf/scripts/create_validation_image.py +41 -0
  193. package/skills/pdf/scripts/extract_form_field_info.py +152 -0
  194. package/skills/pdf/scripts/extract_form_structure.py +124 -0
  195. package/skills/pdf/scripts/fill_fillable_fields.py +116 -0
  196. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +136 -0
  197. package/skills/pptx/SKILL.md +171 -0
  198. package/skills/pptx/editing.md +205 -0
  199. package/skills/pptx/pptxgenjs.md +377 -0
  200. package/skills/pptx/scripts/add_slide.py +225 -0
  201. package/skills/pptx/scripts/clean.py +309 -0
  202. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  203. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  204. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  205. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  206. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  207. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  208. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  209. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  210. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  211. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  212. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  213. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  214. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  215. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  216. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  217. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  218. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  219. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  220. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  221. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  222. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  223. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  224. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  225. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  226. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  227. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  228. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  229. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  230. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  231. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  232. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  233. package/skills/pptx/scripts/ooxml/schemas/mce/mc.xsd +75 -0
  234. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  235. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  236. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  237. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  238. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  239. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  240. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  241. package/skills/pptx/scripts/ooxml/scripts/pack.py +159 -0
  242. package/skills/pptx/scripts/ooxml/scripts/unpack.py +29 -0
  243. package/skills/pptx/scripts/ooxml/scripts/validate.py +106 -0
  244. package/skills/pptx/scripts/ooxml/scripts/validation/__init__.py +15 -0
  245. package/skills/pptx/scripts/ooxml/scripts/validation/base.py +1023 -0
  246. package/skills/pptx/scripts/ooxml/scripts/validation/docx.py +519 -0
  247. package/skills/pptx/scripts/ooxml/scripts/validation/pptx.py +315 -0
  248. package/skills/pptx/scripts/ooxml/scripts/validation/redlining.py +284 -0
  249. package/skills/pptx/scripts/pack.py +168 -0
  250. package/skills/pptx/scripts/thumbnail.py +318 -0
  251. package/skills/pptx/scripts/unpack.py +86 -0
  252. package/skills/xlsx/SKILL.md +291 -0
  253. package/skills/xlsx/recalc.py +247 -0
@@ -0,0 +1,468 @@
1
+ ---
2
+ name: docx
3
+ description: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When you need to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks"
4
+ ---
5
+
6
+ # DOCX creation, editing, and analysis
7
+
8
+ ## Overview
9
+
10
+ A .docx file is a ZIP archive containing XML files.
11
+
12
+ ## Quick Reference
13
+
14
+ | Task | Approach |
15
+ |------|----------|
16
+ | Read/analyze content | `pandoc` or unpack for raw XML |
17
+ | Create new document | Use `docx-js` - see Creating New Documents below |
18
+ | Edit existing document | Unpack → edit XML → repack - see Editing Existing Documents below |
19
+
20
+ ### Converting .doc to .docx
21
+
22
+ Legacy `.doc` files must be converted before editing:
23
+
24
+ ```bash
25
+ soffice --headless --convert-to docx document.doc
26
+ ```
27
+
28
+ ### Reading Content
29
+
30
+ ```bash
31
+ # Text extraction with tracked changes
32
+ pandoc --track-changes=all document.docx -o output.md
33
+
34
+ # Raw XML access
35
+ python scripts/unpack.py document.docx unpacked/
36
+ ```
37
+
38
+ ### Converting to Images
39
+
40
+ ```bash
41
+ soffice --headless --convert-to pdf document.docx
42
+ pdftoppm -jpeg -r 150 document.pdf page
43
+ ```
44
+
45
+ ### Accepting Tracked Changes
46
+
47
+ To produce a clean document with all tracked changes accepted (requires LibreOffice):
48
+
49
+ ```bash
50
+ python scripts/accept_changes.py input.docx output.docx
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Creating New Documents
56
+
57
+ Generate .docx files with JavaScript using the pre-installed `docx` library.
58
+
59
+ ### Setup
60
+ ```javascript
61
+ const { Document, Packer, Paragraph, TextRun, Table, TableRow, TableCell, ImageRun,
62
+ Header, Footer, AlignmentType, PageOrientation, LevelFormat, ExternalHyperlink,
63
+ TableOfContents, HeadingLevel, BorderStyle, WidthType, ShadingType,
64
+ VerticalAlign, PageNumber, PageBreak } = require('docx');
65
+
66
+ const doc = new Document({ sections: [{ children: [/* content */] }] });
67
+ Packer.toBuffer(doc).then(buffer => fs.writeFileSync("doc.docx", buffer));
68
+ ```
69
+
70
+ ### Page Size
71
+
72
+ ```javascript
73
+ // CRITICAL: docx-js defaults to A4, not US Letter
74
+ // Always set page size explicitly for consistent results
75
+ sections: [{
76
+ properties: {
77
+ page: {
78
+ size: {
79
+ width: 12240, // 8.5 inches in DXA
80
+ height: 15840 // 11 inches in DXA
81
+ },
82
+ margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } // 1 inch margins
83
+ }
84
+ },
85
+ children: [/* content */]
86
+ }]
87
+ ```
88
+
89
+ **Common page sizes (DXA units, 1440 DXA = 1 inch):**
90
+
91
+ | Paper | Width | Height | Content Width (1" margins) |
92
+ |-------|-------|--------|---------------------------|
93
+ | US Letter | 12,240 | 15,840 | 9,360 |
94
+ | A4 (default) | 11,906 | 16,838 | 9,026 |
95
+
96
+ ### Styles (Override Built-in Headings)
97
+
98
+ Use Arial as the default font (universally supported). Keep titles black for readability.
99
+
100
+ ```javascript
101
+ const doc = new Document({
102
+ styles: {
103
+ default: { document: { run: { font: "Arial", size: 24 } } }, // 12pt default
104
+ paragraphStyles: [
105
+ // IMPORTANT: Use exact IDs to override built-in styles
106
+ { id: "Heading1", name: "Heading 1", basedOn: "Normal", next: "Normal", quickFormat: true,
107
+ run: { size: 32, bold: true, font: "Arial" },
108
+ paragraph: { spacing: { before: 240, after: 240 }, outlineLevel: 0 } }, // outlineLevel required for TOC
109
+ { id: "Heading2", name: "Heading 2", basedOn: "Normal", next: "Normal", quickFormat: true,
110
+ run: { size: 28, bold: true, font: "Arial" },
111
+ paragraph: { spacing: { before: 180, after: 180 }, outlineLevel: 1 } },
112
+ ]
113
+ },
114
+ sections: [{
115
+ children: [
116
+ new Paragraph({ heading: HeadingLevel.HEADING_1, children: [new TextRun("Title")] }),
117
+ ]
118
+ }]
119
+ });
120
+ ```
121
+
122
+ ### Lists (NEVER use unicode bullets)
123
+
124
+ ```javascript
125
+ // ❌ WRONG - never manually insert bullet characters
126
+ new Paragraph({ children: [new TextRun("• Item")] }) // BAD
127
+ new Paragraph({ children: [new TextRun("\u2022 Item")] }) // BAD
128
+
129
+ // ✅ CORRECT - use numbering config with LevelFormat.BULLET
130
+ const doc = new Document({
131
+ numbering: {
132
+ config: [
133
+ { reference: "bullets",
134
+ levels: [{ level: 0, format: LevelFormat.BULLET, text: "•", alignment: AlignmentType.LEFT,
135
+ style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
136
+ { reference: "numbers",
137
+ levels: [{ level: 0, format: LevelFormat.DECIMAL, text: "%1.", alignment: AlignmentType.LEFT,
138
+ style: { paragraph: { indent: { left: 720, hanging: 360 } } } }] },
139
+ ]
140
+ },
141
+ sections: [{
142
+ children: [
143
+ new Paragraph({ numbering: { reference: "bullets", level: 0 },
144
+ children: [new TextRun("Bullet item")] }),
145
+ new Paragraph({ numbering: { reference: "numbers", level: 0 },
146
+ children: [new TextRun("Numbered item")] }),
147
+ ]
148
+ }]
149
+ });
150
+
151
+ // ⚠️ Each reference creates INDEPENDENT numbering
152
+ // Same reference = continues (1,2,3 then 4,5,6)
153
+ // Different reference = restarts (1,2,3 then 1,2,3)
154
+ ```
155
+
156
+ ### Tables
157
+
158
+ **CRITICAL: Tables need dual widths** - set both `columnWidths` on the table AND `width` on each cell. Without both, tables render incorrectly on some platforms.
159
+
160
+ ```javascript
161
+ // CRITICAL: Always set table width for consistent rendering
162
+ // CRITICAL: Use ShadingType.CLEAR (not SOLID) to prevent black backgrounds
163
+ const border = { style: BorderStyle.SINGLE, size: 1, color: "CCCCCC" };
164
+ const borders = { top: border, bottom: border, left: border, right: border };
165
+
166
+ new Table({
167
+ width: { size: 100, type: WidthType.PERCENTAGE }, // Always set table width
168
+ columnWidths: [4680, 4680], // Set at table level (DXA: 1440 = 1 inch)
169
+ rows: [
170
+ new TableRow({
171
+ children: [
172
+ new TableCell({
173
+ borders,
174
+ width: { size: 4680, type: WidthType.DXA }, // Also set on each cell
175
+ shading: { fill: "D5E8F0", type: ShadingType.CLEAR }, // CLEAR not SOLID
176
+ margins: { top: 80, bottom: 80, left: 120, right: 120 }, // Cell padding (internal, not added to width)
177
+ children: [new Paragraph({ children: [new TextRun("Cell")] })]
178
+ })
179
+ ]
180
+ })
181
+ ]
182
+ })
183
+ ```
184
+
185
+ **Table width calculation:**
186
+
187
+ Use `WidthType.PERCENTAGE` for simplicity, or `WidthType.DXA` for precise control:
188
+
189
+ ```javascript
190
+ // Option 1: Percentage (recommended - automatically fits content area)
191
+ width: { size: 100, type: WidthType.PERCENTAGE }
192
+
193
+ // Option 2: DXA (precise control)
194
+ // Table width = sum of columnWidths = content width
195
+ // US Letter with 1" margins: 12240 - 2880 = 9360 DXA
196
+ width: { size: 9360, type: WidthType.DXA },
197
+ columnWidths: [7000, 2360] // Must sum to table width
198
+ ```
199
+
200
+ **Width rules:**
201
+ - Table width must equal the sum of `columnWidths`
202
+ - Cell `width` must match corresponding `columnWidth`
203
+ - Cell `margins` are internal padding - they reduce content area, not add to cell width
204
+ - For full-width tables: use content width (page width minus left and right margins)
205
+
206
+ ### Images
207
+
208
+ ```javascript
209
+ // CRITICAL: type parameter is REQUIRED
210
+ new Paragraph({
211
+ children: [new ImageRun({
212
+ type: "png", // Required: png, jpg, jpeg, gif, bmp, svg
213
+ data: fs.readFileSync("image.png"),
214
+ transformation: { width: 200, height: 150 },
215
+ altText: { title: "Title", description: "Desc", name: "Name" } // All three required
216
+ })]
217
+ })
218
+ ```
219
+
220
+ ### Page Breaks
221
+
222
+ ```javascript
223
+ // CRITICAL: PageBreak must be inside a Paragraph
224
+ new Paragraph({ children: [new PageBreak()] })
225
+
226
+ // Or use pageBreakBefore
227
+ new Paragraph({ pageBreakBefore: true, children: [new TextRun("New page")] })
228
+ ```
229
+
230
+ ### Table of Contents
231
+
232
+ ```javascript
233
+ // CRITICAL: Headings must use HeadingLevel ONLY - no custom styles
234
+ new TableOfContents("Table of Contents", { hyperlink: true, headingStyleRange: "1-3" })
235
+ ```
236
+
237
+ ### Headers/Footers
238
+
239
+ ```javascript
240
+ sections: [{
241
+ properties: {
242
+ page: { margin: { top: 1440, right: 1440, bottom: 1440, left: 1440 } } // 1440 = 1 inch
243
+ },
244
+ headers: {
245
+ default: new Header({ children: [new Paragraph({ children: [new TextRun("Header")] })] })
246
+ },
247
+ footers: {
248
+ default: new Footer({ children: [new Paragraph({
249
+ children: [new TextRun("Page "), new TextRun({ children: [PageNumber.CURRENT] })]
250
+ })] })
251
+ },
252
+ children: [/* content */]
253
+ }]
254
+ ```
255
+
256
+ ### Critical Rules for docx-js
257
+
258
+ - **Set page size explicitly** - docx-js defaults to A4; use US Letter (12240 x 15840 DXA) for US documents
259
+ - **Never use `\n`** - use separate Paragraph elements
260
+ - **Never use unicode bullets** - use `LevelFormat.BULLET` with numbering config
261
+ - **PageBreak must be in Paragraph** - standalone creates invalid XML
262
+ - **ImageRun requires `type`** - always specify png/jpg/etc
263
+ - **Always set table `width`** - use `{ size: 100, type: WidthType.PERCENTAGE }` for full width
264
+ - **Tables need dual widths** - `columnWidths` array AND cell `width`, both must match
265
+ - **Table width = sum of columnWidths** - for DXA, ensure they add up exactly
266
+ - **Always add cell margins** - use `margins: { top: 80, bottom: 80, left: 120, right: 120 }` for readable padding
267
+ - **Use `ShadingType.CLEAR`** - never SOLID for table shading
268
+ - **TOC requires HeadingLevel only** - no custom styles on heading paragraphs
269
+ - **Override built-in styles** - use exact IDs: "Heading1", "Heading2", etc.
270
+ - **Include `outlineLevel`** - required for TOC (0 for H1, 1 for H2, etc.)
271
+
272
+ ---
273
+
274
+ ## Editing Existing Documents
275
+
276
+ **Follow all 3 steps in order.**
277
+
278
+ ### Step 1: Unpack
279
+ ```bash
280
+ python scripts/unpack.py document.docx unpacked/
281
+ ```
282
+ Extracts XML, pretty-prints, merges adjacent runs, and converts smart quotes to XML entities (`“` etc.) so they survive editing. Use `--merge-runs false` to skip run merging.
283
+
284
+ ### Step 2: Edit XML
285
+
286
+ Edit files in `unpacked/word/`. See XML Reference below for patterns.
287
+
288
+ **Use "Costar" as the author** for tracked changes and comments, unless the user explicitly requests use of a different name.
289
+
290
+ **Use the Edit tool directly for string replacement. Do not write Python scripts.** Scripts introduce unnecessary complexity. The Edit tool shows exactly what is being replaced.
291
+
292
+ **CRITICAL: Use smart quotes for new content.** When adding text with apostrophes or quotes, use XML entities to produce smart quotes:
293
+ ```xml
294
+ <!-- Use these entities for professional typography -->
295
+ <w:t>Here&#x2019;s a quote: &#x201C;Hello&#x201D;</w:t>
296
+ ```
297
+ | Entity | Character |
298
+ |--------|-----------|
299
+ | `&#x2018;` | ‘ (left single) |
300
+ | `&#x2019;` | ’ (right single / apostrophe) |
301
+ | `&#x201C;` | “ (left double) |
302
+ | `&#x201D;` | ” (right double) |
303
+
304
+ **Adding comments:** Use `comment.py` to handle boilerplate across multiple XML files (text must be pre-escaped XML):
305
+ ```bash
306
+ python scripts/comment.py unpacked/ 0 "Comment text with &amp; and &#x2019;"
307
+ python scripts/comment.py unpacked/ 1 "Reply text" --parent 0 # reply to comment 0
308
+ python scripts/comment.py unpacked/ 0 "Text" --author "Custom Author" # custom author name
309
+ ```
310
+ Then add markers to document.xml (see Comments in XML Reference).
311
+
312
+ ### Step 3: Pack
313
+ ```bash
314
+ python scripts/pack.py unpacked/ output.docx --original document.docx
315
+ ```
316
+ Validates with auto-repair, condenses XML, and creates DOCX. Use `--validate false` to skip.
317
+
318
+ **Auto-repair will fix:**
319
+ - `durableId` >= 0x7FFFFFFF (regenerates valid ID)
320
+ - Missing `xml:space="preserve"` on `<w:t>` with whitespace
321
+
322
+ **Auto-repair won't fix:**
323
+ - Malformed XML, invalid element nesting, missing relationships, schema violations
324
+
325
+ ### Common Pitfalls
326
+
327
+ - **Replace entire `<w:r>` elements**: When adding tracked changes, replace the whole `<w:r>...</w:r>` block with `<w:del>...<w:ins>...` as siblings. Don't inject tracked change tags inside a run.
328
+ - **Preserve `<w:rPr>` formatting**: Copy the original run's `<w:rPr>` block into your tracked change runs to maintain bold, font size, etc.
329
+
330
+ ---
331
+
332
+ ## XML Reference
333
+
334
+ ### Schema Compliance
335
+
336
+ - **Element order in `<w:pPr>`**: `<w:pStyle>`, `<w:numPr>`, `<w:spacing>`, `<w:ind>`, `<w:jc>`, `<w:rPr>` last
337
+ - **Whitespace**: Add `xml:space="preserve"` to `<w:t>` with leading/trailing spaces
338
+ - **RSIDs**: Must be 8-digit hex (e.g., `00AB1234`)
339
+
340
+ ### Tracked Changes
341
+
342
+ **Insertion:**
343
+ ```xml
344
+ <w:ins w:id="1" w:author="Costar" w:date="2025-01-01T00:00:00Z">
345
+ <w:r><w:t>inserted text</w:t></w:r>
346
+ </w:ins>
347
+ ```
348
+
349
+ **Deletion:**
350
+ ```xml
351
+ <w:del w:id="2" w:author="Costar" w:date="2025-01-01T00:00:00Z">
352
+ <w:r><w:delText>deleted text</w:delText></w:r>
353
+ </w:del>
354
+ ```
355
+
356
+ **Inside `<w:del>`**: Use `<w:delText>` instead of `<w:t>`, and `<w:delInstrText>` instead of `<w:instrText>`.
357
+
358
+ **Minimal edits** - only mark what changes:
359
+ ```xml
360
+ <!-- Change "30 days" to "60 days" -->
361
+ <w:r><w:t>The term is </w:t></w:r>
362
+ <w:del w:id="1" w:author="Costar" w:date="...">
363
+ <w:r><w:delText>30</w:delText></w:r>
364
+ </w:del>
365
+ <w:ins w:id="2" w:author="Costar" w:date="...">
366
+ <w:r><w:t>60</w:t></w:r>
367
+ </w:ins>
368
+ <w:r><w:t> days.</w:t></w:r>
369
+ ```
370
+
371
+ **Deleting entire paragraphs/list items** - when removing ALL content from a paragraph, also mark the paragraph mark as deleted so it merges with the next paragraph. Add `<w:del/>` inside `<w:pPr><w:rPr>`:
372
+ ```xml
373
+ <w:p>
374
+ <w:pPr>
375
+ <w:numPr>...</w:numPr> <!-- list numbering if present -->
376
+ <w:rPr>
377
+ <w:del w:id="1" w:author="Costar" w:date="2025-01-01T00:00:00Z"/>
378
+ </w:rPr>
379
+ </w:pPr>
380
+ <w:del w:id="2" w:author="Costar" w:date="2025-01-01T00:00:00Z">
381
+ <w:r><w:delText>Entire paragraph content being deleted...</w:delText></w:r>
382
+ </w:del>
383
+ </w:p>
384
+ ```
385
+ Without the `<w:del/>` in `<w:pPr><w:rPr>`, accepting changes leaves an empty paragraph/list item.
386
+
387
+ **Rejecting another author's insertion** - nest deletion inside their insertion:
388
+ ```xml
389
+ <w:ins w:author="Jane" w:id="5">
390
+ <w:del w:author="Costar" w:id="10">
391
+ <w:r><w:delText>their inserted text</w:delText></w:r>
392
+ </w:del>
393
+ </w:ins>
394
+ ```
395
+
396
+ **Restoring another author's deletion** - add insertion after (don't modify their deletion):
397
+ ```xml
398
+ <w:del w:author="Jane" w:id="5">
399
+ <w:r><w:delText>deleted text</w:delText></w:r>
400
+ </w:del>
401
+ <w:ins w:author="Costar" w:id="10">
402
+ <w:r><w:t>deleted text</w:t></w:r>
403
+ </w:ins>
404
+ ```
405
+
406
+ ### Comments
407
+
408
+ After running `comment.py` (see Step 2), add markers to document.xml. For replies, use `--parent` flag and nest markers inside the parent's.
409
+
410
+ **CRITICAL: `<w:commentRangeStart>` and `<w:commentRangeEnd>` are siblings of `<w:r>`, never inside `<w:r>`.**
411
+
412
+ ```xml
413
+ <!-- Comment markers are direct children of w:p, never inside w:r -->
414
+ <w:commentRangeStart w:id="0"/>
415
+ <w:del w:id="1" w:author="Costar" w:date="2025-01-01T00:00:00Z">
416
+ <w:r><w:delText>deleted</w:delText></w:r>
417
+ </w:del>
418
+ <w:r><w:t> more text</w:t></w:r>
419
+ <w:commentRangeEnd w:id="0"/>
420
+ <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>
421
+
422
+ <!-- Comment 0 with reply 1 nested inside -->
423
+ <w:commentRangeStart w:id="0"/>
424
+ <w:commentRangeStart w:id="1"/>
425
+ <w:r><w:t>text</w:t></w:r>
426
+ <w:commentRangeEnd w:id="1"/>
427
+ <w:commentRangeEnd w:id="0"/>
428
+ <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="0"/></w:r>
429
+ <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="1"/></w:r>
430
+ ```
431
+
432
+ ### Images
433
+
434
+ 1. Add image file to `word/media/`
435
+ 2. Add relationship to `word/_rels/document.xml.rels`:
436
+ ```xml
437
+ <Relationship Id="rId5" Type=".../image" Target="media/image1.png"/>
438
+ ```
439
+ 3. Add content type to `[Content_Types].xml`:
440
+ ```xml
441
+ <Default Extension="png" ContentType="image/png"/>
442
+ ```
443
+ 4. Reference in document.xml:
444
+ ```xml
445
+ <w:drawing>
446
+ <wp:inline>
447
+ <wp:extent cx="914400" cy="914400"/> <!-- EMUs: 914400 = 1 inch -->
448
+ <a:graphic>
449
+ <a:graphicData uri=".../picture">
450
+ <pic:pic>
451
+ <pic:blipFill><a:blip r:embed="rId5"/></pic:blipFill>
452
+ </pic:pic>
453
+ </a:graphicData>
454
+ </a:graphic>
455
+ </wp:inline>
456
+ </w:drawing>
457
+ ```
458
+
459
+ ---
460
+
461
+ ## Dependencies
462
+
463
+ All required tools are pre-installed:
464
+
465
+ - **pandoc**: Text extraction and document conversion
466
+ - **docx**: Node.js library for creating new documents
467
+ - **LibreOffice**: PDF conversion
468
+ - **Poppler (pdftoppm)**: PDF to image conversion
@@ -0,0 +1 @@
1
+ # Make scripts directory a package for relative imports in tests
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env python3
2
+ """Accept all tracked changes in a DOCX file using LibreOffice.
3
+
4
+ Requires LibreOffice (soffice) to be installed.
5
+ """
6
+
7
+ import argparse
8
+ import logging
9
+ import os
10
+ import platform
11
+ import shutil
12
+ import subprocess
13
+ import time
14
+ from pathlib import Path
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # LibreOffice profile directory for macro storage
19
+ LIBREOFFICE_PROFILE = "/tmp/libreoffice_docx_profile"
20
+ MACRO_DIR = f"{LIBREOFFICE_PROFILE}/user/basic/Standard"
21
+
22
+ ACCEPT_CHANGES_MACRO = """<?xml version="1.0" encoding="UTF-8"?>
23
+ <!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
24
+ <script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">
25
+ Sub AcceptAllTrackedChanges()
26
+ Dim document As Object
27
+ Dim dispatcher As Object
28
+
29
+ document = ThisComponent.CurrentController.Frame
30
+ dispatcher = createUnoService("com.sun.star.frame.DispatchHelper")
31
+
32
+ dispatcher.executeDispatch(document, ".uno:AcceptAllTrackedChanges", "", 0, Array())
33
+ ThisComponent.store()
34
+ ThisComponent.close(True)
35
+ End Sub
36
+ </script:module>"""
37
+
38
+
39
+ def accept_changes(
40
+ input_file: str,
41
+ output_file: str,
42
+ ) -> tuple[None, str]:
43
+ """Accept all tracked changes in a DOCX file and save to output file.
44
+
45
+ Args:
46
+ input_file: Path to input DOCX file with tracked changes
47
+ output_file: Path to output DOCX file (will be created/overwritten)
48
+
49
+ Returns:
50
+ (None, message) - message indicates success or failure
51
+ """
52
+ input_path = Path(input_file)
53
+ output_path = Path(output_file)
54
+
55
+ if not input_path.exists():
56
+ return None, f"Error: Input file not found: {input_file}"
57
+
58
+ if not input_path.suffix.lower() == ".docx":
59
+ return None, f"Error: Input file is not a DOCX file: {input_file}"
60
+
61
+ # Copy input file to output file location
62
+ try:
63
+ output_path.parent.mkdir(parents=True, exist_ok=True)
64
+ shutil.copy2(input_path, output_path)
65
+ except Exception as e:
66
+ return None, f"Error: Failed to copy input file to output location: {e}"
67
+
68
+ # Setup LibreOffice macro
69
+ if not _setup_libreoffice_macro():
70
+ return None, "Error: Failed to setup LibreOffice macro"
71
+
72
+ # Ensure Xvfb is running (for headless operation on Linux)
73
+ if platform.system() == "Linux":
74
+ try:
75
+ _ensure_xvfb_running()
76
+ except RuntimeError as e:
77
+ return None, f"Error: {e}"
78
+
79
+ # Run LibreOffice with macro to accept changes
80
+ cmd = [
81
+ "soffice",
82
+ "--headless",
83
+ f"-env:UserInstallation=file://{LIBREOFFICE_PROFILE}",
84
+ "--norestore",
85
+ "vnd.sun.star.script:Standard.Module1.AcceptAllTrackedChanges?language=Basic&location=application",
86
+ str(output_path.absolute()),
87
+ ]
88
+
89
+ try:
90
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=30, check=False)
91
+ except subprocess.TimeoutExpired:
92
+ # Timeout is expected - LibreOffice may hang after completing
93
+ return None, f"Successfully accepted all tracked changes: {input_file} -> {output_file}"
94
+
95
+ if result.returncode != 0:
96
+ return None, f"Error: LibreOffice failed: {result.stderr}"
97
+
98
+ return None, f"Successfully accepted all tracked changes: {input_file} -> {output_file}"
99
+
100
+
101
+ def _ensure_xvfb_running() -> None:
102
+ """Ensure Xvfb is running on display :99."""
103
+ if os.environ.get("DISPLAY"):
104
+ return
105
+
106
+ # Check if already running
107
+ try:
108
+ result = subprocess.run(
109
+ ["pgrep", "-f", "Xvfb.*:99"], capture_output=True, text=True, check=False
110
+ )
111
+ if result.returncode == 0 and result.stdout.strip():
112
+ os.environ["DISPLAY"] = ":99"
113
+ return
114
+ except FileNotFoundError:
115
+ pass
116
+
117
+ # Start Xvfb
118
+ try:
119
+ subprocess.Popen(
120
+ ["Xvfb", ":99", "-screen", "0", "1024x768x24"],
121
+ stdout=subprocess.DEVNULL,
122
+ stderr=subprocess.DEVNULL,
123
+ )
124
+ except FileNotFoundError as e:
125
+ raise RuntimeError("Xvfb not found - install with: apt-get install xvfb") from e
126
+
127
+ os.environ["DISPLAY"] = ":99"
128
+
129
+ # Wait for Xvfb to be ready
130
+ socket_path = "/tmp/.X11-unix/X99"
131
+ for _ in range(20):
132
+ if os.path.exists(socket_path):
133
+ return
134
+ time.sleep(0.1)
135
+ raise RuntimeError("Xvfb started but socket not ready")
136
+
137
+
138
+ def _setup_libreoffice_macro() -> bool:
139
+ """Setup LibreOffice macro for accepting tracked changes."""
140
+ macro_dir = Path(MACRO_DIR)
141
+ macro_file = macro_dir / "Module1.xba"
142
+
143
+ if macro_file.exists() and "AcceptAllTrackedChanges" in macro_file.read_text():
144
+ return True
145
+
146
+ # Initialize LibreOffice if needed (use custom profile)
147
+ if not macro_dir.exists():
148
+ subprocess.run(
149
+ [
150
+ "soffice",
151
+ "--headless",
152
+ f"-env:UserInstallation=file://{LIBREOFFICE_PROFILE}",
153
+ "--terminate_after_init",
154
+ ],
155
+ capture_output=True,
156
+ timeout=10,
157
+ check=False,
158
+ )
159
+ macro_dir.mkdir(parents=True, exist_ok=True)
160
+
161
+ try:
162
+ macro_file.write_text(ACCEPT_CHANGES_MACRO)
163
+ return True
164
+ except Exception as e:
165
+ logger.warning(f"Failed to setup LibreOffice macro: {e}")
166
+ return False
167
+
168
+
169
+ if __name__ == "__main__":
170
+ parser = argparse.ArgumentParser(
171
+ description="Accept all tracked changes in a DOCX file"
172
+ )
173
+ parser.add_argument("input_file", help="Input DOCX file with tracked changes")
174
+ parser.add_argument("output_file", help="Output DOCX file (clean, no tracked changes)")
175
+ args = parser.parse_args()
176
+
177
+ _, message = accept_changes(args.input_file, args.output_file)
178
+ print(message)
179
+
180
+ if "Error" in message:
181
+ raise SystemExit(1)