@kortix/sandbox 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/config/customize.sh +143 -0
  2. package/config/kortix-env-setup.sh +25 -0
  3. package/kortix-master/package.json +22 -0
  4. package/kortix-master/src/config.ts +22 -0
  5. package/kortix-master/src/index.ts +44 -0
  6. package/kortix-master/src/routes/env.ts +65 -0
  7. package/kortix-master/src/routes/proxy.ts +108 -0
  8. package/kortix-master/src/routes/update.ts +185 -0
  9. package/kortix-master/src/services/proxy.ts +43 -0
  10. package/kortix-master/src/services/secret-store.ts +156 -0
  11. package/kortix-master/tsconfig.json +14 -0
  12. package/opencode/agents/kortix-browser.md +142 -0
  13. package/opencode/agents/kortix-build.md +62 -0
  14. package/opencode/agents/kortix-explore.md +66 -0
  15. package/opencode/agents/kortix-image-gen.md +33 -0
  16. package/opencode/agents/kortix-main.md +450 -0
  17. package/opencode/agents/kortix-plan.md +100 -0
  18. package/opencode/agents/kortix-research.md +84 -0
  19. package/opencode/agents/kortix-sheets.md +61 -0
  20. package/opencode/agents/kortix-slides.md +64 -0
  21. package/opencode/agents/kortix-web-dev.md +572 -0
  22. package/opencode/commands/email.md +36 -0
  23. package/opencode/commands/init.md +43 -0
  24. package/opencode/commands/journal.md +44 -0
  25. package/opencode/commands/memory-init.md +81 -0
  26. package/opencode/commands/memory-search.md +50 -0
  27. package/opencode/commands/memory-status.md +56 -0
  28. package/opencode/commands/research.md +36 -0
  29. package/opencode/commands/search.md +38 -0
  30. package/opencode/commands/slides.md +32 -0
  31. package/opencode/commands/spreadsheet.md +30 -0
  32. package/opencode/memory.json +37 -0
  33. package/opencode/ocx.jsonc +10 -0
  34. package/opencode/opencode.jsonc +103 -0
  35. package/opencode/package.json +25 -0
  36. package/opencode/patches/apply.sh +19 -0
  37. package/opencode/patches/opencode-pty-spawn.txt +49 -0
  38. package/opencode/plugin/background-agents.ts.disabled +483 -0
  39. package/opencode/plugin/kdco-primitives/get-project-id.ts +172 -0
  40. package/opencode/plugin/kdco-primitives/index.ts +26 -0
  41. package/opencode/plugin/kdco-primitives/log-warn.ts +51 -0
  42. package/opencode/plugin/kdco-primitives/mutex.ts +122 -0
  43. package/opencode/plugin/kdco-primitives/shell.ts +138 -0
  44. package/opencode/plugin/kdco-primitives/temp.ts +36 -0
  45. package/opencode/plugin/kdco-primitives/terminal-detect.ts +34 -0
  46. package/opencode/plugin/kdco-primitives/types.ts +13 -0
  47. package/opencode/plugin/kdco-primitives/with-timeout.ts +84 -0
  48. package/opencode/plugin/memory.ts +306 -0
  49. package/opencode/plugin/worktree/state.ts +412 -0
  50. package/opencode/plugin/worktree/terminal.ts +1002 -0
  51. package/opencode/plugin/worktree.ts +861 -0
  52. package/opencode/skills/KORTIX-browser/SKILL.md +478 -0
  53. package/opencode/skills/KORTIX-cron-triggers/SKILL.md +173 -0
  54. package/opencode/skills/KORTIX-deep-research/SKILL.md +278 -0
  55. package/opencode/skills/KORTIX-docx/SKILL.md +398 -0
  56. package/opencode/skills/KORTIX-docx/scripts/__init__.py +1 -0
  57. package/opencode/skills/KORTIX-docx/scripts/accept_changes.py +104 -0
  58. package/opencode/skills/KORTIX-docx/scripts/comment.py +244 -0
  59. package/opencode/skills/KORTIX-docx/scripts/office/helpers/__init__.py +0 -0
  60. package/opencode/skills/KORTIX-docx/scripts/office/helpers/merge_runs.py +199 -0
  61. package/opencode/skills/KORTIX-docx/scripts/office/helpers/simplify_redlines.py +197 -0
  62. package/opencode/skills/KORTIX-docx/scripts/office/pack.py +159 -0
  63. package/opencode/skills/KORTIX-docx/scripts/office/soffice.py +183 -0
  64. package/opencode/skills/KORTIX-docx/scripts/office/unpack.py +132 -0
  65. package/opencode/skills/KORTIX-docx/scripts/office/validate.py +111 -0
  66. package/opencode/skills/KORTIX-docx/scripts/office/validators/__init__.py +15 -0
  67. package/opencode/skills/KORTIX-docx/scripts/office/validators/base.py +847 -0
  68. package/opencode/skills/KORTIX-docx/scripts/office/validators/docx.py +446 -0
  69. package/opencode/skills/KORTIX-docx/scripts/office/validators/pptx.py +275 -0
  70. package/opencode/skills/KORTIX-docx/scripts/office/validators/redlining.py +247 -0
  71. package/opencode/skills/KORTIX-docx/scripts/render_docx.py +179 -0
  72. package/opencode/skills/KORTIX-docx/scripts/templates/comments.xml +3 -0
  73. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtended.xml +3 -0
  74. package/opencode/skills/KORTIX-docx/scripts/templates/commentsExtensible.xml +3 -0
  75. package/opencode/skills/KORTIX-docx/scripts/templates/commentsIds.xml +3 -0
  76. package/opencode/skills/KORTIX-docx/scripts/templates/people.xml +3 -0
  77. package/opencode/skills/KORTIX-domain-research/SKILL.md +96 -0
  78. package/opencode/skills/KORTIX-domain-research/scripts/domain-lookup.py +810 -0
  79. package/opencode/skills/KORTIX-elevenlabs/SKILL.md +230 -0
  80. package/opencode/skills/KORTIX-elevenlabs/scripts/tts.py +389 -0
  81. package/opencode/skills/KORTIX-email/SKILL.md +145 -0
  82. package/opencode/skills/KORTIX-legal-writer/SKILL.md +409 -0
  83. package/opencode/skills/KORTIX-legal-writer/references/bluebook.md +152 -0
  84. package/opencode/skills/KORTIX-legal-writer/references/document-types.md +416 -0
  85. package/opencode/skills/KORTIX-legal-writer/scripts/courtlistener.py +291 -0
  86. package/opencode/skills/KORTIX-legal-writer/scripts/ecfr_lookup.py +299 -0
  87. package/opencode/skills/KORTIX-legal-writer/scripts/verify-legal.py +507 -0
  88. package/opencode/skills/KORTIX-logo-creator/SKILL.md +293 -0
  89. package/opencode/skills/KORTIX-logo-creator/references/prompt-patterns.md +134 -0
  90. package/opencode/skills/KORTIX-logo-creator/scripts/compose_logo.py +406 -0
  91. package/opencode/skills/KORTIX-logo-creator/scripts/create_logo_sheet.py +258 -0
  92. package/opencode/skills/KORTIX-logo-creator/scripts/remove_bg.py +96 -0
  93. package/opencode/skills/KORTIX-memory/SKILL.md +261 -0
  94. package/opencode/skills/KORTIX-memory/scripts/export-sessions.py +409 -0
  95. package/opencode/skills/KORTIX-paper-creator/SKILL.md +549 -0
  96. package/opencode/skills/KORTIX-paper-creator/assets/template.tex +101 -0
  97. package/opencode/skills/KORTIX-paper-creator/scripts/compile.sh +177 -0
  98. package/opencode/skills/KORTIX-paper-creator/scripts/openalex_to_bibtex.py +220 -0
  99. package/opencode/skills/KORTIX-paper-creator/scripts/verify.sh +354 -0
  100. package/opencode/skills/KORTIX-paper-search/SKILL.md +418 -0
  101. package/opencode/skills/KORTIX-pdf/SKILL.md +232 -0
  102. package/opencode/skills/KORTIX-pdf/forms.md +36 -0
  103. package/opencode/skills/KORTIX-pdf/reference.md +105 -0
  104. package/opencode/skills/KORTIX-pdf/scripts/check_bounding_boxes.py +65 -0
  105. package/opencode/skills/KORTIX-pdf/scripts/check_fillable_fields.py +11 -0
  106. package/opencode/skills/KORTIX-pdf/scripts/convert_pdf_to_images.py +33 -0
  107. package/opencode/skills/KORTIX-pdf/scripts/create_validation_image.py +37 -0
  108. package/opencode/skills/KORTIX-pdf/scripts/extract_form_field_info.py +122 -0
  109. package/opencode/skills/KORTIX-pdf/scripts/extract_form_structure.py +115 -0
  110. package/opencode/skills/KORTIX-pdf/scripts/fill_fillable_fields.py +98 -0
  111. package/opencode/skills/KORTIX-pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
  112. package/opencode/skills/KORTIX-plan/SKILL.md +228 -0
  113. package/opencode/skills/KORTIX-presentation-viewer/SKILL.md +87 -0
  114. package/opencode/skills/KORTIX-presentation-viewer/serve.ts +136 -0
  115. package/opencode/skills/KORTIX-presentation-viewer/viewer.html +559 -0
  116. package/opencode/skills/KORTIX-presentations/SKILL.md +344 -0
  117. package/opencode/skills/KORTIX-remotion/SKILL.md +56 -0
  118. package/opencode/skills/KORTIX-remotion/rules/3d.md +86 -0
  119. package/opencode/skills/KORTIX-remotion/rules/animations.md +29 -0
  120. package/opencode/skills/KORTIX-remotion/rules/assets.md +78 -0
  121. package/opencode/skills/KORTIX-remotion/rules/audio-visualization.md +198 -0
  122. package/opencode/skills/KORTIX-remotion/rules/audio.md +169 -0
  123. package/opencode/skills/KORTIX-remotion/rules/calculate-metadata.md +104 -0
  124. package/opencode/skills/KORTIX-remotion/rules/can-decode.md +75 -0
  125. package/opencode/skills/KORTIX-remotion/rules/charts.md +120 -0
  126. package/opencode/skills/KORTIX-remotion/rules/compositions.md +141 -0
  127. package/opencode/skills/KORTIX-remotion/rules/display-captions.md +184 -0
  128. package/opencode/skills/KORTIX-remotion/rules/extract-frames.md +229 -0
  129. package/opencode/skills/KORTIX-remotion/rules/ffmpeg.md +38 -0
  130. package/opencode/skills/KORTIX-remotion/rules/fonts.md +152 -0
  131. package/opencode/skills/KORTIX-remotion/rules/get-audio-duration.md +58 -0
  132. package/opencode/skills/KORTIX-remotion/rules/get-video-dimensions.md +68 -0
  133. package/opencode/skills/KORTIX-remotion/rules/get-video-duration.md +58 -0
  134. package/opencode/skills/KORTIX-remotion/rules/gifs.md +141 -0
  135. package/opencode/skills/KORTIX-remotion/rules/images.md +130 -0
  136. package/opencode/skills/KORTIX-remotion/rules/import-srt-captions.md +69 -0
  137. package/opencode/skills/KORTIX-remotion/rules/light-leaks.md +73 -0
  138. package/opencode/skills/KORTIX-remotion/rules/lottie.md +68 -0
  139. package/opencode/skills/KORTIX-remotion/rules/maps.md +401 -0
  140. package/opencode/skills/KORTIX-remotion/rules/measuring-dom-nodes.md +35 -0
  141. package/opencode/skills/KORTIX-remotion/rules/measuring-text.md +143 -0
  142. package/opencode/skills/KORTIX-remotion/rules/parameters.md +98 -0
  143. package/opencode/skills/KORTIX-remotion/rules/sequencing.md +118 -0
  144. package/opencode/skills/KORTIX-remotion/rules/subtitles.md +36 -0
  145. package/opencode/skills/KORTIX-remotion/rules/tailwind.md +11 -0
  146. package/opencode/skills/KORTIX-remotion/rules/text-animations.md +20 -0
  147. package/opencode/skills/KORTIX-remotion/rules/timing.md +179 -0
  148. package/opencode/skills/KORTIX-remotion/rules/transcribe-captions.md +70 -0
  149. package/opencode/skills/KORTIX-remotion/rules/transitions.md +197 -0
  150. package/opencode/skills/KORTIX-remotion/rules/transparent-videos.md +106 -0
  151. package/opencode/skills/KORTIX-remotion/rules/trimming.md +53 -0
  152. package/opencode/skills/KORTIX-remotion/rules/videos.md +171 -0
  153. package/opencode/skills/KORTIX-secrets/SKILL.md +280 -0
  154. package/opencode/skills/KORTIX-semantic-search/SKILL.md +213 -0
  155. package/opencode/skills/KORTIX-session-search/SKILL.md +807 -0
  156. package/opencode/skills/KORTIX-session-search/Untitled +1 -0
  157. package/opencode/skills/KORTIX-skill-creator/SKILL.md +163 -0
  158. package/opencode/skills/KORTIX-web-research/SKILL.md +69 -0
  159. package/opencode/skills/KORTIX-xlsx/LICENSE.txt +30 -0
  160. package/opencode/skills/KORTIX-xlsx/SKILL.md +549 -0
  161. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/__init__.py +0 -0
  162. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/merge_runs.py +199 -0
  163. package/opencode/skills/KORTIX-xlsx/scripts/office/helpers/simplify_redlines.py +197 -0
  164. package/opencode/skills/KORTIX-xlsx/scripts/office/pack.py +159 -0
  165. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  166. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  167. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  168. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  169. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  170. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  171. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  172. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  173. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  174. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  175. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  176. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  177. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  178. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  179. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  180. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  181. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  182. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  183. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  184. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  185. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  186. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  187. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  188. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  189. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  190. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  191. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  192. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  193. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  194. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  195. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  196. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
  197. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
  198. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
  199. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
  200. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
  201. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
  202. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  203. package/opencode/skills/KORTIX-xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
  204. package/opencode/skills/KORTIX-xlsx/scripts/office/soffice.py +183 -0
  205. package/opencode/skills/KORTIX-xlsx/scripts/office/unpack.py +132 -0
  206. package/opencode/skills/KORTIX-xlsx/scripts/office/validate.py +111 -0
  207. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/__init__.py +15 -0
  208. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/base.py +847 -0
  209. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/docx.py +446 -0
  210. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/pptx.py +275 -0
  211. package/opencode/skills/KORTIX-xlsx/scripts/office/validators/redlining.py +247 -0
  212. package/opencode/skills/KORTIX-xlsx/scripts/recalc.py +184 -0
  213. package/opencode/tools/image-gen.ts +342 -0
  214. package/opencode/tools/image-search.ts +190 -0
  215. package/opencode/tools/memory-get.ts +168 -0
  216. package/opencode/tools/memory-search.ts +247 -0
  217. package/opencode/tools/presentation-gen.ts +723 -0
  218. package/opencode/tools/scrape-webpage.ts +115 -0
  219. package/opencode/tools/scripts/.python-version +1 -0
  220. package/opencode/tools/scripts/convert_pdf.py +184 -0
  221. package/opencode/tools/scripts/convert_pptx.py +562 -0
  222. package/opencode/tools/scripts/pyproject.toml +11 -0
  223. package/opencode/tools/scripts/uv.lock +287 -0
  224. package/opencode/tools/scripts/validate_slide.py +74 -0
  225. package/opencode/tools/show-user.ts +217 -0
  226. package/opencode/tools/tests/e2e-presentation-fix.ts +277 -0
  227. package/opencode/tools/tests/image-gen.test.ts +215 -0
  228. package/opencode/tools/tests/image-search.test.ts +125 -0
  229. package/opencode/tools/tests/memory-system-benchmark.ts +1076 -0
  230. package/opencode/tools/tests/presentation-gen.test.ts +389 -0
  231. package/opencode/tools/tests/scrape-webpage.test.ts +74 -0
  232. package/opencode/tools/tests/show-user.test.ts +241 -0
  233. package/opencode/tools/tests/video-gen.test.ts +110 -0
  234. package/opencode/tools/tests/web-search.test.ts +106 -0
  235. package/opencode/tools/video-gen.ts +200 -0
  236. package/opencode/tools/web-search.ts +153 -0
  237. package/opencode/tsconfig.json +29 -0
  238. package/package.json +36 -0
  239. package/patch-agent-browser.js +100 -0
  240. package/postinstall.sh +88 -0
  241. package/services/KORTIX-presentation-viewer/run +37 -0
  242. package/services/agent-browser-viewer/run +48 -0
  243. package/services/kortix-master/run +16 -0
  244. package/services/lss-sync/run +22 -0
  245. package/services/opencode-serve/run +25 -0
  246. package/services/opencode-web/run +21 -0
@@ -0,0 +1,275 @@
1
+ """
2
+ Validator for PowerPoint presentation XML files against XSD schemas.
3
+ """
4
+
5
+ import re
6
+
7
+ from .base import BaseSchemaValidator
8
+
9
+
10
+ class PPTXSchemaValidator(BaseSchemaValidator):
11
+
12
+ PRESENTATIONML_NAMESPACE = (
13
+ "http://schemas.openxmlformats.org/presentationml/2006/main"
14
+ )
15
+
16
+ ELEMENT_RELATIONSHIP_TYPES = {
17
+ "sldid": "slide",
18
+ "sldmasterid": "slidemaster",
19
+ "notesmasterid": "notesmaster",
20
+ "sldlayoutid": "slidelayout",
21
+ "themeid": "theme",
22
+ "tablestyleid": "tablestyles",
23
+ }
24
+
25
+ def validate(self):
26
+ if not self.validate_xml():
27
+ return False
28
+
29
+ all_valid = True
30
+ if not self.validate_namespaces():
31
+ all_valid = False
32
+
33
+ if not self.validate_unique_ids():
34
+ all_valid = False
35
+
36
+ if not self.validate_uuid_ids():
37
+ all_valid = False
38
+
39
+ if not self.validate_file_references():
40
+ all_valid = False
41
+
42
+ if not self.validate_slide_layout_ids():
43
+ all_valid = False
44
+
45
+ if not self.validate_content_types():
46
+ all_valid = False
47
+
48
+ if not self.validate_against_xsd():
49
+ all_valid = False
50
+
51
+ if not self.validate_notes_slide_references():
52
+ all_valid = False
53
+
54
+ if not self.validate_all_relationship_ids():
55
+ all_valid = False
56
+
57
+ if not self.validate_no_duplicate_slide_layouts():
58
+ all_valid = False
59
+
60
+ return all_valid
61
+
62
+ def validate_uuid_ids(self):
63
+ import lxml.etree
64
+
65
+ errors = []
66
+ uuid_pattern = re.compile(
67
+ r"^[\{\(]?[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{12}[\}\)]?$"
68
+ )
69
+
70
+ for xml_file in self.xml_files:
71
+ try:
72
+ root = lxml.etree.parse(str(xml_file)).getroot()
73
+
74
+ for elem in root.iter():
75
+ for attr, value in elem.attrib.items():
76
+ attr_name = attr.split("}")[-1].lower()
77
+ if attr_name == "id" or attr_name.endswith("id"):
78
+ if self._looks_like_uuid(value):
79
+ if not uuid_pattern.match(value):
80
+ errors.append(
81
+ f" {xml_file.relative_to(self.unpacked_dir)}: "
82
+ f"Line {elem.sourceline}: ID '{value}' appears to be a UUID but contains invalid hex characters"
83
+ )
84
+
85
+ except (lxml.etree.XMLSyntaxError, Exception) as e:
86
+ errors.append(
87
+ f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
88
+ )
89
+
90
+ if errors:
91
+ print(f"FAILED - Found {len(errors)} UUID ID validation errors:")
92
+ for error in errors:
93
+ print(error)
94
+ return False
95
+ else:
96
+ if self.verbose:
97
+ print("PASSED - All UUID-like IDs contain valid hex values")
98
+ return True
99
+
100
+ def _looks_like_uuid(self, value):
101
+ clean_value = value.strip("{}()").replace("-", "")
102
+ return len(clean_value) == 32 and all(c.isalnum() for c in clean_value)
103
+
104
+ def validate_slide_layout_ids(self):
105
+ import lxml.etree
106
+
107
+ errors = []
108
+
109
+ slide_masters = list(self.unpacked_dir.glob("ppt/slideMasters/*.xml"))
110
+
111
+ if not slide_masters:
112
+ if self.verbose:
113
+ print("PASSED - No slide masters found")
114
+ return True
115
+
116
+ for slide_master in slide_masters:
117
+ try:
118
+ root = lxml.etree.parse(str(slide_master)).getroot()
119
+
120
+ rels_file = slide_master.parent / "_rels" / f"{slide_master.name}.rels"
121
+
122
+ if not rels_file.exists():
123
+ errors.append(
124
+ f" {slide_master.relative_to(self.unpacked_dir)}: "
125
+ f"Missing relationships file: {rels_file.relative_to(self.unpacked_dir)}"
126
+ )
127
+ continue
128
+
129
+ rels_root = lxml.etree.parse(str(rels_file)).getroot()
130
+
131
+ valid_layout_rids = set()
132
+ for rel in rels_root.findall(
133
+ f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
134
+ ):
135
+ rel_type = rel.get("Type", "")
136
+ if "slideLayout" in rel_type:
137
+ valid_layout_rids.add(rel.get("Id"))
138
+
139
+ for sld_layout_id in root.findall(
140
+ f".//{{{self.PRESENTATIONML_NAMESPACE}}}sldLayoutId"
141
+ ):
142
+ r_id = sld_layout_id.get(
143
+ f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id"
144
+ )
145
+ layout_id = sld_layout_id.get("id")
146
+
147
+ if r_id and r_id not in valid_layout_rids:
148
+ errors.append(
149
+ f" {slide_master.relative_to(self.unpacked_dir)}: "
150
+ f"Line {sld_layout_id.sourceline}: sldLayoutId with id='{layout_id}' "
151
+ f"references r:id='{r_id}' which is not found in slide layout relationships"
152
+ )
153
+
154
+ except (lxml.etree.XMLSyntaxError, Exception) as e:
155
+ errors.append(
156
+ f" {slide_master.relative_to(self.unpacked_dir)}: Error: {e}"
157
+ )
158
+
159
+ if errors:
160
+ print(f"FAILED - Found {len(errors)} slide layout ID validation errors:")
161
+ for error in errors:
162
+ print(error)
163
+ print(
164
+ "Remove invalid references or add missing slide layouts to the relationships file."
165
+ )
166
+ return False
167
+ else:
168
+ if self.verbose:
169
+ print("PASSED - All slide layout IDs reference valid slide layouts")
170
+ return True
171
+
172
+ def validate_no_duplicate_slide_layouts(self):
173
+ import lxml.etree
174
+
175
+ errors = []
176
+ slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
177
+
178
+ for rels_file in slide_rels_files:
179
+ try:
180
+ root = lxml.etree.parse(str(rels_file)).getroot()
181
+
182
+ layout_rels = [
183
+ rel
184
+ for rel in root.findall(
185
+ f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
186
+ )
187
+ if "slideLayout" in rel.get("Type", "")
188
+ ]
189
+
190
+ if len(layout_rels) > 1:
191
+ errors.append(
192
+ f" {rels_file.relative_to(self.unpacked_dir)}: has {len(layout_rels)} slideLayout references"
193
+ )
194
+
195
+ except Exception as e:
196
+ errors.append(
197
+ f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
198
+ )
199
+
200
+ if errors:
201
+ print("FAILED - Found slides with duplicate slideLayout references:")
202
+ for error in errors:
203
+ print(error)
204
+ return False
205
+ else:
206
+ if self.verbose:
207
+ print("PASSED - All slides have exactly one slideLayout reference")
208
+ return True
209
+
210
+ def validate_notes_slide_references(self):
211
+ import lxml.etree
212
+
213
+ errors = []
214
+ notes_slide_references = {}
215
+
216
+ slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
217
+
218
+ if not slide_rels_files:
219
+ if self.verbose:
220
+ print("PASSED - No slide relationship files found")
221
+ return True
222
+
223
+ for rels_file in slide_rels_files:
224
+ try:
225
+ root = lxml.etree.parse(str(rels_file)).getroot()
226
+
227
+ for rel in root.findall(
228
+ f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
229
+ ):
230
+ rel_type = rel.get("Type", "")
231
+ if "notesSlide" in rel_type:
232
+ target = rel.get("Target", "")
233
+ if target:
234
+ normalized_target = target.replace("../", "")
235
+
236
+ slide_name = rels_file.stem.replace(
237
+ ".xml", ""
238
+ )
239
+
240
+ if normalized_target not in notes_slide_references:
241
+ notes_slide_references[normalized_target] = []
242
+ notes_slide_references[normalized_target].append(
243
+ (slide_name, rels_file)
244
+ )
245
+
246
+ except (lxml.etree.XMLSyntaxError, Exception) as e:
247
+ errors.append(
248
+ f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
249
+ )
250
+
251
+ for target, references in notes_slide_references.items():
252
+ if len(references) > 1:
253
+ slide_names = [ref[0] for ref in references]
254
+ errors.append(
255
+ f" Notes slide '{target}' is referenced by multiple slides: {', '.join(slide_names)}"
256
+ )
257
+ for slide_name, rels_file in references:
258
+ errors.append(f" - {rels_file.relative_to(self.unpacked_dir)}")
259
+
260
+ if errors:
261
+ print(
262
+ f"FAILED - Found {len([e for e in errors if not e.startswith(' ')])} notes slide reference validation errors:"
263
+ )
264
+ for error in errors:
265
+ print(error)
266
+ print("Each slide may optionally have its own slide file.")
267
+ return False
268
+ else:
269
+ if self.verbose:
270
+ print("PASSED - All notes slide references are unique")
271
+ return True
272
+
273
+
274
+ if __name__ == "__main__":
275
+ raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,247 @@
1
+ """
2
+ Validator for tracked changes in Word documents.
3
+ """
4
+
5
+ import subprocess
6
+ import tempfile
7
+ import zipfile
8
+ from pathlib import Path
9
+
10
+
11
+ class RedliningValidator:
12
+
13
+ def __init__(self, unpacked_dir, original_docx, verbose=False, author="Claude"):
14
+ self.unpacked_dir = Path(unpacked_dir)
15
+ self.original_docx = Path(original_docx)
16
+ self.verbose = verbose
17
+ self.author = author
18
+ self.namespaces = {
19
+ "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
20
+ }
21
+
22
+ def repair(self) -> int:
23
+ return 0
24
+
25
+ def validate(self):
26
+ modified_file = self.unpacked_dir / "word" / "document.xml"
27
+ if not modified_file.exists():
28
+ print(f"FAILED - Modified document.xml not found at {modified_file}")
29
+ return False
30
+
31
+ try:
32
+ import xml.etree.ElementTree as ET
33
+
34
+ tree = ET.parse(modified_file)
35
+ root = tree.getroot()
36
+
37
+ del_elements = root.findall(".//w:del", self.namespaces)
38
+ ins_elements = root.findall(".//w:ins", self.namespaces)
39
+
40
+ author_del_elements = [
41
+ elem
42
+ for elem in del_elements
43
+ if elem.get(f"{{{self.namespaces['w']}}}author") == self.author
44
+ ]
45
+ author_ins_elements = [
46
+ elem
47
+ for elem in ins_elements
48
+ if elem.get(f"{{{self.namespaces['w']}}}author") == self.author
49
+ ]
50
+
51
+ if not author_del_elements and not author_ins_elements:
52
+ if self.verbose:
53
+ print(f"PASSED - No tracked changes by {self.author} found.")
54
+ return True
55
+
56
+ except Exception:
57
+ pass
58
+
59
+ with tempfile.TemporaryDirectory() as temp_dir:
60
+ temp_path = Path(temp_dir)
61
+
62
+ try:
63
+ with zipfile.ZipFile(self.original_docx, "r") as zip_ref:
64
+ zip_ref.extractall(temp_path)
65
+ except Exception as e:
66
+ print(f"FAILED - Error unpacking original docx: {e}")
67
+ return False
68
+
69
+ original_file = temp_path / "word" / "document.xml"
70
+ if not original_file.exists():
71
+ print(
72
+ f"FAILED - Original document.xml not found in {self.original_docx}"
73
+ )
74
+ return False
75
+
76
+ try:
77
+ import xml.etree.ElementTree as ET
78
+
79
+ modified_tree = ET.parse(modified_file)
80
+ modified_root = modified_tree.getroot()
81
+ original_tree = ET.parse(original_file)
82
+ original_root = original_tree.getroot()
83
+ except ET.ParseError as e:
84
+ print(f"FAILED - Error parsing XML files: {e}")
85
+ return False
86
+
87
+ self._remove_author_tracked_changes(original_root)
88
+ self._remove_author_tracked_changes(modified_root)
89
+
90
+ modified_text = self._extract_text_content(modified_root)
91
+ original_text = self._extract_text_content(original_root)
92
+
93
+ if modified_text != original_text:
94
+ error_message = self._generate_detailed_diff(
95
+ original_text, modified_text
96
+ )
97
+ print(error_message)
98
+ return False
99
+
100
+ if self.verbose:
101
+ print(f"PASSED - All changes by {self.author} are properly tracked")
102
+ return True
103
+
104
+ def _generate_detailed_diff(self, original_text, modified_text):
105
+ error_parts = [
106
+ f"FAILED - Document text doesn't match after removing {self.author}'s tracked changes",
107
+ "",
108
+ "Likely causes:",
109
+ " 1. Modified text inside another author's <w:ins> or <w:del> tags",
110
+ " 2. Made edits without proper tracked changes",
111
+ " 3. Didn't nest <w:del> inside <w:ins> when deleting another's insertion",
112
+ "",
113
+ "For pre-redlined documents, use correct patterns:",
114
+ " - To reject another's INSERTION: Nest <w:del> inside their <w:ins>",
115
+ " - To restore another's DELETION: Add new <w:ins> AFTER their <w:del>",
116
+ "",
117
+ ]
118
+
119
+ git_diff = self._get_git_word_diff(original_text, modified_text)
120
+ if git_diff:
121
+ error_parts.extend(["Differences:", "============", git_diff])
122
+ else:
123
+ error_parts.append("Unable to generate word diff (git not available)")
124
+
125
+ return "\n".join(error_parts)
126
+
127
+ def _get_git_word_diff(self, original_text, modified_text):
128
+ try:
129
+ with tempfile.TemporaryDirectory() as temp_dir:
130
+ temp_path = Path(temp_dir)
131
+
132
+ original_file = temp_path / "original.txt"
133
+ modified_file = temp_path / "modified.txt"
134
+
135
+ original_file.write_text(original_text, encoding="utf-8")
136
+ modified_file.write_text(modified_text, encoding="utf-8")
137
+
138
+ result = subprocess.run(
139
+ [
140
+ "git",
141
+ "diff",
142
+ "--word-diff=plain",
143
+ "--word-diff-regex=.",
144
+ "-U0",
145
+ "--no-index",
146
+ str(original_file),
147
+ str(modified_file),
148
+ ],
149
+ capture_output=True,
150
+ text=True,
151
+ )
152
+
153
+ if result.stdout.strip():
154
+ lines = result.stdout.split("\n")
155
+ content_lines = []
156
+ in_content = False
157
+ for line in lines:
158
+ if line.startswith("@@"):
159
+ in_content = True
160
+ continue
161
+ if in_content and line.strip():
162
+ content_lines.append(line)
163
+
164
+ if content_lines:
165
+ return "\n".join(content_lines)
166
+
167
+ result = subprocess.run(
168
+ [
169
+ "git",
170
+ "diff",
171
+ "--word-diff=plain",
172
+ "-U0",
173
+ "--no-index",
174
+ str(original_file),
175
+ str(modified_file),
176
+ ],
177
+ capture_output=True,
178
+ text=True,
179
+ )
180
+
181
+ if result.stdout.strip():
182
+ lines = result.stdout.split("\n")
183
+ content_lines = []
184
+ in_content = False
185
+ for line in lines:
186
+ if line.startswith("@@"):
187
+ in_content = True
188
+ continue
189
+ if in_content and line.strip():
190
+ content_lines.append(line)
191
+ return "\n".join(content_lines)
192
+
193
+ except (subprocess.CalledProcessError, FileNotFoundError, Exception):
194
+ pass
195
+
196
+ return None
197
+
198
+ def _remove_author_tracked_changes(self, root):
199
+ ins_tag = f"{{{self.namespaces['w']}}}ins"
200
+ del_tag = f"{{{self.namespaces['w']}}}del"
201
+ author_attr = f"{{{self.namespaces['w']}}}author"
202
+
203
+ for parent in root.iter():
204
+ to_remove = []
205
+ for child in parent:
206
+ if child.tag == ins_tag and child.get(author_attr) == self.author:
207
+ to_remove.append(child)
208
+ for elem in to_remove:
209
+ parent.remove(elem)
210
+
211
+ deltext_tag = f"{{{self.namespaces['w']}}}delText"
212
+ t_tag = f"{{{self.namespaces['w']}}}t"
213
+
214
+ for parent in root.iter():
215
+ to_process = []
216
+ for child in parent:
217
+ if child.tag == del_tag and child.get(author_attr) == self.author:
218
+ to_process.append((child, list(parent).index(child)))
219
+
220
+ for del_elem, del_index in reversed(to_process):
221
+ for elem in del_elem.iter():
222
+ if elem.tag == deltext_tag:
223
+ elem.tag = t_tag
224
+
225
+ for child in reversed(list(del_elem)):
226
+ parent.insert(del_index, child)
227
+ parent.remove(del_elem)
228
+
229
+ def _extract_text_content(self, root):
230
+ p_tag = f"{{{self.namespaces['w']}}}p"
231
+ t_tag = f"{{{self.namespaces['w']}}}t"
232
+
233
+ paragraphs = []
234
+ for p_elem in root.findall(f".//{p_tag}"):
235
+ text_parts = []
236
+ for t_elem in p_elem.findall(f".//{t_tag}"):
237
+ if t_elem.text:
238
+ text_parts.append(t_elem.text)
239
+ paragraph_text = "".join(text_parts)
240
+ if paragraph_text:
241
+ paragraphs.append(paragraph_text)
242
+
243
+ return "\n".join(paragraphs)
244
+
245
+
246
+ if __name__ == "__main__":
247
+ raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,184 @@
1
+ """
2
+ Excel Formula Recalculation Script
3
+ Recalculates all formulas in an Excel file using LibreOffice
4
+ """
5
+
6
+ import json
7
+ import os
8
+ import platform
9
+ import subprocess
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ from office.soffice import get_soffice_env
14
+
15
+ from openpyxl import load_workbook
16
+
17
+ MACRO_DIR_MACOS = "~/Library/Application Support/LibreOffice/4/user/basic/Standard"
18
+ MACRO_DIR_LINUX = "~/.config/libreoffice/4/user/basic/Standard"
19
+ MACRO_FILENAME = "Module1.xba"
20
+
21
+ RECALCULATE_MACRO = """<?xml version="1.0" encoding="UTF-8"?>
22
+ <!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
23
+ <script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">
24
+ Sub RecalculateAndSave()
25
+ ThisComponent.calculateAll()
26
+ ThisComponent.store()
27
+ ThisComponent.close(True)
28
+ End Sub
29
+ </script:module>"""
30
+
31
+
32
+ def has_gtimeout():
33
+ try:
34
+ subprocess.run(
35
+ ["gtimeout", "--version"], capture_output=True, timeout=1, check=False
36
+ )
37
+ return True
38
+ except (FileNotFoundError, subprocess.TimeoutExpired):
39
+ return False
40
+
41
+
42
+ def setup_libreoffice_macro():
43
+ macro_dir = os.path.expanduser(
44
+ MACRO_DIR_MACOS if platform.system() == "Darwin" else MACRO_DIR_LINUX
45
+ )
46
+ macro_file = os.path.join(macro_dir, MACRO_FILENAME)
47
+
48
+ if (
49
+ os.path.exists(macro_file)
50
+ and "RecalculateAndSave" in Path(macro_file).read_text()
51
+ ):
52
+ return True
53
+
54
+ if not os.path.exists(macro_dir):
55
+ subprocess.run(
56
+ ["soffice", "--headless", "--terminate_after_init"],
57
+ capture_output=True,
58
+ timeout=10,
59
+ env=get_soffice_env(),
60
+ )
61
+ os.makedirs(macro_dir, exist_ok=True)
62
+
63
+ try:
64
+ Path(macro_file).write_text(RECALCULATE_MACRO)
65
+ return True
66
+ except Exception:
67
+ return False
68
+
69
+
70
+ def recalc(filename, timeout=30):
71
+ if not Path(filename).exists():
72
+ return {"error": f"File {filename} does not exist"}
73
+
74
+ abs_path = str(Path(filename).absolute())
75
+
76
+ if not setup_libreoffice_macro():
77
+ return {"error": "Failed to setup LibreOffice macro"}
78
+
79
+ cmd = [
80
+ "soffice",
81
+ "--headless",
82
+ "--norestore",
83
+ "vnd.sun.star.script:Standard.Module1.RecalculateAndSave?language=Basic&location=application",
84
+ abs_path,
85
+ ]
86
+
87
+ if platform.system() == "Linux":
88
+ cmd = ["timeout", str(timeout)] + cmd
89
+ elif platform.system() == "Darwin" and has_gtimeout():
90
+ cmd = ["gtimeout", str(timeout)] + cmd
91
+
92
+ result = subprocess.run(cmd, capture_output=True, text=True, env=get_soffice_env())
93
+
94
+ if result.returncode != 0 and result.returncode != 124:
95
+ error_msg = result.stderr or "Unknown error during recalculation"
96
+ if "Module1" in error_msg or "RecalculateAndSave" not in error_msg:
97
+ return {"error": "LibreOffice macro not configured properly"}
98
+ return {"error": error_msg}
99
+
100
+ try:
101
+ wb = load_workbook(filename, data_only=True)
102
+
103
+ excel_errors = [
104
+ "#VALUE!",
105
+ "#DIV/0!",
106
+ "#REF!",
107
+ "#NAME?",
108
+ "#NULL!",
109
+ "#NUM!",
110
+ "#N/A",
111
+ ]
112
+ error_details = {err: [] for err in excel_errors}
113
+ total_errors = 0
114
+
115
+ for sheet_name in wb.sheetnames:
116
+ ws = wb[sheet_name]
117
+ for row in ws.iter_rows():
118
+ for cell in row:
119
+ if cell.value is not None and isinstance(cell.value, str):
120
+ for err in excel_errors:
121
+ if err in cell.value:
122
+ location = f"{sheet_name}!{cell.coordinate}"
123
+ error_details[err].append(location)
124
+ total_errors += 1
125
+ break
126
+
127
+ wb.close()
128
+
129
+ result = {
130
+ "status": "success" if total_errors == 0 else "errors_found",
131
+ "total_errors": total_errors,
132
+ "error_summary": {},
133
+ }
134
+
135
+ for err_type, locations in error_details.items():
136
+ if locations:
137
+ result["error_summary"][err_type] = {
138
+ "count": len(locations),
139
+ "locations": locations[:20],
140
+ }
141
+
142
+ wb_formulas = load_workbook(filename, data_only=False)
143
+ formula_count = 0
144
+ for sheet_name in wb_formulas.sheetnames:
145
+ ws = wb_formulas[sheet_name]
146
+ for row in ws.iter_rows():
147
+ for cell in row:
148
+ if (
149
+ cell.value
150
+ and isinstance(cell.value, str)
151
+ and cell.value.startswith("=")
152
+ ):
153
+ formula_count += 1
154
+ wb_formulas.close()
155
+
156
+ result["total_formulas"] = formula_count
157
+
158
+ return result
159
+
160
+ except Exception as e:
161
+ return {"error": str(e)}
162
+
163
+
164
+ def main():
165
+ if len(sys.argv) < 2:
166
+ print("Usage: python recalc.py <excel_file> [timeout_seconds]")
167
+ print("\nRecalculates all formulas in an Excel file using LibreOffice")
168
+ print("\nReturns JSON with error details:")
169
+ print(" - status: 'success' or 'errors_found'")
170
+ print(" - total_errors: Total number of Excel errors found")
171
+ print(" - total_formulas: Number of formulas in the file")
172
+ print(" - error_summary: Breakdown by error type with locations")
173
+ print(" - #VALUE!, #DIV/0!, #REF!, #NAME?, #NULL!, #NUM!, #N/A")
174
+ sys.exit(1)
175
+
176
+ filename = sys.argv[1]
177
+ timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 30
178
+
179
+ result = recalc(filename, timeout)
180
+ print(json.dumps(result, indent=2))
181
+
182
+
183
+ if __name__ == "__main__":
184
+ main()