anymous 1.2.7 → 1.3.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 (567) hide show
  1. package/bin/anymous.cjs +1 -1
  2. package/package.json +1 -1
  3. package/script/postinstall.mjs +214 -0
  4. package/skills/algorithmic-art/LICENSE.txt +202 -0
  5. package/skills/algorithmic-art/SKILL.md +405 -0
  6. package/skills/algorithmic-art/templates/generator_template.js +223 -0
  7. package/skills/algorithmic-art/templates/viewer.html +599 -0
  8. package/skills/artifacts-builder/LICENSE.txt +202 -0
  9. package/skills/artifacts-builder/SKILL.md +74 -0
  10. package/skills/artifacts-builder/scripts/bundle-artifact.sh +54 -0
  11. package/skills/artifacts-builder/scripts/init-artifact.sh +322 -0
  12. package/skills/artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  13. package/skills/brainstorming/SKILL.md +151 -0
  14. package/skills/brainstorming/scripts/frame-template.html +213 -0
  15. package/skills/brainstorming/scripts/helper.js +167 -0
  16. package/skills/brainstorming/scripts/server.cjs +723 -0
  17. package/skills/brainstorming/scripts/start-server.sh +209 -0
  18. package/skills/brainstorming/scripts/stop-server.sh +120 -0
  19. package/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  20. package/skills/brainstorming/visual-companion.md +298 -0
  21. package/skills/brand-guidelines/LICENSE.txt +202 -0
  22. package/skills/brand-guidelines/SKILL.md +73 -0
  23. package/skills/canvas-design/LICENSE.txt +202 -0
  24. package/skills/canvas-design/SKILL.md +130 -0
  25. package/skills/canvas-design/canvas-fonts/ArsenalSC-OFL.txt +93 -0
  26. package/skills/canvas-design/canvas-fonts/ArsenalSC-Regular.ttf +0 -0
  27. package/skills/canvas-design/canvas-fonts/BigShoulders-Bold.ttf +0 -0
  28. package/skills/canvas-design/canvas-fonts/BigShoulders-OFL.txt +93 -0
  29. package/skills/canvas-design/canvas-fonts/BigShoulders-Regular.ttf +0 -0
  30. package/skills/canvas-design/canvas-fonts/Boldonse-OFL.txt +93 -0
  31. package/skills/canvas-design/canvas-fonts/Boldonse-Regular.ttf +0 -0
  32. package/skills/canvas-design/canvas-fonts/BricolageGrotesque-Bold.ttf +0 -0
  33. package/skills/canvas-design/canvas-fonts/BricolageGrotesque-OFL.txt +93 -0
  34. package/skills/canvas-design/canvas-fonts/BricolageGrotesque-Regular.ttf +0 -0
  35. package/skills/canvas-design/canvas-fonts/CrimsonPro-Bold.ttf +0 -0
  36. package/skills/canvas-design/canvas-fonts/CrimsonPro-Italic.ttf +0 -0
  37. package/skills/canvas-design/canvas-fonts/CrimsonPro-OFL.txt +93 -0
  38. package/skills/canvas-design/canvas-fonts/CrimsonPro-Regular.ttf +0 -0
  39. package/skills/canvas-design/canvas-fonts/DMMono-OFL.txt +93 -0
  40. package/skills/canvas-design/canvas-fonts/DMMono-Regular.ttf +0 -0
  41. package/skills/canvas-design/canvas-fonts/EricaOne-OFL.txt +94 -0
  42. package/skills/canvas-design/canvas-fonts/EricaOne-Regular.ttf +0 -0
  43. package/skills/canvas-design/canvas-fonts/GeistMono-Bold.ttf +0 -0
  44. package/skills/canvas-design/canvas-fonts/GeistMono-OFL.txt +93 -0
  45. package/skills/canvas-design/canvas-fonts/GeistMono-Regular.ttf +0 -0
  46. package/skills/canvas-design/canvas-fonts/Gloock-OFL.txt +93 -0
  47. package/skills/canvas-design/canvas-fonts/Gloock-Regular.ttf +0 -0
  48. package/skills/canvas-design/canvas-fonts/IBMPlexMono-Bold.ttf +0 -0
  49. package/skills/canvas-design/canvas-fonts/IBMPlexMono-OFL.txt +93 -0
  50. package/skills/canvas-design/canvas-fonts/IBMPlexMono-Regular.ttf +0 -0
  51. package/skills/canvas-design/canvas-fonts/IBMPlexSerif-Bold.ttf +0 -0
  52. package/skills/canvas-design/canvas-fonts/IBMPlexSerif-BoldItalic.ttf +0 -0
  53. package/skills/canvas-design/canvas-fonts/IBMPlexSerif-Italic.ttf +0 -0
  54. package/skills/canvas-design/canvas-fonts/IBMPlexSerif-Regular.ttf +0 -0
  55. package/skills/canvas-design/canvas-fonts/InstrumentSans-Bold.ttf +0 -0
  56. package/skills/canvas-design/canvas-fonts/InstrumentSans-BoldItalic.ttf +0 -0
  57. package/skills/canvas-design/canvas-fonts/InstrumentSans-Italic.ttf +0 -0
  58. package/skills/canvas-design/canvas-fonts/InstrumentSans-OFL.txt +93 -0
  59. package/skills/canvas-design/canvas-fonts/InstrumentSans-Regular.ttf +0 -0
  60. package/skills/canvas-design/canvas-fonts/InstrumentSerif-Italic.ttf +0 -0
  61. package/skills/canvas-design/canvas-fonts/InstrumentSerif-Regular.ttf +0 -0
  62. package/skills/canvas-design/canvas-fonts/Italiana-OFL.txt +93 -0
  63. package/skills/canvas-design/canvas-fonts/Italiana-Regular.ttf +0 -0
  64. package/skills/canvas-design/canvas-fonts/JetBrainsMono-Bold.ttf +0 -0
  65. package/skills/canvas-design/canvas-fonts/JetBrainsMono-OFL.txt +93 -0
  66. package/skills/canvas-design/canvas-fonts/JetBrainsMono-Regular.ttf +0 -0
  67. package/skills/canvas-design/canvas-fonts/Jura-Light.ttf +0 -0
  68. package/skills/canvas-design/canvas-fonts/Jura-Medium.ttf +0 -0
  69. package/skills/canvas-design/canvas-fonts/Jura-OFL.txt +93 -0
  70. package/skills/canvas-design/canvas-fonts/LibreBaskerville-OFL.txt +93 -0
  71. package/skills/canvas-design/canvas-fonts/LibreBaskerville-Regular.ttf +0 -0
  72. package/skills/canvas-design/canvas-fonts/Lora-Bold.ttf +0 -0
  73. package/skills/canvas-design/canvas-fonts/Lora-BoldItalic.ttf +0 -0
  74. package/skills/canvas-design/canvas-fonts/Lora-Italic.ttf +0 -0
  75. package/skills/canvas-design/canvas-fonts/Lora-OFL.txt +93 -0
  76. package/skills/canvas-design/canvas-fonts/Lora-Regular.ttf +0 -0
  77. package/skills/canvas-design/canvas-fonts/NationalPark-Bold.ttf +0 -0
  78. package/skills/canvas-design/canvas-fonts/NationalPark-OFL.txt +93 -0
  79. package/skills/canvas-design/canvas-fonts/NationalPark-Regular.ttf +0 -0
  80. package/skills/canvas-design/canvas-fonts/NothingYouCouldDo-OFL.txt +93 -0
  81. package/skills/canvas-design/canvas-fonts/NothingYouCouldDo-Regular.ttf +0 -0
  82. package/skills/canvas-design/canvas-fonts/Outfit-Bold.ttf +0 -0
  83. package/skills/canvas-design/canvas-fonts/Outfit-OFL.txt +93 -0
  84. package/skills/canvas-design/canvas-fonts/Outfit-Regular.ttf +0 -0
  85. package/skills/canvas-design/canvas-fonts/PixelifySans-Medium.ttf +0 -0
  86. package/skills/canvas-design/canvas-fonts/PixelifySans-OFL.txt +93 -0
  87. package/skills/canvas-design/canvas-fonts/PoiretOne-OFL.txt +93 -0
  88. package/skills/canvas-design/canvas-fonts/PoiretOne-Regular.ttf +0 -0
  89. package/skills/canvas-design/canvas-fonts/RedHatMono-Bold.ttf +0 -0
  90. package/skills/canvas-design/canvas-fonts/RedHatMono-OFL.txt +93 -0
  91. package/skills/canvas-design/canvas-fonts/RedHatMono-Regular.ttf +0 -0
  92. package/skills/canvas-design/canvas-fonts/Silkscreen-OFL.txt +93 -0
  93. package/skills/canvas-design/canvas-fonts/Silkscreen-Regular.ttf +0 -0
  94. package/skills/canvas-design/canvas-fonts/SmoochSans-Medium.ttf +0 -0
  95. package/skills/canvas-design/canvas-fonts/SmoochSans-OFL.txt +93 -0
  96. package/skills/canvas-design/canvas-fonts/Tektur-Medium.ttf +0 -0
  97. package/skills/canvas-design/canvas-fonts/Tektur-OFL.txt +93 -0
  98. package/skills/canvas-design/canvas-fonts/Tektur-Regular.ttf +0 -0
  99. package/skills/canvas-design/canvas-fonts/WorkSans-Bold.ttf +0 -0
  100. package/skills/canvas-design/canvas-fonts/WorkSans-BoldItalic.ttf +0 -0
  101. package/skills/canvas-design/canvas-fonts/WorkSans-Italic.ttf +0 -0
  102. package/skills/canvas-design/canvas-fonts/WorkSans-OFL.txt +93 -0
  103. package/skills/canvas-design/canvas-fonts/WorkSans-Regular.ttf +0 -0
  104. package/skills/canvas-design/canvas-fonts/YoungSerif-OFL.txt +93 -0
  105. package/skills/canvas-design/canvas-fonts/YoungSerif-Regular.ttf +0 -0
  106. package/skills/changelog-generator/SKILL.md +110 -0
  107. package/skills/claude-api/LICENSE.txt +202 -0
  108. package/skills/claude-api/SKILL.md +546 -0
  109. package/skills/claude-api/csharp/claude-api/README.md +362 -0
  110. package/skills/claude-api/csharp/claude-api/batches.md +14 -0
  111. package/skills/claude-api/csharp/claude-api/files-api.md +23 -0
  112. package/skills/claude-api/csharp/claude-api/streaming.md +28 -0
  113. package/skills/claude-api/csharp/claude-api/tool-use.md +164 -0
  114. package/skills/claude-api/curl/examples.md +256 -0
  115. package/skills/claude-api/curl/managed-agents.md +340 -0
  116. package/skills/claude-api/go/claude-api/README.md +188 -0
  117. package/skills/claude-api/go/claude-api/files-api.md +21 -0
  118. package/skills/claude-api/go/claude-api/streaming.md +43 -0
  119. package/skills/claude-api/go/claude-api/tool-use.md +220 -0
  120. package/skills/claude-api/go/managed-agents/README.md +564 -0
  121. package/skills/claude-api/java/claude-api/README.md +238 -0
  122. package/skills/claude-api/java/claude-api/files-api.md +25 -0
  123. package/skills/claude-api/java/claude-api/streaming.md +24 -0
  124. package/skills/claude-api/java/claude-api/tool-use.md +228 -0
  125. package/skills/claude-api/java/managed-agents/README.md +443 -0
  126. package/skills/claude-api/php/claude-api/README.md +174 -0
  127. package/skills/claude-api/php/claude-api/batches.md +15 -0
  128. package/skills/claude-api/php/claude-api/files-api.md +11 -0
  129. package/skills/claude-api/php/claude-api/streaming.md +27 -0
  130. package/skills/claude-api/php/claude-api/tool-use.md +253 -0
  131. package/skills/claude-api/php/managed-agents/README.md +443 -0
  132. package/skills/claude-api/python/claude-api/README.md +565 -0
  133. package/skills/claude-api/python/claude-api/batches.md +198 -0
  134. package/skills/claude-api/python/claude-api/files-api.md +170 -0
  135. package/skills/claude-api/python/claude-api/streaming.md +179 -0
  136. package/skills/claude-api/python/claude-api/tool-use.md +629 -0
  137. package/skills/claude-api/python/managed-agents/README.md +335 -0
  138. package/skills/claude-api/ruby/claude-api/README.md +134 -0
  139. package/skills/claude-api/ruby/claude-api/streaming.md +16 -0
  140. package/skills/claude-api/ruby/claude-api/tool-use.md +41 -0
  141. package/skills/claude-api/ruby/managed-agents/README.md +394 -0
  142. package/skills/claude-api/shared/agent-design.md +101 -0
  143. package/skills/claude-api/shared/anthropic-cli.md +246 -0
  144. package/skills/claude-api/shared/claude-platform-on-aws.md +59 -0
  145. package/skills/claude-api/shared/error-codes.md +257 -0
  146. package/skills/claude-api/shared/live-sources.md +143 -0
  147. package/skills/claude-api/shared/managed-agents-api-reference.md +448 -0
  148. package/skills/claude-api/shared/managed-agents-client-patterns.md +217 -0
  149. package/skills/claude-api/shared/managed-agents-core.md +319 -0
  150. package/skills/claude-api/shared/managed-agents-environments.md +219 -0
  151. package/skills/claude-api/shared/managed-agents-events.md +270 -0
  152. package/skills/claude-api/shared/managed-agents-memory.md +199 -0
  153. package/skills/claude-api/shared/managed-agents-multiagent.md +125 -0
  154. package/skills/claude-api/shared/managed-agents-onboarding.md +82 -0
  155. package/skills/claude-api/shared/managed-agents-outcomes.md +108 -0
  156. package/skills/claude-api/shared/managed-agents-overview.md +71 -0
  157. package/skills/claude-api/shared/managed-agents-scheduled-deployments.md +146 -0
  158. package/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +174 -0
  159. package/skills/claude-api/shared/managed-agents-tools.md +371 -0
  160. package/skills/claude-api/shared/managed-agents-webhooks.md +143 -0
  161. package/skills/claude-api/shared/model-migration.md +1548 -0
  162. package/skills/claude-api/shared/models.md +138 -0
  163. package/skills/claude-api/shared/platform-availability.md +97 -0
  164. package/skills/claude-api/shared/prompt-caching.md +235 -0
  165. package/skills/claude-api/shared/token-counting.md +56 -0
  166. package/skills/claude-api/shared/tool-use-concepts.md +514 -0
  167. package/skills/claude-api/typescript/claude-api/README.md +405 -0
  168. package/skills/claude-api/typescript/claude-api/batches.md +106 -0
  169. package/skills/claude-api/typescript/claude-api/files-api.md +98 -0
  170. package/skills/claude-api/typescript/claude-api/streaming.md +178 -0
  171. package/skills/claude-api/typescript/claude-api/tool-use.md +589 -0
  172. package/skills/claude-api/typescript/managed-agents/README.md +358 -0
  173. package/skills/code-security-auditor/SKILL.md +164 -0
  174. package/skills/code-security-auditor/references/package_checks.md +59 -0
  175. package/skills/competitive-ads-extractor/SKILL.md +298 -0
  176. package/skills/content-research-writer/SKILL.md +538 -0
  177. package/skills/developer-growth-analysis/SKILL.md +322 -0
  178. package/skills/dispatching-parallel-agents/SKILL.md +167 -0
  179. package/skills/doc-coauthoring/SKILL.md +375 -0
  180. package/skills/docx/LICENSE.txt +30 -0
  181. package/skills/docx/SKILL.md +91 -0
  182. package/skills/docx/scripts/__init__.py +1 -0
  183. package/skills/docx/scripts/accept_changes.py +135 -0
  184. package/skills/docx/scripts/comment.py +368 -0
  185. package/skills/docx/scripts/merge_runs.py +310 -0
  186. package/skills/docx/scripts/office/helpers/__init__.py +111 -0
  187. package/skills/docx/scripts/office/helpers/pptx_chart.py +170 -0
  188. package/skills/docx/scripts/office/helpers/pptx_slide.py +60 -0
  189. package/skills/docx/scripts/office/helpers/pptx_theme.py +114 -0
  190. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  191. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  192. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  193. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  194. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  195. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  196. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  197. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  198. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  199. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  200. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  201. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  202. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  203. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  204. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  205. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  206. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  207. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  208. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  209. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  210. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  211. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  212. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  213. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  214. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  215. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  216. package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  217. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  218. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  219. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  220. package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  221. package/skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
  222. package/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  223. package/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  224. package/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  225. package/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  226. package/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  227. package/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  228. package/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  229. package/skills/docx/scripts/office/soffice.py +192 -0
  230. package/skills/docx/scripts/office/validate.py +173 -0
  231. package/skills/docx/scripts/office/validators/__init__.py +15 -0
  232. package/skills/docx/scripts/office/validators/base.py +875 -0
  233. package/skills/docx/scripts/office/validators/docx.py +466 -0
  234. package/skills/docx/scripts/office/validators/pptx.py +441 -0
  235. package/skills/docx/scripts/office/validators/redlining.py +299 -0
  236. package/skills/docx/scripts/templates/comments.xml +3 -0
  237. package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  238. package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  239. package/skills/docx/scripts/templates/commentsIds.xml +3 -0
  240. package/skills/docx/scripts/templates/people.xml +3 -0
  241. package/skills/domain-name-brainstormer/SKILL.md +217 -0
  242. package/skills/executing-plans/SKILL.md +64 -0
  243. package/skills/file-organizer/SKILL.md +433 -0
  244. package/skills/finishing-a-development-branch/SKILL.md +201 -0
  245. package/skills/frontend-design/LICENSE.txt +177 -0
  246. package/skills/frontend-design/SKILL.md +55 -0
  247. package/skills/image-enhancer/SKILL.md +106 -0
  248. package/skills/internal-comms/LICENSE.txt +202 -0
  249. package/skills/internal-comms/SKILL.md +32 -0
  250. package/skills/internal-comms/examples/3p-updates.md +47 -0
  251. package/skills/internal-comms/examples/company-newsletter.md +65 -0
  252. package/skills/internal-comms/examples/faq-answers.md +30 -0
  253. package/skills/internal-comms/examples/general-comms.md +16 -0
  254. package/skills/invoice-organizer/SKILL.md +452 -0
  255. package/skills/lead-research-assistant/SKILL.md +205 -0
  256. package/skills/mcp-builder/LICENSE.txt +202 -0
  257. package/skills/mcp-builder/SKILL.md +236 -0
  258. package/skills/mcp-builder/reference/evaluation.md +602 -0
  259. package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
  260. package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
  261. package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
  262. package/skills/mcp-builder/scripts/connections.py +151 -0
  263. package/skills/mcp-builder/scripts/evaluation.py +373 -0
  264. package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
  265. package/skills/mcp-builder/scripts/requirements.txt +2 -0
  266. package/skills/meeting-insights-analyzer/SKILL.md +327 -0
  267. package/skills/pdf/LICENSE.txt +30 -0
  268. package/skills/pdf/SKILL.md +314 -0
  269. package/skills/pdf/forms.md +294 -0
  270. package/skills/pdf/reference.md +612 -0
  271. package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
  272. package/skills/pdf/scripts/check_fillable_fields.py +11 -0
  273. package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
  274. package/skills/pdf/scripts/create_validation_image.py +37 -0
  275. package/skills/pdf/scripts/extract_form_field_info.py +122 -0
  276. package/skills/pdf/scripts/extract_form_structure.py +115 -0
  277. package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
  278. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  279. package/skills/pptx/LICENSE.txt +30 -0
  280. package/skills/pptx/SKILL.md +238 -0
  281. package/skills/pptx/scripts/__init__.py +0 -0
  282. package/skills/pptx/scripts/add_slide.py +367 -0
  283. package/skills/pptx/scripts/clean.py +309 -0
  284. package/skills/pptx/scripts/office/helpers/__init__.py +111 -0
  285. package/skills/pptx/scripts/office/helpers/pptx_chart.py +170 -0
  286. package/skills/pptx/scripts/office/helpers/pptx_slide.py +60 -0
  287. package/skills/pptx/scripts/office/helpers/pptx_theme.py +114 -0
  288. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  289. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  290. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  291. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  292. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  293. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  294. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  295. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  296. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  297. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  298. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  299. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  300. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  301. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  302. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  303. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  304. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  305. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  306. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  307. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  308. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  309. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  310. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  311. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  312. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  313. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  314. package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  315. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  316. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  317. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  318. package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  319. package/skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
  320. package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  321. package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  322. package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  323. package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  324. package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  325. package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  326. package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  327. package/skills/pptx/scripts/office/soffice.py +192 -0
  328. package/skills/pptx/scripts/office/validate.py +173 -0
  329. package/skills/pptx/scripts/office/validators/__init__.py +15 -0
  330. package/skills/pptx/scripts/office/validators/base.py +875 -0
  331. package/skills/pptx/scripts/office/validators/docx.py +466 -0
  332. package/skills/pptx/scripts/office/validators/pptx.py +441 -0
  333. package/skills/pptx/scripts/office/validators/redlining.py +299 -0
  334. package/skills/pptx/scripts/thumbnail.py +311 -0
  335. package/skills/raffle-winner-picker/SKILL.md +165 -0
  336. package/skills/receiving-code-review/SKILL.md +205 -0
  337. package/skills/requesting-code-review/SKILL.md +95 -0
  338. package/skills/requesting-code-review/code-reviewer.md +172 -0
  339. package/skills/skill-creator/LICENSE.txt +202 -0
  340. package/skills/skill-creator/SKILL.md +485 -0
  341. package/skills/skill-creator/agents/analyzer.md +274 -0
  342. package/skills/skill-creator/agents/comparator.md +202 -0
  343. package/skills/skill-creator/agents/grader.md +223 -0
  344. package/skills/skill-creator/assets/eval_review.html +146 -0
  345. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  346. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  347. package/skills/skill-creator/references/schemas.md +430 -0
  348. package/skills/skill-creator/scripts/__init__.py +0 -0
  349. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  350. package/skills/skill-creator/scripts/generate_report.py +326 -0
  351. package/skills/skill-creator/scripts/improve_description.py +247 -0
  352. package/skills/skill-creator/scripts/package_skill.py +136 -0
  353. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  354. package/skills/skill-creator/scripts/run_eval.py +310 -0
  355. package/skills/skill-creator/scripts/run_loop.py +328 -0
  356. package/skills/skill-creator/scripts/utils.py +47 -0
  357. package/skills/skill-share/SKILL.md +80 -0
  358. package/skills/slack-gif-creator/LICENSE.txt +202 -0
  359. package/skills/slack-gif-creator/SKILL.md +254 -0
  360. package/skills/slack-gif-creator/core/easing.py +234 -0
  361. package/skills/slack-gif-creator/core/frame_composer.py +176 -0
  362. package/skills/slack-gif-creator/core/gif_builder.py +269 -0
  363. package/skills/slack-gif-creator/core/validators.py +136 -0
  364. package/skills/slack-gif-creator/requirements.txt +4 -0
  365. package/skills/staff-engineer-review/SKILL.md +172 -0
  366. package/skills/subagent-driven-development/SKILL.md +503 -0
  367. package/skills/subagent-driven-development/implementer-prompt.md +142 -0
  368. package/skills/subagent-driven-development/re-review-prompt.md +106 -0
  369. package/skills/subagent-driven-development/scripts/review-package +46 -0
  370. package/skills/subagent-driven-development/scripts/sdd-workspace +40 -0
  371. package/skills/subagent-driven-development/scripts/task-brief +41 -0
  372. package/skills/subagent-driven-development/task-reviewer-prompt.md +185 -0
  373. package/skills/systematic-debugging/CREATION-LOG.md +119 -0
  374. package/skills/systematic-debugging/SKILL.md +283 -0
  375. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  376. package/skills/systematic-debugging/condition-based-waiting.md +115 -0
  377. package/skills/systematic-debugging/defense-in-depth.md +122 -0
  378. package/skills/systematic-debugging/find-polluter.sh +72 -0
  379. package/skills/systematic-debugging/root-cause-tracing.md +169 -0
  380. package/skills/systematic-debugging/test-academic.md +14 -0
  381. package/skills/systematic-debugging/test-pressure-1.md +58 -0
  382. package/skills/systematic-debugging/test-pressure-2.md +68 -0
  383. package/skills/systematic-debugging/test-pressure-3.md +69 -0
  384. package/skills/test-driven-development/SKILL.md +320 -0
  385. package/skills/test-driven-development/writing-good-tests.md +198 -0
  386. package/skills/theme-factory/LICENSE.txt +202 -0
  387. package/skills/theme-factory/SKILL.md +59 -0
  388. package/skills/theme-factory/theme-showcase.pdf +0 -0
  389. package/skills/theme-factory/themes/arctic-frost.md +19 -0
  390. package/skills/theme-factory/themes/botanical-garden.md +19 -0
  391. package/skills/theme-factory/themes/desert-rose.md +19 -0
  392. package/skills/theme-factory/themes/forest-canopy.md +19 -0
  393. package/skills/theme-factory/themes/golden-hour.md +19 -0
  394. package/skills/theme-factory/themes/midnight-galaxy.md +19 -0
  395. package/skills/theme-factory/themes/modern-minimalist.md +19 -0
  396. package/skills/theme-factory/themes/ocean-depths.md +19 -0
  397. package/skills/theme-factory/themes/sunset-boulevard.md +19 -0
  398. package/skills/theme-factory/themes/tech-innovation.md +19 -0
  399. package/skills/using-git-worktrees/SKILL.md +167 -0
  400. package/skills/using-superpowers/SKILL.md +62 -0
  401. package/skills/using-superpowers/references/antigravity-tools.md +23 -0
  402. package/skills/using-superpowers/references/codex-tools.md +39 -0
  403. package/skills/using-superpowers/references/gemini-tools.md +63 -0
  404. package/skills/using-superpowers/references/pi-tools.md +16 -0
  405. package/skills/verification-before-completion/SKILL.md +120 -0
  406. package/skills/video-downloader/SKILL.md +112 -0
  407. package/skills/web-artifacts-builder/LICENSE.txt +202 -0
  408. package/skills/web-artifacts-builder/SKILL.md +74 -0
  409. package/skills/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
  410. package/skills/web-artifacts-builder/scripts/init-artifact.sh +322 -0
  411. package/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
  412. package/skills/webapp-testing/LICENSE.txt +202 -0
  413. package/skills/webapp-testing/SKILL.md +96 -0
  414. package/skills/webapp-testing/examples/console_logging.py +35 -0
  415. package/skills/webapp-testing/examples/element_discovery.py +40 -0
  416. package/skills/webapp-testing/examples/static_html_automation.py +33 -0
  417. package/skills/webapp-testing/scripts/with_server.py +106 -0
  418. package/skills/writing-plans/SKILL.md +168 -0
  419. package/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  420. package/skills/writing-skills/SKILL.md +679 -0
  421. package/skills/writing-skills/anthropic-best-practices.md +1150 -0
  422. package/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  423. package/skills/writing-skills/graphviz-conventions.dot +172 -0
  424. package/skills/writing-skills/persuasion-principles.md +187 -0
  425. package/skills/writing-skills/render-graphs.js +168 -0
  426. package/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  427. package/skills/xlsx/LICENSE.txt +30 -0
  428. package/skills/xlsx/SKILL.md +99 -0
  429. package/skills/xlsx/scripts/office/helpers/__init__.py +111 -0
  430. package/skills/xlsx/scripts/office/helpers/pptx_chart.py +170 -0
  431. package/skills/xlsx/scripts/office/helpers/pptx_slide.py +60 -0
  432. package/skills/xlsx/scripts/office/helpers/pptx_theme.py +114 -0
  433. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  434. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  435. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  436. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  437. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  438. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  439. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  440. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  441. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  442. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  443. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  444. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  445. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  446. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  447. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  448. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  449. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  450. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  451. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  452. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  453. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  454. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  455. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  456. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  457. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  458. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  459. package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  460. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  461. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  462. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  463. package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  464. package/skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
  465. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  466. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  467. package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  468. package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  469. package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  470. package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  471. package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  472. package/skills/xlsx/scripts/office/soffice.py +192 -0
  473. package/skills/xlsx/scripts/office/validate.py +173 -0
  474. package/skills/xlsx/scripts/office/validators/__init__.py +15 -0
  475. package/skills/xlsx/scripts/office/validators/base.py +875 -0
  476. package/skills/xlsx/scripts/office/validators/docx.py +466 -0
  477. package/skills/xlsx/scripts/office/validators/pptx.py +441 -0
  478. package/skills/xlsx/scripts/office/validators/redlining.py +299 -0
  479. package/skills/xlsx/scripts/recalc.py +308 -0
  480. package/src/account/account.ts +1 -2
  481. package/src/account/repo.ts +2 -1
  482. package/src/acp/content.ts +1 -1
  483. package/src/acp/directory.ts +2 -2
  484. package/src/acp/event.ts +1 -1
  485. package/src/acp/session.ts +2 -2
  486. package/src/agent/agent.ts +62 -31
  487. package/src/agent/subagent-permissions.ts +1 -1
  488. package/src/cli/cmd/account.ts +2 -1
  489. package/src/cli/cmd/debug/agent.handler.ts +1 -1
  490. package/src/cli/cmd/export.ts +1 -1
  491. package/src/cli/cmd/github.handler.ts +4 -2
  492. package/src/cli/cmd/mcp.ts +2 -2
  493. package/src/cli/cmd/run/footer.prompt.tsx +7 -2
  494. package/src/cli/cmd/stats.ts +1 -1
  495. package/src/cli/network.ts +1 -1
  496. package/src/config/config.ts +3 -5
  497. package/src/config/plugin.ts +1 -1
  498. package/src/control-plane/workspace.ts +3 -2
  499. package/src/effect/app-node-builder-v1.ts +1 -1
  500. package/src/effect/instance-state.ts +2 -1
  501. package/src/effect/runner.ts +2 -1
  502. package/src/effect/runtime-flags.ts +2 -1
  503. package/src/image/image.ts +1 -1
  504. package/src/installation/index.ts +4 -8
  505. package/src/lsp/diagnostic.ts +1 -1
  506. package/src/mcp/catalog.ts +1 -1
  507. package/src/mcp/index.ts +2 -2
  508. package/src/mcp/oauth-provider.ts +1 -1
  509. package/src/permission/index.ts +7 -3
  510. package/src/plugin/index.ts +7 -3
  511. package/src/plugin/loader.ts +1 -1
  512. package/src/plugin/tui/runtime.ts +1 -1
  513. package/src/project/bootstrap-service.ts +2 -1
  514. package/src/project/project.ts +3 -2
  515. package/src/project/vcs.ts +1 -1
  516. package/src/provider/error.ts +1 -1
  517. package/src/provider/provider.ts +3 -2
  518. package/src/question/index.ts +1 -1
  519. package/src/server/auth.ts +2 -1
  520. package/src/server/routes/instance/httpapi/groups/metadata.ts +1 -1
  521. package/src/server/routes/instance/httpapi/handlers/control-plane.ts +2 -1
  522. package/src/server/routes/instance/httpapi/handlers/control.ts +2 -2
  523. package/src/server/routes/instance/httpapi/handlers/experimental.ts +2 -1
  524. package/src/server/routes/instance/httpapi/handlers/global.ts +2 -1
  525. package/src/server/routes/instance/httpapi/handlers/mcp.ts +2 -1
  526. package/src/server/routes/instance/httpapi/handlers/permission.ts +1 -1
  527. package/src/server/routes/instance/httpapi/handlers/project-copy.ts +1 -1
  528. package/src/server/routes/instance/httpapi/handlers/provider.ts +3 -2
  529. package/src/server/routes/instance/httpapi/handlers/pty.ts +1 -1
  530. package/src/server/routes/instance/httpapi/handlers/question.ts +1 -1
  531. package/src/server/routes/instance/httpapi/handlers/session.ts +5 -5
  532. package/src/server/routes/instance/httpapi/handlers/sync.ts +2 -2
  533. package/src/server/routes/instance/httpapi/handlers/tui.ts +1 -1
  534. package/src/server/routes/instance/httpapi/handlers/workspace.ts +2 -1
  535. package/src/server/routes/instance/httpapi/lifecycle.ts +2 -1
  536. package/src/server/routes/instance/httpapi/middleware/instance-context.ts +1 -1
  537. package/src/server/routes/instance/httpapi/middleware/proxy.ts +2 -1
  538. package/src/server/routes/instance/httpapi/server.ts +2 -1
  539. package/src/server/shared/fence.ts +1 -1
  540. package/src/server/shared/ui.ts +2 -1
  541. package/src/session/compaction.ts +5 -4
  542. package/src/session/instruction.ts +1 -1
  543. package/src/session/llm/native-runtime.ts +2 -1
  544. package/src/session/llm/request.ts +2 -2
  545. package/src/session/llm.ts +2 -2
  546. package/src/session/message-v2.ts +5 -16
  547. package/src/session/overflow.ts +2 -2
  548. package/src/session/prompt.ts +4 -3
  549. package/src/session/reminders.ts +2 -2
  550. package/src/session/revert.ts +1 -1
  551. package/src/session/run-state.ts +4 -3
  552. package/src/session/session.ts +2 -1
  553. package/src/session/status.ts +1 -1
  554. package/src/session/summary.ts +1 -1
  555. package/src/session/system.ts +1 -1
  556. package/src/session/todo.ts +1 -1
  557. package/src/session/tools.ts +6 -6
  558. package/src/share/session.ts +1 -1
  559. package/src/share/share-next.ts +1 -1
  560. package/src/tool/computer.ts +195 -136
  561. package/src/tool/edit.ts +1 -1
  562. package/src/tool/mcp-websearch.ts +2 -1
  563. package/src/tool/plan.ts +1 -1
  564. package/src/tool/registry.ts +2 -2
  565. package/src/tool/task.ts +1 -1
  566. package/src/tool/tool.ts +2 -2
  567. package/src/util/bom.ts +1 -1
@@ -0,0 +1,368 @@
1
+ """Add comments to a DOCX document.
2
+
3
+ Accepts either an unpacked directory OR a .docx/.dotx file directly.
4
+
5
+ Usage:
6
+ # Against an unpacked directory (writes satellite files in place)
7
+ python comment.py unpacked/ "Comment text"
8
+ python comment.py unpacked/ "Reply text" --parent 0
9
+
10
+ # Against a .docx directly (extracts, writes satellite files, rezips)
11
+ python comment.py contract.docx "This cap is too low" -o annotated.docx
12
+ python comment.py contract.docx "Comment" --id 5 # explicit ID
13
+
14
+ The comment ID is auto-assigned (max existing + 1) unless --id is given.
15
+ Plain text is XML-escaped automatically; if you pass already-escaped text
16
+ (e.g. &, ’) use --raw to skip escaping.
17
+
18
+ After running, add markers to word/document.xml so the comment is visible:
19
+ <w:commentRangeStart w:id="N"/>
20
+ ... commented content ...
21
+ <w:commentRangeEnd w:id="N"/>
22
+ <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="N"/></w:r>
23
+ """
24
+
25
+ import argparse
26
+ import random
27
+ import shutil
28
+ import sys
29
+ import tempfile
30
+ import zipfile
31
+ from datetime import datetime, timezone
32
+ from pathlib import Path
33
+
34
+ import defusedxml.minidom
35
+ from xml.parsers.expat import ExpatError
36
+ from xml.sax.saxutils import escape as xml_escape
37
+
38
+ from office.helpers import opc_target, rezip as _rezip, safe_extract as _safe_extract
39
+
40
+ TEMPLATE_DIR = Path(__file__).parent / "templates"
41
+ NS = {
42
+ "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
43
+ "w14": "http://schemas.microsoft.com/office/word/2010/wordml",
44
+ "w15": "http://schemas.microsoft.com/office/word/2012/wordml",
45
+ "w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid",
46
+ "w16cex": "http://schemas.microsoft.com/office/word/2018/wordml/cex",
47
+ }
48
+
49
+ COMMENT_XML = """\
50
+ <w:comment w:id="{id}" w:author="{author}" w:date="{date}" w:initials="{initials}">
51
+ <w:p w14:paraId="{para_id}" w14:textId="77777777">
52
+ <w:r>
53
+ <w:rPr><w:rStyle w:val="CommentReference"/></w:rPr>
54
+ <w:annotationRef/>
55
+ </w:r>
56
+ <w:r>
57
+ <w:rPr>
58
+ <w:color w:val="000000"/>
59
+ <w:sz w:val="20"/>
60
+ <w:szCs w:val="20"/>
61
+ </w:rPr>
62
+ <w:t xml:space="preserve">{text}</w:t>
63
+ </w:r>
64
+ </w:p>
65
+ </w:comment>"""
66
+
67
+ COMMENT_MARKER_TEMPLATE = """
68
+ Add to word/document.xml (markers must be direct children of w:p, never inside w:r):
69
+ <w:commentRangeStart w:id="{cid}"/>
70
+ <w:r>...</w:r>
71
+ <w:commentRangeEnd w:id="{cid}"/>
72
+ <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="{cid}"/></w:r>"""
73
+
74
+ REPLY_MARKER_TEMPLATE = """
75
+ Nest markers inside parent {pid}'s markers (direct children of w:p, never inside w:r):
76
+ <w:commentRangeStart w:id="{pid}"/><w:commentRangeStart w:id="{cid}"/>
77
+ <w:r>...</w:r>
78
+ <w:commentRangeEnd w:id="{cid}"/><w:commentRangeEnd w:id="{pid}"/>
79
+ <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="{pid}"/></w:r>
80
+ <w:r><w:rPr><w:rStyle w:val="CommentReference"/></w:rPr><w:commentReference w:id="{cid}"/></w:r>"""
81
+
82
+ SMART_QUOTE_ENTITIES = {
83
+ "“": "&#x201C;",
84
+ "”": "&#x201D;",
85
+ "‘": "&#x2018;",
86
+ "’": "&#x2019;",
87
+ }
88
+
89
+
90
+ def _generate_hex_id() -> str:
91
+ return f"{random.randint(0, 0x7FFFFFFE):08X}"
92
+
93
+
94
+ def _encode_smart_quotes(text: str) -> str:
95
+ for char, entity in SMART_QUOTE_ENTITIES.items():
96
+ text = text.replace(char, entity)
97
+ return text
98
+
99
+
100
+ def _append_xml(xml_path: Path, root_tag: str, content: str) -> None:
101
+ dom = defusedxml.minidom.parseString(xml_path.read_text(encoding="utf-8"))
102
+ root = dom.getElementsByTagName(root_tag)[0]
103
+ ns_attrs = " ".join(f'xmlns:{k}="{v}"' for k, v in NS.items())
104
+ wrapper_dom = defusedxml.minidom.parseString(f"<root {ns_attrs}>{content}</root>")
105
+ for child in wrapper_dom.documentElement.childNodes:
106
+ if child.nodeType == child.ELEMENT_NODE:
107
+ root.appendChild(dom.importNode(child, True))
108
+ output = _encode_smart_quotes(dom.toxml(encoding="UTF-8").decode("utf-8"))
109
+ xml_path.write_text(output, encoding="utf-8")
110
+
111
+
112
+ def _find_para_id(comments_path: Path, comment_id: int) -> str | None:
113
+ dom = defusedxml.minidom.parseString(comments_path.read_text(encoding="utf-8"))
114
+ for c in dom.getElementsByTagName("w:comment"):
115
+ if c.getAttribute("w:id") == str(comment_id):
116
+ for p in c.getElementsByTagName("w:p"):
117
+ if pid := p.getAttribute("w14:paraId"):
118
+ return pid
119
+ return None
120
+
121
+
122
+ def _next_comment_id(comments_path: Path) -> int:
123
+ if not comments_path.exists():
124
+ return 0
125
+ dom = defusedxml.minidom.parseString(comments_path.read_text(encoding="utf-8"))
126
+ ids = []
127
+ for c in dom.getElementsByTagName("w:comment"):
128
+ try:
129
+ ids.append(int(c.getAttribute("w:id")))
130
+ except ValueError:
131
+ pass
132
+ return (max(ids) + 1) if ids else 0
133
+
134
+
135
+ def _get_next_rid(rels_path: Path) -> int:
136
+ dom = defusedxml.minidom.parseString(rels_path.read_text(encoding="utf-8"))
137
+ max_rid = 0
138
+ for rel in dom.getElementsByTagName("Relationship"):
139
+ rid = rel.getAttribute("Id")
140
+ if rid and rid.startswith("rId"):
141
+ try:
142
+ max_rid = max(max_rid, int(rid[3:]))
143
+ except ValueError:
144
+ pass
145
+ return max_rid + 1
146
+
147
+
148
+ def _has_relationship(rels_path: Path, target: str) -> bool:
149
+ dom = defusedxml.minidom.parseString(rels_path.read_text(encoding="utf-8"))
150
+ return any(
151
+ rel.getAttribute("Target") == target
152
+ for rel in dom.getElementsByTagName("Relationship")
153
+ )
154
+
155
+
156
+ def _has_content_type(ct_path: Path, part_name: str) -> bool:
157
+ dom = defusedxml.minidom.parseString(ct_path.read_text(encoding="utf-8"))
158
+ return any(
159
+ o.getAttribute("PartName") == part_name
160
+ for o in dom.getElementsByTagName("Override")
161
+ )
162
+
163
+
164
+ _COMMENT_RELS = [
165
+ ("http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments", "comments.xml"),
166
+ ("http://schemas.microsoft.com/office/2011/relationships/commentsExtended", "commentsExtended.xml"),
167
+ ("http://schemas.microsoft.com/office/2016/09/relationships/commentsIds", "commentsIds.xml"),
168
+ ("http://schemas.microsoft.com/office/2018/08/relationships/commentsExtensible", "commentsExtensible.xml"),
169
+ ]
170
+ _COMMENT_OVERRIDES = [
171
+ ("/word/comments.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"),
172
+ ("/word/commentsExtended.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml"),
173
+ ("/word/commentsIds.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsIds+xml"),
174
+ ("/word/commentsExtensible.xml", "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtensible+xml"),
175
+ ]
176
+
177
+
178
+ def _ensure_comment_relationships(unpacked_dir: Path) -> None:
179
+ rels_path = unpacked_dir / "word" / "_rels" / "document.xml.rels"
180
+ if not rels_path.exists():
181
+ return
182
+ dom = defusedxml.minidom.parseString(rels_path.read_text(encoding="utf-8"))
183
+ root = dom.documentElement
184
+ comment_types = {rel_type for rel_type, _ in _COMMENT_RELS}
185
+ existing = set()
186
+ for rel in dom.getElementsByTagName("Relationship"):
187
+ if rel.getAttribute("Type") not in comment_types:
188
+ continue
189
+ part = opc_target(
190
+ rel.getAttribute("Target"),
191
+ "word/document.xml",
192
+ rel.getAttribute("TargetMode"),
193
+ )
194
+ if part is not None:
195
+ existing.add(part)
196
+ next_rid = _get_next_rid(rels_path)
197
+ changed = False
198
+ for rel_type, target in _COMMENT_RELS:
199
+ if opc_target(target, "word/document.xml") in existing:
200
+ continue
201
+ rel = dom.createElement("Relationship")
202
+ rel.setAttribute("Id", f"rId{next_rid}")
203
+ rel.setAttribute("Type", rel_type)
204
+ rel.setAttribute("Target", target)
205
+ root.appendChild(rel)
206
+ next_rid += 1
207
+ changed = True
208
+ if changed:
209
+ rels_path.write_bytes(dom.toxml(encoding="UTF-8"))
210
+
211
+
212
+ def _ensure_comment_content_types(unpacked_dir: Path) -> None:
213
+ ct_path = unpacked_dir / "[Content_Types].xml"
214
+ if not ct_path.exists():
215
+ return
216
+ dom = defusedxml.minidom.parseString(ct_path.read_text(encoding="utf-8"))
217
+ root = dom.documentElement
218
+ existing = {
219
+ o.getAttribute("PartName")
220
+ for o in dom.getElementsByTagName("Override")
221
+ }
222
+ changed = False
223
+ for part_name, content_type in _COMMENT_OVERRIDES:
224
+ if part_name in existing:
225
+ continue
226
+ override = dom.createElement("Override")
227
+ override.setAttribute("PartName", part_name)
228
+ override.setAttribute("ContentType", content_type)
229
+ root.appendChild(override)
230
+ changed = True
231
+ if changed:
232
+ ct_path.write_bytes(dom.toxml(encoding="UTF-8"))
233
+
234
+
235
+ def add_comment(
236
+ unpacked_dir: Path | str,
237
+ text: str,
238
+ comment_id: int | None = None,
239
+ author: str = "Claude",
240
+ initials: str = "C",
241
+ parent_id: int | None = None,
242
+ raw: bool = False,
243
+ ) -> tuple[int, str, str]:
244
+ unpacked_dir = Path(unpacked_dir)
245
+ if not raw:
246
+ text = xml_escape(text)
247
+ author = xml_escape(author, {'"': "&quot;"})
248
+ initials = xml_escape(initials, {'"': "&quot;"})
249
+ word = unpacked_dir / "word"
250
+ if not word.exists():
251
+ raise FileNotFoundError(f"{word} not found (not an unpacked .docx?)")
252
+
253
+ comments = word / "comments.xml"
254
+ if comment_id is None:
255
+ comment_id = _next_comment_id(comments)
256
+
257
+ parent_para = None
258
+ if parent_id is not None:
259
+ parent_para = _find_para_id(comments, parent_id) if comments.exists() else None
260
+ if not parent_para:
261
+ raise ValueError(f"parent comment {parent_id} not found")
262
+
263
+ para_id, durable_id = _generate_hex_id(), _generate_hex_id()
264
+ ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
265
+
266
+ if not comments.exists():
267
+ shutil.copy(TEMPLATE_DIR / "comments.xml", comments)
268
+ _ensure_comment_relationships(unpacked_dir)
269
+ _ensure_comment_content_types(unpacked_dir)
270
+ _append_xml(
271
+ comments,
272
+ "w:comments",
273
+ COMMENT_XML.format(
274
+ id=comment_id, author=author, date=ts, initials=initials,
275
+ para_id=para_id, text=text,
276
+ ),
277
+ )
278
+
279
+ ext = word / "commentsExtended.xml"
280
+ if not ext.exists():
281
+ shutil.copy(TEMPLATE_DIR / "commentsExtended.xml", ext)
282
+ if parent_para is not None:
283
+ _append_xml(
284
+ ext, "w15:commentsEx",
285
+ f'<w15:commentEx w15:paraId="{para_id}" w15:paraIdParent="{parent_para}" w15:done="0"/>',
286
+ )
287
+ else:
288
+ _append_xml(
289
+ ext, "w15:commentsEx",
290
+ f'<w15:commentEx w15:paraId="{para_id}" w15:done="0"/>',
291
+ )
292
+
293
+ ids = word / "commentsIds.xml"
294
+ if not ids.exists():
295
+ shutil.copy(TEMPLATE_DIR / "commentsIds.xml", ids)
296
+ _append_xml(
297
+ ids, "w16cid:commentsIds",
298
+ f'<w16cid:commentId w16cid:paraId="{para_id}" w16cid:durableId="{durable_id}"/>',
299
+ )
300
+
301
+ extensible = word / "commentsExtensible.xml"
302
+ if not extensible.exists():
303
+ shutil.copy(TEMPLATE_DIR / "commentsExtensible.xml", extensible)
304
+ _append_xml(
305
+ extensible, "w16cex:commentsExtensible",
306
+ f'<w16cex:commentExtensible w16cex:durableId="{durable_id}" w16cex:dateUtc="{ts}"/>',
307
+ )
308
+
309
+ action = "reply" if parent_id is not None else "comment"
310
+ return comment_id, para_id, f"Added {action} id={comment_id} (paraId={para_id})"
311
+
312
+
313
+ def main() -> None:
314
+ p = argparse.ArgumentParser(description="Add a comment to a DOCX (directory or .docx file).")
315
+ p.add_argument("input", help="Unpacked DOCX directory OR a .docx/.dotx file")
316
+ p.add_argument("text", help="Comment text (plain text; XML-escaped automatically)")
317
+ p.add_argument("--raw", action="store_true",
318
+ help="Treat text as pre-escaped XML (skip automatic escaping)")
319
+ p.add_argument("--id", type=int, dest="comment_id",
320
+ help="Comment ID (default: auto-assign as max existing + 1)")
321
+ p.add_argument("--author", default="Claude", help="Author name")
322
+ p.add_argument("--initials", default="C", help="Author initials")
323
+ p.add_argument("--parent", type=int, help="Parent comment ID (makes this a reply)")
324
+ p.add_argument("-o", "--output",
325
+ help="Output .docx path (only used when input is a .docx; default: overwrite input)")
326
+ args = p.parse_args()
327
+
328
+ src = Path(args.input)
329
+
330
+ try:
331
+ if src.is_dir():
332
+ if args.output:
333
+ print("Warning: --output ignored for directory input", file=sys.stderr)
334
+ cid, _, msg = add_comment(
335
+ src, args.text, comment_id=args.comment_id,
336
+ author=args.author, initials=args.initials,
337
+ parent_id=args.parent, raw=args.raw,
338
+ )
339
+ print(msg)
340
+ elif src.is_file() and src.suffix.lower() in (".docx", ".dotx"):
341
+ out = Path(args.output) if args.output else src
342
+ with tempfile.TemporaryDirectory() as tmp:
343
+ tmp_path = Path(tmp)
344
+ with zipfile.ZipFile(src) as zf:
345
+ _safe_extract(zf, tmp_path)
346
+ cid, _, msg = add_comment(
347
+ tmp_path, args.text, comment_id=args.comment_id,
348
+ author=args.author, initials=args.initials,
349
+ parent_id=args.parent, raw=args.raw,
350
+ )
351
+ _rezip(tmp_path, out)
352
+ print(msg)
353
+ print(f"Wrote {out} (comment defined; add markers to word/document.xml to make it visible)")
354
+ else:
355
+ print(f"Error: {src} is neither a directory nor a .docx/.dotx file", file=sys.stderr)
356
+ sys.exit(1)
357
+ except (FileNotFoundError, ValueError, zipfile.BadZipFile, ExpatError) as e:
358
+ print(f"Error: {e}", file=sys.stderr)
359
+ sys.exit(1)
360
+
361
+ if args.parent is not None:
362
+ print(REPLY_MARKER_TEMPLATE.format(pid=args.parent, cid=cid))
363
+ else:
364
+ print(COMMENT_MARKER_TEMPLATE.format(cid=cid))
365
+
366
+
367
+ if __name__ == "__main__":
368
+ main()
@@ -0,0 +1,310 @@
1
+ """Merge adjacent identically-formatted runs in a DOCX.
2
+
3
+ Word fragments paragraph text across many <w:r> elements (revision ids,
4
+ spell-check markers, editing history), which makes find-and-replace on
5
+ word/document.xml unreliable — the string you're looking for is split
6
+ across runs. This coalesces adjacent runs whose formatting (<w:rPr>) is
7
+ identical, strips rsid attributes and proofErr markers, and consolidates the
8
+ text elements — <w:t>, and <w:delText> for text inside a tracked deletion.
9
+
10
+ Rendering is unchanged. The text you search is what Word draws, which is not
11
+ always the bytes in the file: an element without xml:space="preserve" has its
12
+ edge whitespace trimmed before it reaches the page, so `<w:t>Hello </w:t>`
13
+ followed by `<w:t>world</w:t>` reads "Helloworld" and merges to exactly that.
14
+
15
+ Runs in two different <w:ins>/<w:del> wrappers are never merged: that would
16
+ rewrite tracked-change structure, collapsing separate revisions into one.
17
+
18
+ Only word/document.xml is processed (not headers, footers, or footnotes).
19
+
20
+ Usage:
21
+ python merge_runs.py unpacked/ # after unzip, before editing
22
+ python merge_runs.py document.docx # rewrite in place
23
+ python merge_runs.py document.docx -o out.docx
24
+ """
25
+
26
+
27
+ import argparse
28
+ import sys
29
+ import tempfile
30
+ import zipfile
31
+ from pathlib import Path
32
+
33
+ import defusedxml.minidom
34
+
35
+ from office.helpers import XML_SPACE, rendered_text, rezip, safe_extract
36
+
37
+ WORDML_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
38
+
39
+
40
+ def merge_runs(input_dir: str) -> tuple[int, str]:
41
+ doc_xml = Path(input_dir) / "word" / "document.xml"
42
+
43
+ if not doc_xml.exists():
44
+ return 0, f"Error: {doc_xml} not found"
45
+
46
+ try:
47
+ dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8"))
48
+ root = dom.documentElement
49
+ run_names = _run_tag_names(root)
50
+
51
+ _remove_elements(root, "proofErr")
52
+
53
+ runs = _find_runs(root, run_names)
54
+ _strip_rsid_attrs(runs)
55
+
56
+ merge_count = 0
57
+ for container in {run.parentNode for run in runs}:
58
+ merge_count += _merge_runs_in(container, run_names)
59
+
60
+ doc_xml.write_bytes(dom.toxml(encoding="UTF-8"))
61
+ return merge_count, f"Merged {merge_count} runs"
62
+
63
+ except Exception as e:
64
+ return 0, f"Error: {e}"
65
+
66
+
67
+
68
+
69
+ def _is_element(node, tag: str) -> bool:
70
+ name = node.localName or node.tagName
71
+ return name == tag or name.endswith(f":{tag}")
72
+
73
+
74
+ def _run_tag_names(root) -> set[str]:
75
+ names = set()
76
+ for attr in root.attributes.values():
77
+ if attr.value == WORDML_NS:
78
+ if attr.name == "xmlns":
79
+ names.add("r")
80
+ elif attr.name.startswith("xmlns:"):
81
+ names.add(attr.name.split(":", 1)[1] + ":r")
82
+ return names or {"w:r", "r"}
83
+
84
+
85
+ def _find_elements(root, tag: str) -> list:
86
+ results = []
87
+
88
+ def traverse(node):
89
+ if node.nodeType == node.ELEMENT_NODE:
90
+ if _is_element(node, tag):
91
+ results.append(node)
92
+ for child in node.childNodes:
93
+ traverse(child)
94
+
95
+ traverse(root)
96
+ return results
97
+
98
+
99
+ def _find_runs(root, run_names: set[str]) -> list:
100
+ return [e for e in _find_elements(root, "r") if _is_run(e, run_names)]
101
+
102
+
103
+ def _get_child(parent, tag: str):
104
+ return next(iter(_get_children(parent, tag)), None)
105
+
106
+
107
+ def _get_children(parent, tag: str) -> list:
108
+ return [
109
+ child
110
+ for child in parent.childNodes
111
+ if child.nodeType == child.ELEMENT_NODE and _is_element(child, tag)
112
+ ]
113
+
114
+
115
+ def _is_adjacent(elem1, elem2) -> bool:
116
+ node = elem1.nextSibling
117
+ while node:
118
+ if node == elem2:
119
+ return True
120
+ if node.nodeType == node.ELEMENT_NODE:
121
+ return False
122
+ if node.nodeType == node.TEXT_NODE and node.data.strip(XML_SPACE):
123
+ return False
124
+ node = node.nextSibling
125
+ return False
126
+
127
+
128
+
129
+
130
+ def _remove_elements(root, tag: str):
131
+ for elem in _find_elements(root, tag):
132
+ if elem.parentNode:
133
+ elem.parentNode.removeChild(elem)
134
+
135
+
136
+ def _strip_rsid_attrs(runs: list):
137
+ for run in runs:
138
+ for attr in list(run.attributes.values()):
139
+ if "rsid" in attr.name.lower():
140
+ run.removeAttribute(attr.name)
141
+
142
+
143
+
144
+
145
+ def _merge_runs_in(container, run_names: set[str]) -> int:
146
+ merge_count = 0
147
+ run = _first_child_run(container, run_names)
148
+
149
+ while run:
150
+ while True:
151
+ next_elem = _next_element_sibling(run)
152
+ if next_elem and _is_run(next_elem, run_names) and _can_merge(run, next_elem):
153
+ _merge_run_content(run, next_elem)
154
+ container.removeChild(next_elem)
155
+ merge_count += 1
156
+ else:
157
+ break
158
+
159
+ _consolidate_text(run)
160
+ run = _next_sibling_run(run, run_names)
161
+
162
+ return merge_count
163
+
164
+
165
+ def _first_child_run(container, run_names: set[str]):
166
+ for child in container.childNodes:
167
+ if child.nodeType == child.ELEMENT_NODE and _is_run(child, run_names):
168
+ return child
169
+ return None
170
+
171
+
172
+ def _next_element_sibling(node):
173
+ sibling = node.nextSibling
174
+ while sibling:
175
+ if sibling.nodeType == sibling.ELEMENT_NODE:
176
+ return sibling
177
+ sibling = sibling.nextSibling
178
+ return None
179
+
180
+
181
+ def _next_sibling_run(node, run_names: set[str]):
182
+ sibling = node.nextSibling
183
+ while sibling:
184
+ if sibling.nodeType == sibling.ELEMENT_NODE:
185
+ if _is_run(sibling, run_names):
186
+ return sibling
187
+ sibling = sibling.nextSibling
188
+ return None
189
+
190
+
191
+ def _is_run(node, run_names: set[str]) -> bool:
192
+ return node.tagName in run_names
193
+
194
+
195
+ def _can_merge(run1, run2) -> bool:
196
+ rpr1 = _get_child(run1, "rPr")
197
+ rpr2 = _get_child(run2, "rPr")
198
+
199
+ if (rpr1 is None) != (rpr2 is None):
200
+ return False
201
+ if rpr1 is None:
202
+ return True
203
+ return rpr1.toxml() == rpr2.toxml()
204
+
205
+
206
+ def _merge_run_content(target, source):
207
+ for child in list(source.childNodes):
208
+ if child.nodeType == child.ELEMENT_NODE:
209
+ name = child.localName or child.tagName
210
+ if name != "rPr" and not name.endswith(":rPr"):
211
+ target.appendChild(child)
212
+
213
+
214
+ def _element_text(elem) -> str:
215
+ return "".join(
216
+ child.data
217
+ for child in elem.childNodes
218
+ if child.nodeType in (child.TEXT_NODE, child.CDATA_SECTION_NODE)
219
+ )
220
+
221
+
222
+ def _has_preserve(elem) -> bool:
223
+ return elem.getAttribute("xml:space") == "preserve"
224
+
225
+
226
+ def _rendered_text(elem) -> str:
227
+ return rendered_text(_element_text(elem), _has_preserve(elem))
228
+
229
+
230
+ def _consolidate_text(run):
231
+ for tag in ("t", "delText"):
232
+ _consolidate_text_elements(run, tag)
233
+
234
+
235
+ def _consolidate_text_elements(run, tag: str):
236
+ t_elements = _get_children(run, tag)
237
+
238
+ for i in range(len(t_elements) - 1, 0, -1):
239
+ curr, prev = t_elements[i], t_elements[i - 1]
240
+
241
+ if _is_adjacent(prev, curr):
242
+ merged = _rendered_text(prev) + _rendered_text(curr)
243
+ had_preserve = _has_preserve(prev) or _has_preserve(curr)
244
+
245
+ new_text = run.ownerDocument.createTextNode(merged)
246
+ for node in list(prev.childNodes):
247
+ if node.nodeType in (node.TEXT_NODE, node.CDATA_SECTION_NODE):
248
+ prev.removeChild(node)
249
+ else:
250
+ run.insertBefore(node, curr)
251
+ prev.appendChild(new_text)
252
+ for node in list(curr.childNodes):
253
+ if node.nodeType not in (node.TEXT_NODE, node.CDATA_SECTION_NODE):
254
+ run.insertBefore(node, curr)
255
+
256
+ if merged != merged.strip(XML_SPACE) or had_preserve:
257
+ prev.setAttribute("xml:space", "preserve")
258
+ elif prev.hasAttribute("xml:space"):
259
+ prev.removeAttribute("xml:space")
260
+
261
+ run.removeChild(curr)
262
+
263
+
264
+
265
+
266
+ def _merge_or_die(path: Path) -> str:
267
+ _, msg = merge_runs(str(path))
268
+ if msg.startswith("Error"):
269
+ print(msg, file=sys.stderr)
270
+ sys.exit(1)
271
+ return msg
272
+
273
+
274
+ def main() -> None:
275
+ p = argparse.ArgumentParser(
276
+ description="Merge adjacent identically-formatted runs in a DOCX (directory or .docx file)."
277
+ )
278
+ p.add_argument("input", help="Unpacked DOCX directory OR a .docx/.dotx file")
279
+ p.add_argument(
280
+ "-o", "--output",
281
+ help="Output .docx path (only valid when input is a .docx; default: overwrite input)",
282
+ )
283
+ args = p.parse_args()
284
+
285
+ src = Path(args.input)
286
+
287
+ try:
288
+ if src.is_dir():
289
+ if args.output:
290
+ p.error("--output is only valid for .docx input; directory input is modified in place")
291
+ print(_merge_or_die(src))
292
+ elif src.is_file() and src.suffix.lower() in (".docx", ".dotx"):
293
+ out = Path(args.output) if args.output else src
294
+ with tempfile.TemporaryDirectory() as tmp:
295
+ tmp_path = Path(tmp)
296
+ with zipfile.ZipFile(src) as zf:
297
+ safe_extract(zf, tmp_path)
298
+ msg = _merge_or_die(tmp_path)
299
+ rezip(tmp_path, out)
300
+ print(f"{msg}; wrote {out}")
301
+ else:
302
+ print(f"Error: {src} is neither a directory nor a .docx/.dotx file", file=sys.stderr)
303
+ sys.exit(1)
304
+ except (OSError, ValueError, zipfile.BadZipFile) as e:
305
+ print(f"Error: {e}", file=sys.stderr)
306
+ sys.exit(1)
307
+
308
+
309
+ if __name__ == "__main__":
310
+ main()