@farazirfan/costar-server-executor 1.7.37 → 1.7.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/dist/agent/agent.d.ts +90 -0
  2. package/dist/agent/agent.d.ts.map +1 -1
  3. package/dist/agent/agent.js +606 -0
  4. package/dist/agent/agent.js.map +1 -1
  5. package/dist/agent/pi-embedded-runner/run.d.ts.map +1 -1
  6. package/dist/agent/pi-embedded-runner/run.js +2 -1
  7. package/dist/agent/pi-embedded-runner/run.js.map +1 -1
  8. package/dist/agent/pi-embedded-runner/system-prompt.d.ts.map +1 -1
  9. package/dist/agent/pi-embedded-runner/system-prompt.js +16 -37
  10. package/dist/agent/pi-embedded-runner/system-prompt.js.map +1 -1
  11. package/dist/agent/pi-embedded-runner/tools.d.ts +4 -1
  12. package/dist/agent/pi-embedded-runner/tools.d.ts.map +1 -1
  13. package/dist/agent/pi-embedded-runner/tools.js +3 -1
  14. package/dist/agent/pi-embedded-runner/tools.js.map +1 -1
  15. package/dist/agent/pi-embedded-runner/types.d.ts +4 -0
  16. package/dist/agent/pi-embedded-runner/types.d.ts.map +1 -1
  17. package/dist/cli/env-loader.d.ts.map +1 -1
  18. package/dist/cli/env-loader.js +1 -0
  19. package/dist/cli/env-loader.js.map +1 -1
  20. package/dist/cli/setup.js +2 -2
  21. package/dist/cli/setup.js.map +1 -1
  22. package/dist/cron/normalize.d.ts +31 -0
  23. package/dist/cron/normalize.d.ts.map +1 -0
  24. package/dist/cron/normalize.js +211 -0
  25. package/dist/cron/normalize.js.map +1 -0
  26. package/dist/cron/scheduler.d.ts +33 -3
  27. package/dist/cron/scheduler.d.ts.map +1 -1
  28. package/dist/cron/scheduler.js +253 -48
  29. package/dist/cron/scheduler.js.map +1 -1
  30. package/dist/heartbeat/runner.d.ts +27 -12
  31. package/dist/heartbeat/runner.d.ts.map +1 -1
  32. package/dist/heartbeat/runner.js +82 -104
  33. package/dist/heartbeat/runner.js.map +1 -1
  34. package/dist/infra/heartbeat-events-filter.d.ts +29 -0
  35. package/dist/infra/heartbeat-events-filter.d.ts.map +1 -0
  36. package/dist/infra/heartbeat-events-filter.js +80 -0
  37. package/dist/infra/heartbeat-events-filter.js.map +1 -0
  38. package/dist/infra/index.d.ts +9 -0
  39. package/dist/infra/index.d.ts.map +1 -0
  40. package/dist/infra/index.js +9 -0
  41. package/dist/infra/index.js.map +1 -0
  42. package/dist/infra/system-events.d.ts +58 -2
  43. package/dist/infra/system-events.d.ts.map +1 -1
  44. package/dist/infra/system-events.js +80 -14
  45. package/dist/infra/system-events.js.map +1 -1
  46. package/dist/server.d.ts.map +1 -1
  47. package/dist/server.js +6 -1
  48. package/dist/server.js.map +1 -1
  49. package/dist/services/platform-keys.d.ts +19 -0
  50. package/dist/services/platform-keys.d.ts.map +1 -0
  51. package/dist/services/platform-keys.js +74 -0
  52. package/dist/services/platform-keys.js.map +1 -0
  53. package/dist/subagent/registry.d.ts +96 -0
  54. package/dist/subagent/registry.d.ts.map +1 -0
  55. package/dist/subagent/registry.js +180 -0
  56. package/dist/subagent/registry.js.map +1 -0
  57. package/dist/tools/complete-turn.d.ts +2 -2
  58. package/dist/tools/complete-turn.js +10 -10
  59. package/dist/tools/complete-turn.js.map +1 -1
  60. package/dist/tools/contacts.d.ts +13 -0
  61. package/dist/tools/contacts.d.ts.map +1 -0
  62. package/dist/tools/contacts.js +80 -0
  63. package/dist/tools/contacts.js.map +1 -0
  64. package/dist/tools/cron.d.ts +17 -2
  65. package/dist/tools/cron.d.ts.map +1 -1
  66. package/dist/tools/cron.js +117 -35
  67. package/dist/tools/cron.js.map +1 -1
  68. package/dist/tools/google-maps.d.ts +6 -6
  69. package/dist/tools/google-maps.d.ts.map +1 -1
  70. package/dist/tools/google-maps.js +207 -262
  71. package/dist/tools/google-maps.js.map +1 -1
  72. package/dist/tools/index.d.ts +17 -7
  73. package/dist/tools/index.d.ts.map +1 -1
  74. package/dist/tools/index.js +40 -9
  75. package/dist/tools/index.js.map +1 -1
  76. package/dist/tools/phone-call.d.ts +11 -0
  77. package/dist/tools/phone-call.d.ts.map +1 -0
  78. package/dist/tools/phone-call.js +151 -0
  79. package/dist/tools/phone-call.js.map +1 -0
  80. package/dist/tools/sessions-spawn.d.ts +33 -0
  81. package/dist/tools/sessions-spawn.d.ts.map +1 -0
  82. package/dist/tools/sessions-spawn.js +164 -0
  83. package/dist/tools/sessions-spawn.js.map +1 -0
  84. package/dist/tools/spotify.d.ts +12 -0
  85. package/dist/tools/spotify.d.ts.map +1 -0
  86. package/dist/tools/spotify.js +251 -0
  87. package/dist/tools/spotify.js.map +1 -0
  88. package/dist/tools/subagents.d.ts +23 -0
  89. package/dist/tools/subagents.d.ts.map +1 -0
  90. package/dist/tools/subagents.js +209 -0
  91. package/dist/tools/subagents.js.map +1 -0
  92. package/dist/tools/whatsapp.d.ts +13 -0
  93. package/dist/tools/whatsapp.d.ts.map +1 -0
  94. package/dist/tools/whatsapp.js +215 -0
  95. package/dist/tools/whatsapp.js.map +1 -0
  96. package/dist/tools/youtube.d.ts +12 -0
  97. package/dist/tools/youtube.d.ts.map +1 -0
  98. package/dist/tools/youtube.js +218 -0
  99. package/dist/tools/youtube.js.map +1 -0
  100. package/dist/utils/asterizk-auth.d.ts +43 -0
  101. package/dist/utils/asterizk-auth.d.ts.map +1 -0
  102. package/dist/utils/asterizk-auth.js +125 -0
  103. package/dist/utils/asterizk-auth.js.map +1 -0
  104. package/dist/web-server.d.ts.map +1 -1
  105. package/dist/web-server.js +132 -0
  106. package/dist/web-server.js.map +1 -1
  107. package/dist/workspace/index.d.ts +3 -4
  108. package/dist/workspace/index.d.ts.map +1 -1
  109. package/dist/workspace/index.js +3 -4
  110. package/dist/workspace/index.js.map +1 -1
  111. package/dist/workspace/templates.d.ts +8 -7
  112. package/dist/workspace/templates.d.ts.map +1 -1
  113. package/dist/workspace/templates.js +18 -127
  114. package/dist/workspace/templates.js.map +1 -1
  115. package/dist/workspace/workspace.d.ts +2 -4
  116. package/dist/workspace/workspace.d.ts.map +1 -1
  117. package/dist/workspace/workspace.js +7 -16
  118. package/dist/workspace/workspace.js.map +1 -1
  119. package/package.json +1 -1
  120. package/public/index.html +231 -0
  121. package/skills/docx/SKILL.md +468 -0
  122. package/skills/docx/scripts/__init__.py +1 -0
  123. package/skills/docx/scripts/accept_changes.py +181 -0
  124. package/skills/docx/scripts/comment.py +347 -0
  125. package/skills/docx/scripts/helpers/__init__.py +0 -0
  126. package/skills/docx/scripts/helpers/merge_runs.py +231 -0
  127. package/skills/docx/scripts/helpers/simplify_redlines.py +240 -0
  128. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  129. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  130. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  131. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  132. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  133. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  134. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  135. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  136. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  137. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  138. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  139. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  140. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  141. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  142. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  143. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  144. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  145. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  146. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  147. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  148. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  149. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  150. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  151. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  152. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  153. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  154. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  155. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  156. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  157. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  158. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  159. package/skills/docx/scripts/ooxml/schemas/mce/mc.xsd +75 -0
  160. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  161. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  162. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  163. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  164. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  165. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  166. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  167. package/skills/docx/scripts/ooxml/scripts/pack.py +159 -0
  168. package/skills/docx/scripts/ooxml/scripts/unpack.py +29 -0
  169. package/skills/docx/scripts/ooxml/scripts/validate.py +106 -0
  170. package/skills/docx/scripts/ooxml/scripts/validation/__init__.py +15 -0
  171. package/skills/docx/scripts/ooxml/scripts/validation/base.py +1023 -0
  172. package/skills/docx/scripts/ooxml/scripts/validation/docx.py +519 -0
  173. package/skills/docx/scripts/ooxml/scripts/validation/pptx.py +315 -0
  174. package/skills/docx/scripts/ooxml/scripts/validation/redlining.py +284 -0
  175. package/skills/docx/scripts/pack.py +166 -0
  176. package/skills/docx/scripts/templates/comments.xml +3 -0
  177. package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  178. package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  179. package/skills/docx/scripts/templates/commentsIds.xml +3 -0
  180. package/skills/docx/scripts/templates/people.xml +3 -0
  181. package/skills/docx/scripts/unpack.py +134 -0
  182. package/skills/longform-video-generation/SKILL.md +298 -0
  183. package/skills/longform-video-generation/references/advanced_techniques.md +474 -0
  184. package/skills/longform-video-generation/references/google_api_guide.md +288 -0
  185. package/skills/longform-video-generation/scripts/video_generator.py +579 -0
  186. package/skills/pdf/FORMS.md +305 -0
  187. package/skills/pdf/REFERENCE.md +612 -0
  188. package/skills/pdf/SKILL.md +293 -0
  189. package/skills/pdf/scripts/check_bounding_boxes.py +70 -0
  190. package/skills/pdf/scripts/check_fillable_fields.py +12 -0
  191. package/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
  192. package/skills/pdf/scripts/create_validation_image.py +41 -0
  193. package/skills/pdf/scripts/extract_form_field_info.py +152 -0
  194. package/skills/pdf/scripts/extract_form_structure.py +124 -0
  195. package/skills/pdf/scripts/fill_fillable_fields.py +116 -0
  196. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +136 -0
  197. package/skills/pptx/SKILL.md +171 -0
  198. package/skills/pptx/editing.md +205 -0
  199. package/skills/pptx/pptxgenjs.md +377 -0
  200. package/skills/pptx/scripts/add_slide.py +225 -0
  201. package/skills/pptx/scripts/clean.py +309 -0
  202. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  203. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  204. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  205. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  206. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  207. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  208. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  209. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  210. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  211. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  212. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  213. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  214. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  215. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  216. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  217. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  218. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  219. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  220. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  221. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  222. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  223. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  224. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  225. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  226. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  227. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  228. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  229. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  230. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  231. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  232. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  233. package/skills/pptx/scripts/ooxml/schemas/mce/mc.xsd +75 -0
  234. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  235. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  236. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  237. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  238. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  239. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  240. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  241. package/skills/pptx/scripts/ooxml/scripts/pack.py +159 -0
  242. package/skills/pptx/scripts/ooxml/scripts/unpack.py +29 -0
  243. package/skills/pptx/scripts/ooxml/scripts/validate.py +106 -0
  244. package/skills/pptx/scripts/ooxml/scripts/validation/__init__.py +15 -0
  245. package/skills/pptx/scripts/ooxml/scripts/validation/base.py +1023 -0
  246. package/skills/pptx/scripts/ooxml/scripts/validation/docx.py +519 -0
  247. package/skills/pptx/scripts/ooxml/scripts/validation/pptx.py +315 -0
  248. package/skills/pptx/scripts/ooxml/scripts/validation/redlining.py +284 -0
  249. package/skills/pptx/scripts/pack.py +168 -0
  250. package/skills/pptx/scripts/thumbnail.py +318 -0
  251. package/skills/pptx/scripts/unpack.py +86 -0
  252. package/skills/xlsx/SKILL.md +291 -0
  253. package/skills/xlsx/recalc.py +247 -0
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env python3
2
+ """Add a new slide to an unpacked PPTX directory.
3
+
4
+ Usage: python add_slide.py <unpacked_dir> <source>
5
+
6
+ The source can be:
7
+ - A slide file (e.g., slide2.xml) - duplicates the slide
8
+ - A layout file (e.g., slideLayout2.xml) - creates from layout
9
+
10
+ Examples:
11
+ python add_slide.py unpacked/ slide2.xml
12
+ # Duplicates slide2, creates slide5.xml
13
+
14
+ python add_slide.py unpacked/ slideLayout2.xml
15
+ # Creates slide5.xml from slideLayout2.xml
16
+
17
+ To see available layouts: ls unpacked/ppt/slideLayouts/
18
+
19
+ Prints the <p:sldId> element to add to presentation.xml.
20
+ """
21
+
22
+ import re
23
+ import shutil
24
+ import sys
25
+ from pathlib import Path
26
+
27
+
28
+ def get_next_slide_number(slides_dir: Path) -> int:
29
+ """Find the next available slide number."""
30
+ existing = [int(m.group(1)) for f in slides_dir.glob("slide*.xml")
31
+ if (m := re.match(r"slide(\d+)\.xml", f.name))]
32
+ return max(existing) + 1 if existing else 1
33
+
34
+
35
+ def create_slide_from_layout(unpacked_dir: Path, layout_file: str) -> None:
36
+ """Create a new slide from a layout template."""
37
+ slides_dir = unpacked_dir / "ppt" / "slides"
38
+ rels_dir = slides_dir / "_rels"
39
+ layouts_dir = unpacked_dir / "ppt" / "slideLayouts"
40
+
41
+ layout_path = layouts_dir / layout_file
42
+ if not layout_path.exists():
43
+ print(f"Error: {layout_path} not found", file=sys.stderr)
44
+ sys.exit(1)
45
+
46
+ # Auto-select destination name
47
+ next_num = get_next_slide_number(slides_dir)
48
+ dest = f"slide{next_num}.xml"
49
+ dest_slide = slides_dir / dest
50
+ dest_rels = rels_dir / f"{dest}.rels"
51
+
52
+ # 1. Create minimal slide XML that references the layout
53
+ slide_xml = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
54
+ <p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
55
+ <p:cSld>
56
+ <p:spTree>
57
+ <p:nvGrpSpPr>
58
+ <p:cNvPr id="1" name=""/>
59
+ <p:cNvGrpSpPr/>
60
+ <p:nvPr/>
61
+ </p:nvGrpSpPr>
62
+ <p:grpSpPr>
63
+ <a:xfrm>
64
+ <a:off x="0" y="0"/>
65
+ <a:ext cx="0" cy="0"/>
66
+ <a:chOff x="0" y="0"/>
67
+ <a:chExt cx="0" cy="0"/>
68
+ </a:xfrm>
69
+ </p:grpSpPr>
70
+ </p:spTree>
71
+ </p:cSld>
72
+ <p:clrMapOvr>
73
+ <a:masterClrMapping/>
74
+ </p:clrMapOvr>
75
+ </p:sld>'''
76
+ dest_slide.write_text(slide_xml, encoding="utf-8")
77
+
78
+ # 2. Create relationships file pointing to the layout
79
+ rels_dir.mkdir(exist_ok=True)
80
+ rels_xml = f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
81
+ <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
82
+ <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/{layout_file}"/>
83
+ </Relationships>'''
84
+ dest_rels.write_text(rels_xml, encoding="utf-8")
85
+
86
+ # 3. Add to [Content_Types].xml
87
+ _add_to_content_types(unpacked_dir, dest)
88
+
89
+ # 4. Add relationship to presentation.xml.rels and get rId
90
+ rid = _add_to_presentation_rels(unpacked_dir, dest)
91
+
92
+ # 5. Get next slide ID
93
+ next_slide_id = _get_next_slide_id(unpacked_dir)
94
+
95
+ # Output
96
+ print(f"Created {dest} from {layout_file}")
97
+ print(f'Add to presentation.xml <p:sldIdLst>: <p:sldId id="{next_slide_id}" r:id="{rid}"/>')
98
+
99
+
100
+ def duplicate_slide(unpacked_dir: Path, source: str) -> None:
101
+ """Duplicate a slide and update all references."""
102
+ slides_dir = unpacked_dir / "ppt" / "slides"
103
+ rels_dir = slides_dir / "_rels"
104
+
105
+ source_slide = slides_dir / source
106
+
107
+ # Validate source exists
108
+ if not source_slide.exists():
109
+ print(f"Error: {source_slide} not found", file=sys.stderr)
110
+ sys.exit(1)
111
+
112
+ # Auto-select destination name
113
+ next_num = get_next_slide_number(slides_dir)
114
+ dest = f"slide{next_num}.xml"
115
+ dest_slide = slides_dir / dest
116
+
117
+ source_rels = rels_dir / f"{source}.rels"
118
+ dest_rels = rels_dir / f"{dest}.rels"
119
+
120
+ # 1. Copy slide XML
121
+ shutil.copy2(source_slide, dest_slide)
122
+
123
+ # 2. Copy relationships file (if exists)
124
+ if source_rels.exists():
125
+ shutil.copy2(source_rels, dest_rels)
126
+
127
+ # 3. Remove notes references from new rels file
128
+ rels_content = dest_rels.read_text(encoding="utf-8")
129
+ rels_content = re.sub(
130
+ r'\s*<Relationship[^>]*Type="[^"]*notesSlide"[^>]*/>\s*',
131
+ "\n",
132
+ rels_content,
133
+ )
134
+ dest_rels.write_text(rels_content, encoding="utf-8")
135
+
136
+ # 4. Add to [Content_Types].xml
137
+ _add_to_content_types(unpacked_dir, dest)
138
+
139
+ # 5. Add relationship to presentation.xml.rels
140
+ rid = _add_to_presentation_rels(unpacked_dir, dest)
141
+
142
+ # 6. Get next slide ID
143
+ next_slide_id = _get_next_slide_id(unpacked_dir)
144
+
145
+ # Output
146
+ print(f"Created {dest} from {source}")
147
+ print(f'Add to presentation.xml <p:sldIdLst>: <p:sldId id="{next_slide_id}" r:id="{rid}"/>')
148
+
149
+
150
+ def _add_to_content_types(unpacked_dir: Path, dest: str) -> None:
151
+ """Add new slide to [Content_Types].xml."""
152
+ content_types_path = unpacked_dir / "[Content_Types].xml"
153
+ content_types = content_types_path.read_text(encoding="utf-8")
154
+
155
+ new_override = f'<Override PartName="/ppt/slides/{dest}" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>'
156
+
157
+ if f"/ppt/slides/{dest}" not in content_types:
158
+ content_types = content_types.replace("</Types>", f" {new_override}\n</Types>")
159
+ content_types_path.write_text(content_types, encoding="utf-8")
160
+
161
+
162
+ def _add_to_presentation_rels(unpacked_dir: Path, dest: str) -> str:
163
+ """Add relationship to presentation.xml.rels. Returns the new rId."""
164
+ pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
165
+ pres_rels = pres_rels_path.read_text(encoding="utf-8")
166
+
167
+ rids = [int(m) for m in re.findall(r'Id="rId(\d+)"', pres_rels)]
168
+ next_rid = max(rids) + 1 if rids else 1
169
+ rid = f"rId{next_rid}"
170
+
171
+ new_rel = f'<Relationship Id="{rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/{dest}"/>'
172
+
173
+ if f"slides/{dest}" not in pres_rels:
174
+ pres_rels = pres_rels.replace("</Relationships>", f" {new_rel}\n</Relationships>")
175
+ pres_rels_path.write_text(pres_rels, encoding="utf-8")
176
+
177
+ return rid
178
+
179
+
180
+ def _get_next_slide_id(unpacked_dir: Path) -> int:
181
+ """Get the next available slide ID for presentation.xml."""
182
+ pres_path = unpacked_dir / "ppt" / "presentation.xml"
183
+ pres_content = pres_path.read_text(encoding="utf-8")
184
+ slide_ids = [int(m) for m in re.findall(r'<p:sldId[^>]*id="(\d+)"', pres_content)]
185
+ return max(slide_ids) + 1 if slide_ids else 256
186
+
187
+
188
+ def parse_source(source: str) -> tuple[str, str | None]:
189
+ """Parse source argument to determine if it's a slide or layout.
190
+
191
+ Returns:
192
+ ("slide", None) if source is a slide file like "slide2.xml"
193
+ ("layout", filename) if source is a layout like "slideLayout2.xml"
194
+ """
195
+ if source.startswith("slideLayout") and source.endswith(".xml"):
196
+ return ("layout", source)
197
+
198
+ # Otherwise treat as a slide file
199
+ return ("slide", None)
200
+
201
+
202
+ if __name__ == "__main__":
203
+ if len(sys.argv) != 3:
204
+ print("Usage: python add_slide.py <unpacked_dir> <source>", file=sys.stderr)
205
+ print("", file=sys.stderr)
206
+ print("Source can be:", file=sys.stderr)
207
+ print(" slide2.xml - duplicate an existing slide", file=sys.stderr)
208
+ print(" slideLayout2.xml - create from a layout template", file=sys.stderr)
209
+ print("", file=sys.stderr)
210
+ print("To see available layouts: ls <unpacked_dir>/ppt/slideLayouts/", file=sys.stderr)
211
+ sys.exit(1)
212
+
213
+ unpacked_dir = Path(sys.argv[1])
214
+ source = sys.argv[2]
215
+
216
+ if not unpacked_dir.exists():
217
+ print(f"Error: {unpacked_dir} not found", file=sys.stderr)
218
+ sys.exit(1)
219
+
220
+ source_type, layout_file = parse_source(source)
221
+
222
+ if source_type == "layout" and layout_file is not None:
223
+ create_slide_from_layout(unpacked_dir, layout_file)
224
+ else:
225
+ duplicate_slide(unpacked_dir, source)
@@ -0,0 +1,309 @@
1
+ #!/usr/bin/env python3
2
+ """Remove unreferenced files from an unpacked PPTX directory.
3
+
4
+ Usage: python clean.py <unpacked_dir>
5
+
6
+ Example:
7
+ python clean.py unpacked/
8
+
9
+ This script removes:
10
+ - Orphaned slides (not in sldIdLst) and their relationships
11
+ - [trash] directory (unreferenced files)
12
+ - Orphaned .rels files for deleted resources
13
+ - Unreferenced media, embeddings, charts, diagrams, drawings, ink files
14
+ - Unreferenced theme files
15
+ - Unreferenced notes slides
16
+ - Content-Type overrides for deleted files
17
+ """
18
+
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ import defusedxml.minidom
23
+
24
+
25
+ import re
26
+
27
+
28
+ def get_slides_in_sldidlst(unpacked_dir: Path) -> set[str]:
29
+ """Get slide filenames referenced in presentation.xml sldIdLst."""
30
+ pres_path = unpacked_dir / "ppt" / "presentation.xml"
31
+ pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
32
+
33
+ if not pres_path.exists() or not pres_rels_path.exists():
34
+ return set()
35
+
36
+ # Build rId -> slide filename mapping from presentation.xml.rels
37
+ rels_dom = defusedxml.minidom.parse(str(pres_rels_path))
38
+ rid_to_slide = {}
39
+ for rel in rels_dom.getElementsByTagName("Relationship"):
40
+ rid = rel.getAttribute("Id")
41
+ target = rel.getAttribute("Target")
42
+ rel_type = rel.getAttribute("Type")
43
+ if "slide" in rel_type and target.startswith("slides/"):
44
+ rid_to_slide[rid] = target.replace("slides/", "")
45
+
46
+ # Get rIds from sldIdLst in presentation.xml
47
+ pres_content = pres_path.read_text(encoding="utf-8")
48
+ referenced_rids = set(re.findall(r'<p:sldId[^>]*r:id="([^"]+)"', pres_content))
49
+
50
+ # Return slide filenames that are in sldIdLst
51
+ return {rid_to_slide[rid] for rid in referenced_rids if rid in rid_to_slide}
52
+
53
+
54
+ def remove_orphaned_slides(unpacked_dir: Path) -> list[str]:
55
+ """Remove slides not referenced in sldIdLst."""
56
+ slides_dir = unpacked_dir / "ppt" / "slides"
57
+ slides_rels_dir = slides_dir / "_rels"
58
+ pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
59
+
60
+ if not slides_dir.exists():
61
+ return []
62
+
63
+ referenced_slides = get_slides_in_sldidlst(unpacked_dir)
64
+ removed = []
65
+
66
+ # Find and remove orphaned slide files
67
+ for slide_file in slides_dir.glob("slide*.xml"):
68
+ if slide_file.name not in referenced_slides:
69
+ # Remove slide file
70
+ rel_path = slide_file.relative_to(unpacked_dir)
71
+ slide_file.unlink()
72
+ removed.append(str(rel_path))
73
+
74
+ # Remove slide rels file
75
+ rels_file = slides_rels_dir / f"{slide_file.name}.rels"
76
+ if rels_file.exists():
77
+ rels_file.unlink()
78
+ removed.append(str(rels_file.relative_to(unpacked_dir)))
79
+
80
+ # Remove relationships from presentation.xml.rels for deleted slides
81
+ if removed and pres_rels_path.exists():
82
+ rels_dom = defusedxml.minidom.parse(str(pres_rels_path))
83
+ changed = False
84
+
85
+ for rel in list(rels_dom.getElementsByTagName("Relationship")):
86
+ target = rel.getAttribute("Target")
87
+ if target.startswith("slides/"):
88
+ slide_name = target.replace("slides/", "")
89
+ if slide_name not in referenced_slides:
90
+ if rel.parentNode:
91
+ rel.parentNode.removeChild(rel)
92
+ changed = True
93
+
94
+ if changed:
95
+ with open(pres_rels_path, "wb") as f:
96
+ f.write(rels_dom.toxml(encoding="utf-8"))
97
+
98
+ return removed
99
+
100
+
101
+ def remove_trash_directory(unpacked_dir: Path) -> list[str]:
102
+ """Remove [trash] directory if it exists."""
103
+ trash_dir = unpacked_dir / "[trash]"
104
+ removed = []
105
+
106
+ if trash_dir.exists() and trash_dir.is_dir():
107
+ for file_path in trash_dir.iterdir():
108
+ if file_path.is_file():
109
+ rel_path = file_path.relative_to(unpacked_dir)
110
+ removed.append(str(rel_path))
111
+ file_path.unlink()
112
+ trash_dir.rmdir()
113
+
114
+ return removed
115
+
116
+
117
+ def get_slide_referenced_files(unpacked_dir: Path) -> set:
118
+ """Get files referenced directly from slides."""
119
+ referenced = set()
120
+ slides_rels_dir = unpacked_dir / "ppt" / "slides" / "_rels"
121
+
122
+ if not slides_rels_dir.exists():
123
+ return referenced
124
+
125
+ for rels_file in slides_rels_dir.glob("*.rels"):
126
+ dom = defusedxml.minidom.parse(str(rels_file))
127
+ for rel in dom.getElementsByTagName("Relationship"):
128
+ target = rel.getAttribute("Target")
129
+ if not target:
130
+ continue
131
+ target_path = (rels_file.parent.parent / target).resolve()
132
+ try:
133
+ referenced.add(target_path.relative_to(unpacked_dir.resolve()))
134
+ except ValueError:
135
+ pass
136
+
137
+ return referenced
138
+
139
+
140
+ def remove_orphaned_rels_files(unpacked_dir: Path) -> list[str]:
141
+ """Remove .rels files for unreferenced resources."""
142
+ resource_dirs = ["charts", "diagrams", "drawings"]
143
+ removed = []
144
+ slide_referenced = get_slide_referenced_files(unpacked_dir)
145
+
146
+ for dir_name in resource_dirs:
147
+ rels_dir = unpacked_dir / "ppt" / dir_name / "_rels"
148
+ if not rels_dir.exists():
149
+ continue
150
+
151
+ for rels_file in rels_dir.glob("*.rels"):
152
+ resource_file = rels_dir.parent / rels_file.name.replace(".rels", "")
153
+ try:
154
+ resource_rel_path = resource_file.resolve().relative_to(unpacked_dir.resolve())
155
+ except ValueError:
156
+ continue
157
+
158
+ if not resource_file.exists() or resource_rel_path not in slide_referenced:
159
+ rels_file.unlink()
160
+ rel_path = rels_file.relative_to(unpacked_dir)
161
+ removed.append(str(rel_path))
162
+
163
+ return removed
164
+
165
+
166
+ def get_referenced_files(unpacked_dir: Path) -> set:
167
+ """Get all files referenced in .rels files."""
168
+ referenced = set()
169
+
170
+ for rels_file in unpacked_dir.rglob("*.rels"):
171
+ dom = defusedxml.minidom.parse(str(rels_file))
172
+ for rel in dom.getElementsByTagName("Relationship"):
173
+ target = rel.getAttribute("Target")
174
+ if not target:
175
+ continue
176
+ target_path = (rels_file.parent.parent / target).resolve()
177
+ try:
178
+ referenced.add(target_path.relative_to(unpacked_dir.resolve()))
179
+ except ValueError:
180
+ pass
181
+
182
+ return referenced
183
+
184
+
185
+ def remove_orphaned_files(unpacked_dir: Path, referenced: set) -> list[str]:
186
+ """Remove files not in the referenced set."""
187
+ resource_dirs = ["media", "embeddings", "charts", "diagrams", "tags", "drawings", "ink"]
188
+ removed = []
189
+
190
+ for dir_name in resource_dirs:
191
+ dir_path = unpacked_dir / "ppt" / dir_name
192
+ if not dir_path.exists():
193
+ continue
194
+
195
+ for file_path in dir_path.glob("*"):
196
+ if not file_path.is_file():
197
+ continue
198
+ rel_path = file_path.relative_to(unpacked_dir)
199
+ if rel_path not in referenced:
200
+ file_path.unlink()
201
+ removed.append(str(rel_path))
202
+
203
+ # Clean up unreferenced theme files
204
+ theme_dir = unpacked_dir / "ppt" / "theme"
205
+ if theme_dir.exists():
206
+ for file_path in theme_dir.glob("theme*.xml"):
207
+ rel_path = file_path.relative_to(unpacked_dir)
208
+ if rel_path not in referenced:
209
+ file_path.unlink()
210
+ removed.append(str(rel_path))
211
+ # Also remove corresponding .rels
212
+ theme_rels = theme_dir / "_rels" / f"{file_path.name}.rels"
213
+ if theme_rels.exists():
214
+ theme_rels.unlink()
215
+ removed.append(str(theme_rels.relative_to(unpacked_dir)))
216
+
217
+ # Remove orphaned notes slides
218
+ notes_dir = unpacked_dir / "ppt" / "notesSlides"
219
+ if notes_dir.exists():
220
+ for file_path in notes_dir.glob("*.xml"):
221
+ if not file_path.is_file():
222
+ continue
223
+ rel_path = file_path.relative_to(unpacked_dir)
224
+ if rel_path not in referenced:
225
+ file_path.unlink()
226
+ removed.append(str(rel_path))
227
+
228
+ notes_rels_dir = notes_dir / "_rels"
229
+ if notes_rels_dir.exists():
230
+ for file_path in notes_rels_dir.glob("*.rels"):
231
+ notes_file = notes_dir / file_path.name.replace(".rels", "")
232
+ if not notes_file.exists():
233
+ file_path.unlink()
234
+ removed.append(str(file_path.relative_to(unpacked_dir)))
235
+
236
+ return removed
237
+
238
+
239
+ def update_content_types(unpacked_dir: Path, removed_files: list[str]) -> None:
240
+ """Remove Content-Type overrides for deleted files."""
241
+ ct_path = unpacked_dir / "[Content_Types].xml"
242
+ if not ct_path.exists():
243
+ return
244
+
245
+ dom = defusedxml.minidom.parse(str(ct_path))
246
+ changed = False
247
+
248
+ for override in list(dom.getElementsByTagName("Override")):
249
+ part_name = override.getAttribute("PartName").lstrip("/")
250
+ if part_name in removed_files:
251
+ if override.parentNode:
252
+ override.parentNode.removeChild(override)
253
+ changed = True
254
+
255
+ if changed:
256
+ with open(ct_path, "wb") as f:
257
+ f.write(dom.toxml(encoding="utf-8"))
258
+
259
+
260
+ def clean_unused_files(unpacked_dir: Path) -> list[str]:
261
+ """Remove all unreferenced files from the unpacked directory."""
262
+ all_removed = []
263
+
264
+ # Remove orphaned slides first (not in sldIdLst)
265
+ slides_removed = remove_orphaned_slides(unpacked_dir)
266
+ all_removed.extend(slides_removed)
267
+
268
+ # Remove [trash] directory
269
+ trash_removed = remove_trash_directory(unpacked_dir)
270
+ all_removed.extend(trash_removed)
271
+
272
+ # Keep cleaning until nothing more is removed
273
+ while True:
274
+ removed_rels = remove_orphaned_rels_files(unpacked_dir)
275
+ referenced = get_referenced_files(unpacked_dir)
276
+ removed_files = remove_orphaned_files(unpacked_dir, referenced)
277
+
278
+ total_removed = removed_rels + removed_files
279
+ if not total_removed:
280
+ break
281
+
282
+ all_removed.extend(total_removed)
283
+
284
+ if all_removed:
285
+ update_content_types(unpacked_dir, all_removed)
286
+
287
+ return all_removed
288
+
289
+
290
+ if __name__ == "__main__":
291
+ if len(sys.argv) != 2:
292
+ print("Usage: python clean.py <unpacked_dir>", file=sys.stderr)
293
+ print("Example: python clean.py unpacked/", file=sys.stderr)
294
+ sys.exit(1)
295
+
296
+ unpacked_dir = Path(sys.argv[1])
297
+
298
+ if not unpacked_dir.exists():
299
+ print(f"Error: {unpacked_dir} not found", file=sys.stderr)
300
+ sys.exit(1)
301
+
302
+ removed = clean_unused_files(unpacked_dir)
303
+
304
+ if removed:
305
+ print(f"Removed {len(removed)} unreferenced files:")
306
+ for f in removed:
307
+ print(f" {f}")
308
+ else:
309
+ print("No unreferenced files found")