extraslide 0.1.0__tar.gz

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 (177) hide show
  1. extraslide-0.1.0/.gitignore +45 -0
  2. extraslide-0.1.0/.python-version +1 -0
  3. extraslide-0.1.0/CHANGELOG.md +28 -0
  4. extraslide-0.1.0/CLAUDE.md +178 -0
  5. extraslide-0.1.0/LICENSE +21 -0
  6. extraslide-0.1.0/PKG-INFO +272 -0
  7. extraslide-0.1.0/README.md +238 -0
  8. extraslide-0.1.0/docs/copy-workflow.md +177 -0
  9. extraslide-0.1.0/docs/googleslides/concepts/page-elements.md +161 -0
  10. extraslide-0.1.0/docs/googleslides/concepts/text.md +268 -0
  11. extraslide-0.1.0/docs/googleslides/concepts/transforms.md +160 -0
  12. extraslide-0.1.0/docs/googleslides/guides/add-chart.md +193 -0
  13. extraslide-0.1.0/docs/googleslides/guides/add-image.md +194 -0
  14. extraslide-0.1.0/docs/googleslides/guides/add-shape.md +233 -0
  15. extraslide-0.1.0/docs/googleslides/guides/batch.md +292 -0
  16. extraslide-0.1.0/docs/googleslides/guides/create-slide.md +196 -0
  17. extraslide-0.1.0/docs/googleslides/guides/field-masks.md +223 -0
  18. extraslide-0.1.0/docs/googleslides/guides/merge.md +272 -0
  19. extraslide-0.1.0/docs/googleslides/guides/notes.md +202 -0
  20. extraslide-0.1.0/docs/googleslides/guides/overview.md +93 -0
  21. extraslide-0.1.0/docs/googleslides/guides/performance.md +228 -0
  22. extraslide-0.1.0/docs/googleslides/guides/presentations.md +155 -0
  23. extraslide-0.1.0/docs/googleslides/guides/styling.md +299 -0
  24. extraslide-0.1.0/docs/googleslides/guides/transform.md +250 -0
  25. extraslide-0.1.0/docs/googleslides/index.md +189 -0
  26. extraslide-0.1.0/docs/googleslides/reference/objects/affine-transform.md +278 -0
  27. extraslide-0.1.0/docs/googleslides/reference/objects/auto-text.md +33 -0
  28. extraslide-0.1.0/docs/googleslides/reference/objects/autofit.md +31 -0
  29. extraslide-0.1.0/docs/googleslides/reference/objects/bullet.md +28 -0
  30. extraslide-0.1.0/docs/googleslides/reference/objects/color-scheme.md +22 -0
  31. extraslide-0.1.0/docs/googleslides/reference/objects/color-stop.md +26 -0
  32. extraslide-0.1.0/docs/googleslides/reference/objects/crop-properties.md +26 -0
  33. extraslide-0.1.0/docs/googleslides/reference/objects/dimension.md +28 -0
  34. extraslide-0.1.0/docs/googleslides/reference/objects/group.md +22 -0
  35. extraslide-0.1.0/docs/googleslides/reference/objects/image-properties.md +40 -0
  36. extraslide-0.1.0/docs/googleslides/reference/objects/image.md +29 -0
  37. extraslide-0.1.0/docs/googleslides/reference/objects/index.md +103 -0
  38. extraslide-0.1.0/docs/googleslides/reference/objects/layout-placeholder-id-mapping.md +26 -0
  39. extraslide-0.1.0/docs/googleslides/reference/objects/layout-properties.md +22 -0
  40. extraslide-0.1.0/docs/googleslides/reference/objects/layout-reference.md +37 -0
  41. extraslide-0.1.0/docs/googleslides/reference/objects/line-connection.md +20 -0
  42. extraslide-0.1.0/docs/googleslides/reference/objects/line-fill.md +22 -0
  43. extraslide-0.1.0/docs/googleslides/reference/objects/line-properties.md +83 -0
  44. extraslide-0.1.0/docs/googleslides/reference/objects/line.md +51 -0
  45. extraslide-0.1.0/docs/googleslides/reference/objects/link.md +34 -0
  46. extraslide-0.1.0/docs/googleslides/reference/objects/list.md +20 -0
  47. extraslide-0.1.0/docs/googleslides/reference/objects/master-properties.md +18 -0
  48. extraslide-0.1.0/docs/googleslides/reference/objects/nesting-level.md +22 -0
  49. extraslide-0.1.0/docs/googleslides/reference/objects/notes-properties.md +18 -0
  50. extraslide-0.1.0/docs/googleslides/reference/objects/opaque-color.md +46 -0
  51. extraslide-0.1.0/docs/googleslides/reference/objects/optional-color.md +22 -0
  52. extraslide-0.1.0/docs/googleslides/reference/objects/outline-fill.md +22 -0
  53. extraslide-0.1.0/docs/googleslides/reference/objects/outline.md +49 -0
  54. extraslide-0.1.0/docs/googleslides/reference/objects/page-background-fill.md +35 -0
  55. extraslide-0.1.0/docs/googleslides/reference/objects/page-element-properties.md +27 -0
  56. extraslide-0.1.0/docs/googleslides/reference/objects/page-element.md +249 -0
  57. extraslide-0.1.0/docs/googleslides/reference/objects/page-properties.md +25 -0
  58. extraslide-0.1.0/docs/googleslides/reference/objects/page.md +212 -0
  59. extraslide-0.1.0/docs/googleslides/reference/objects/paragraph-marker.md +25 -0
  60. extraslide-0.1.0/docs/googleslides/reference/objects/paragraph-style.md +64 -0
  61. extraslide-0.1.0/docs/googleslides/reference/objects/placeholder.md +44 -0
  62. extraslide-0.1.0/docs/googleslides/reference/objects/presentation.md +158 -0
  63. extraslide-0.1.0/docs/googleslides/reference/objects/range.md +31 -0
  64. extraslide-0.1.0/docs/googleslides/reference/objects/recolor.md +54 -0
  65. extraslide-0.1.0/docs/googleslides/reference/objects/rgb-color.md +22 -0
  66. extraslide-0.1.0/docs/googleslides/reference/objects/shadow.md +68 -0
  67. extraslide-0.1.0/docs/googleslides/reference/objects/shape-background-fill.md +32 -0
  68. extraslide-0.1.0/docs/googleslides/reference/objects/shape-properties.md +46 -0
  69. extraslide-0.1.0/docs/googleslides/reference/objects/shape.md +276 -0
  70. extraslide-0.1.0/docs/googleslides/reference/objects/sheets-chart-properties.md +22 -0
  71. extraslide-0.1.0/docs/googleslides/reference/objects/sheets-chart.md +28 -0
  72. extraslide-0.1.0/docs/googleslides/reference/objects/size.md +24 -0
  73. extraslide-0.1.0/docs/googleslides/reference/objects/slide-properties.md +28 -0
  74. extraslide-0.1.0/docs/googleslides/reference/objects/solid-fill.md +24 -0
  75. extraslide-0.1.0/docs/googleslides/reference/objects/speaker-spotlight-properties.md +25 -0
  76. extraslide-0.1.0/docs/googleslides/reference/objects/speaker-spotlight.md +22 -0
  77. extraslide-0.1.0/docs/googleslides/reference/objects/stretched-picture-fill.md +24 -0
  78. extraslide-0.1.0/docs/googleslides/reference/objects/substring-match-criteria.md +22 -0
  79. extraslide-0.1.0/docs/googleslides/reference/objects/table-border-cell.md +25 -0
  80. extraslide-0.1.0/docs/googleslides/reference/objects/table-border-fill.md +22 -0
  81. extraslide-0.1.0/docs/googleslides/reference/objects/table-border-properties.md +39 -0
  82. extraslide-0.1.0/docs/googleslides/reference/objects/table-border-row.md +22 -0
  83. extraslide-0.1.0/docs/googleslides/reference/objects/table-cell-background-fill.md +32 -0
  84. extraslide-0.1.0/docs/googleslides/reference/objects/table-cell-location.md +20 -0
  85. extraslide-0.1.0/docs/googleslides/reference/objects/table-cell-properties.md +34 -0
  86. extraslide-0.1.0/docs/googleslides/reference/objects/table-cell.md +32 -0
  87. extraslide-0.1.0/docs/googleslides/reference/objects/table-column-properties.md +22 -0
  88. extraslide-0.1.0/docs/googleslides/reference/objects/table-range.md +26 -0
  89. extraslide-0.1.0/docs/googleslides/reference/objects/table-row-properties.md +22 -0
  90. extraslide-0.1.0/docs/googleslides/reference/objects/table-row.md +28 -0
  91. extraslide-0.1.0/docs/googleslides/reference/objects/table.md +34 -0
  92. extraslide-0.1.0/docs/googleslides/reference/objects/text-content.md +284 -0
  93. extraslide-0.1.0/docs/googleslides/reference/objects/text-element.md +32 -0
  94. extraslide-0.1.0/docs/googleslides/reference/objects/text-run.md +24 -0
  95. extraslide-0.1.0/docs/googleslides/reference/objects/text-style.md +56 -0
  96. extraslide-0.1.0/docs/googleslides/reference/objects/theme-color-pair.md +46 -0
  97. extraslide-0.1.0/docs/googleslides/reference/objects/thumbnail.md +22 -0
  98. extraslide-0.1.0/docs/googleslides/reference/objects/video-properties.md +30 -0
  99. extraslide-0.1.0/docs/googleslides/reference/objects/video.md +36 -0
  100. extraslide-0.1.0/docs/googleslides/reference/objects/weighted-font-family.md +20 -0
  101. extraslide-0.1.0/docs/googleslides/reference/objects/word-art.md +18 -0
  102. extraslide-0.1.0/docs/googleslides/reference/objects/write-control.md +18 -0
  103. extraslide-0.1.0/docs/googleslides/reference/requests/create-image.md +42 -0
  104. extraslide-0.1.0/docs/googleslides/reference/requests/create-line.md +61 -0
  105. extraslide-0.1.0/docs/googleslides/reference/requests/create-paragraph-bullets.md +65 -0
  106. extraslide-0.1.0/docs/googleslides/reference/requests/create-shape.md +202 -0
  107. extraslide-0.1.0/docs/googleslides/reference/requests/create-sheets-chart.md +53 -0
  108. extraslide-0.1.0/docs/googleslides/reference/requests/create-slide.md +45 -0
  109. extraslide-0.1.0/docs/googleslides/reference/requests/create-table.md +44 -0
  110. extraslide-0.1.0/docs/googleslides/reference/requests/create-video.md +52 -0
  111. extraslide-0.1.0/docs/googleslides/reference/requests/delete-object.md +179 -0
  112. extraslide-0.1.0/docs/googleslides/reference/requests/delete-paragraph-bullets.md +43 -0
  113. extraslide-0.1.0/docs/googleslides/reference/requests/delete-table-column.md +40 -0
  114. extraslide-0.1.0/docs/googleslides/reference/requests/delete-table-row.md +40 -0
  115. extraslide-0.1.0/docs/googleslides/reference/requests/delete-text.md +43 -0
  116. extraslide-0.1.0/docs/googleslides/reference/requests/duplicate-object.md +36 -0
  117. extraslide-0.1.0/docs/googleslides/reference/requests/group-objects.md +36 -0
  118. extraslide-0.1.0/docs/googleslides/reference/requests/index.md +81 -0
  119. extraslide-0.1.0/docs/googleslides/reference/requests/insert-table-columns.md +44 -0
  120. extraslide-0.1.0/docs/googleslides/reference/requests/insert-table-rows.md +44 -0
  121. extraslide-0.1.0/docs/googleslides/reference/requests/insert-text.md +231 -0
  122. extraslide-0.1.0/docs/googleslides/reference/requests/merge-table-cells.md +40 -0
  123. extraslide-0.1.0/docs/googleslides/reference/requests/refresh-sheets-chart.md +34 -0
  124. extraslide-0.1.0/docs/googleslides/reference/requests/replace-all-shapes-with-image.md +61 -0
  125. extraslide-0.1.0/docs/googleslides/reference/requests/replace-all-shapes-with-sheets-chart.md +53 -0
  126. extraslide-0.1.0/docs/googleslides/reference/requests/replace-all-text.md +42 -0
  127. extraslide-0.1.0/docs/googleslides/reference/requests/replace-image.md +46 -0
  128. extraslide-0.1.0/docs/googleslides/reference/requests/reroute-line.md +34 -0
  129. extraslide-0.1.0/docs/googleslides/reference/requests/ungroup-objects.md +34 -0
  130. extraslide-0.1.0/docs/googleslides/reference/requests/unmerge-table-cells.md +40 -0
  131. extraslide-0.1.0/docs/googleslides/reference/requests/update-image-properties.md +42 -0
  132. extraslide-0.1.0/docs/googleslides/reference/requests/update-line-category.md +45 -0
  133. extraslide-0.1.0/docs/googleslides/reference/requests/update-line-properties.md +42 -0
  134. extraslide-0.1.0/docs/googleslides/reference/requests/update-page-element-alt-text.md +38 -0
  135. extraslide-0.1.0/docs/googleslides/reference/requests/update-page-element-transform.md +50 -0
  136. extraslide-0.1.0/docs/googleslides/reference/requests/update-page-elements-z-order.md +46 -0
  137. extraslide-0.1.0/docs/googleslides/reference/requests/update-page-properties.md +42 -0
  138. extraslide-0.1.0/docs/googleslides/reference/requests/update-paragraph-style.md +48 -0
  139. extraslide-0.1.0/docs/googleslides/reference/requests/update-shape-properties.md +42 -0
  140. extraslide-0.1.0/docs/googleslides/reference/requests/update-slide-properties.md +42 -0
  141. extraslide-0.1.0/docs/googleslides/reference/requests/update-slides-position.md +36 -0
  142. extraslide-0.1.0/docs/googleslides/reference/requests/update-table-border-properties.md +61 -0
  143. extraslide-0.1.0/docs/googleslides/reference/requests/update-table-cell-properties.md +45 -0
  144. extraslide-0.1.0/docs/googleslides/reference/requests/update-table-column-properties.md +44 -0
  145. extraslide-0.1.0/docs/googleslides/reference/requests/update-table-row-properties.md +44 -0
  146. extraslide-0.1.0/docs/googleslides/reference/requests/update-text-style.md +275 -0
  147. extraslide-0.1.0/docs/googleslides/reference/requests/update-transform.md +254 -0
  148. extraslide-0.1.0/docs/googleslides/reference/requests/update-video-properties.md +42 -0
  149. extraslide-0.1.0/docs/googleslides/reference/rest-api.md +228 -0
  150. extraslide-0.1.0/docs/markup-syntax-design.md +2539 -0
  151. extraslide-0.1.0/docs/sml-reconciliation-spec.md +2237 -0
  152. extraslide-0.1.0/pyproject.toml +108 -0
  153. extraslide-0.1.0/scripts/analyze_discovery.py +677 -0
  154. extraslide-0.1.0/src/extraslide/__init__.py +29 -0
  155. extraslide-0.1.0/src/extraslide/__main__.py +202 -0
  156. extraslide-0.1.0/src/extraslide/bounds.py +243 -0
  157. extraslide-0.1.0/src/extraslide/classes.py +858 -0
  158. extraslide-0.1.0/src/extraslide/client.py +320 -0
  159. extraslide-0.1.0/src/extraslide/content_diff.py +520 -0
  160. extraslide-0.1.0/src/extraslide/content_generator.py +333 -0
  161. extraslide-0.1.0/src/extraslide/content_parser.py +194 -0
  162. extraslide-0.1.0/src/extraslide/content_requests.py +1373 -0
  163. extraslide-0.1.0/src/extraslide/id_manager.py +161 -0
  164. extraslide-0.1.0/src/extraslide/py.typed +0 -0
  165. extraslide-0.1.0/src/extraslide/render_tree.py +269 -0
  166. extraslide-0.1.0/src/extraslide/slide_processor.py +183 -0
  167. extraslide-0.1.0/src/extraslide/style_extractor.py +439 -0
  168. extraslide-0.1.0/src/extraslide/transport.py +237 -0
  169. extraslide-0.1.0/src/extraslide/units.py +88 -0
  170. extraslide-0.1.0/tests/__init__.py +0 -0
  171. extraslide-0.1.0/tests/golden/simple_presentation/presentation.json +38505 -0
  172. extraslide-0.1.0/tests/test_classes.py +519 -0
  173. extraslide-0.1.0/tests/test_content_diff.py +161 -0
  174. extraslide-0.1.0/tests/test_slide_processor.py +330 -0
  175. extraslide-0.1.0/tests/test_transport.py +118 -0
  176. extraslide-0.1.0/tests/test_units.py +123 -0
  177. extraslide-0.1.0/uv.lock +712 -0
@@ -0,0 +1,45 @@
1
+ # Output directory for manual testing
2
+ output/
3
+
4
+ # Python
5
+ __pycache__/
6
+ *.py[cod]
7
+ *$py.class
8
+ *.so
9
+ .Python
10
+ build/
11
+ develop-eggs/
12
+ dist/
13
+ downloads/
14
+ eggs/
15
+ .eggs/
16
+ lib/
17
+ lib64/
18
+ parts/
19
+ sdist/
20
+ var/
21
+ wheels/
22
+ *.egg-info/
23
+ .installed.cfg
24
+ *.egg
25
+
26
+ # Virtual environments
27
+ .venv/
28
+ venv/
29
+ ENV/
30
+
31
+ # IDE
32
+ .idea/
33
+ .vscode/
34
+ *.swp
35
+ *.swo
36
+
37
+ # Testing
38
+ .coverage
39
+ htmlcov/
40
+ .pytest_cache/
41
+ .mypy_cache/
42
+
43
+ # OS
44
+ .DS_Store
45
+ Thumbs.db
@@ -0,0 +1 @@
1
+ 3.13
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ All notable changes to the extraslide library will be documented in this file.
4
+
5
+ ## [0.1.0] - 2026-02-11
6
+
7
+ ### Added
8
+
9
+ - Initial release (alpha)
10
+ - **Pull**: Download Google Slides to a local folder with SML (Slide Markup Language) format
11
+ - Per-slide `content.sml` files in minimal XML
12
+ - `styles.json` - Style definitions (position, fill, stroke, text) per element
13
+ - `id_mapping.json` - Clean ID to Google object ID mapping
14
+ - `presentation.json` - Metadata (title, dimensions)
15
+ - `.pristine/` snapshot for diff comparison
16
+ - **Diff**: Compare edited SML against pristine state and generate `batchUpdate` JSON
17
+ - **Push**: Apply changes to Google Slides via the API
18
+ - **Copy-based workflow**: Duplicate elements by repeating XML with same ID but only x,y coordinates (omit w,h to signal copy)
19
+ - Cross-slide and same-slide copy detection
20
+ - Translation-based positioning
21
+ - Full shape support including images with native dimensions
22
+ - Unique suffix generation to prevent ID collisions
23
+ - **Supported operations**:
24
+ - Text replacement and modification
25
+ - Element copying with position translation
26
+ - Content alignment extraction and application
27
+ - Async transport-based architecture with `GoogleSlidesTransport` and `LocalFileTransport` for testing
28
+ - Golden file testing infrastructure
@@ -0,0 +1,178 @@
1
+ ## Overview
2
+
3
+ Python library that converts Google Slides to/from SML (Slide Markup Language), an XML-based format optimized for LLM editing. Implements the pull/diff/push workflow.
4
+
5
+ ## Key Files
6
+
7
+ | File | Purpose |
8
+ |------|---------|
9
+ | `src/extraslide/client.py` | `SlidesClient` - main API with pull/diff/push |
10
+ | `src/extraslide/slide_processor.py` | Builds render trees, extracts styles |
11
+ | `src/extraslide/content_generator.py` | Generates minimal SML from render trees |
12
+ | `src/extraslide/content_parser.py` | Parses SML content files |
13
+ | `src/extraslide/content_diff.py` | Detects copies, calculates translations |
14
+ | `src/extraslide/content_requests.py` | Generates batchUpdate requests |
15
+ | `src/extraslide/style_extractor.py` | Extracts styles to JSON |
16
+ | `src/extraslide/render_tree.py` | Visual containment hierarchy |
17
+ | `src/extraslide/id_manager.py` | Clean ID assignment and mapping |
18
+ | `src/extraslide/transport.py` | `Transport` ABC, `GoogleSlidesTransport`, `LocalFileTransport` |
19
+ | `src/extraslide/classes.py` | Data classes for slide elements (Color, Fill, Stroke, etc.) |
20
+ | `src/extraslide/credentials.py` | `CredentialsManager` for OAuth token handling |
21
+ | `src/extraslide/units.py` | EMU/pt conversion utilities |
22
+
23
+ ## Documentation
24
+
25
+ - `docs/copy-workflow.md` - Copy-based editing workflow (agent guide)
26
+ - `docs/markup-syntax-design.md` - SML format specification
27
+ - `docs/sml-reconciliation-spec.md` - How diff/push reconciles changes
28
+
29
+ ## CLI Interface
30
+
31
+ ```bash
32
+ # Download a presentation to local folder
33
+ python -m extraslide pull <presentation_url_or_id> [output_dir]
34
+ # Output: ./<presentation_id>/ or specified output_dir
35
+
36
+ # Options:
37
+ # --no-raw Don't save raw API response to .raw/ folder
38
+
39
+ # Preview changes (dry run)
40
+ python -m extraslide diff <folder>
41
+ # Output: batchUpdate JSON to stdout
42
+
43
+ # Apply changes to Google Slides
44
+ python -m extraslide push <folder>
45
+ # Output: Success message with change count
46
+ ```
47
+
48
+ Also works via `uvx extraslide pull/diff/push`.
49
+
50
+ ## Folder Structure
51
+
52
+ After `pull`:
53
+ ```
54
+ <presentation_id>/
55
+ presentation.json # Metadata (title, presentation ID, dimensions)
56
+ id_mapping.json # clean_id -> google_object_id
57
+ styles.json # clean_id -> styles (position, fill, stroke, text)
58
+ slides/
59
+ 01/content.sml # Slide 1 content (minimal XML)
60
+ 02/content.sml # Slide 2 content
61
+ ...
62
+ .raw/
63
+ presentation.json # Raw API response (for debugging)
64
+ .pristine/
65
+ presentation.zip # Zip of entire folder for diff comparison
66
+ ```
67
+
68
+ Edit `slides/NN/content.sml` files. To copy elements, duplicate XML with same ID but only x,y (omit w,h). See `docs/copy-workflow.md`.
69
+
70
+ ## Development
71
+
72
+ ```bash
73
+ cd extraslide
74
+ uv sync --all-extras
75
+ uv run pytest tests/ -v
76
+ uv run ruff check . && uv run ruff format .
77
+ uv run mypy src/extraslide
78
+ ```
79
+
80
+ ## Testing
81
+
82
+ Tests are in `tests/` and focus on:
83
+ - `test_content_diff.py` - Copy detection and diff logic
84
+ - `test_slide_processor.py` - Render tree building and SML generation
85
+ - `test_transport.py` - Transport layer tests
86
+ - `test_classes.py` - Data class conversions
87
+ - `test_units.py` - Unit conversions
88
+
89
+ ### Golden File Testing
90
+
91
+ Golden files enable testing without mocking or making real API calls:
92
+
93
+ ```
94
+ tests/golden/
95
+ <presentation_id>/
96
+ presentation.json # Raw API response
97
+ ```
98
+
99
+ Use `LocalFileTransport` in tests:
100
+
101
+ ```python
102
+ from extraslide import SlidesClient, LocalFileTransport
103
+
104
+ @pytest.fixture
105
+ def client():
106
+ transport = LocalFileTransport(Path("tests/golden"))
107
+ return SlidesClient(transport)
108
+
109
+ @pytest.mark.asyncio
110
+ async def test_pull(client, tmp_path):
111
+ files = await client.pull("simple_presentation", tmp_path)
112
+ assert (tmp_path / "simple_presentation" / "slides" / "01" / "content.sml").exists()
113
+ ```
114
+
115
+ ### Creating New Golden Files
116
+
117
+ 1. Create a Google Slides file with the features to test
118
+ 2. Pull it: `python -m extraslide pull <url>` (raw files saved by default)
119
+ 3. Copy `.raw/presentation.json` to `tests/golden/<name>/presentation.json`
120
+ 4. Verify the output looks correct
121
+ 5. Commit the golden files
122
+
123
+ ## Architecture Notes
124
+
125
+ ### Transport-Based Design
126
+
127
+ ```
128
+ ┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
129
+ │ SlidesClient │────▶│ Transport │────▶│ Google API / │
130
+ │ (orchestration) │ │ (data fetching) │ │ Local Files │
131
+ └─────────────────┘ └──────────────────┘ └─────────────────┘
132
+ ```
133
+
134
+ - `Transport` is an abstract base class with `get_presentation()`, `batch_update()`, `close()`
135
+ - `GoogleSlidesTransport` - Production: makes real API calls via `httpx`
136
+ - `LocalFileTransport` - Testing: reads from local golden files
137
+ - Access token is a transport concern, not a client concern
138
+
139
+ ### Pull Flow
140
+
141
+ 1. **Fetch** - `transport.get_presentation()` gets full presentation JSON
142
+ 2. **Process** - `slide_processor.process_presentation()` builds render trees
143
+ 3. **Extract styles** - Store styles (fill, stroke, text) in `styles.json`
144
+ 4. **Generate SML** - `content_generator` creates minimal XML per slide
145
+ 5. **Write** - Save files to `slides/NN/content.sml`
146
+ 6. **Save raw** - Optionally save `.raw/presentation.json`
147
+ 7. **Pristine copy** - Create `.pristine/presentation.zip` (zip of entire folder)
148
+
149
+ ### Diff/Push Flow
150
+
151
+ 1. **Read pristine** - Extract files from `.pristine/presentation.zip`
152
+ 2. **Read current** - Read `slides/NN/content.sml` files
153
+ 3. **Parse both** - `content_parser` converts SML to internal structures
154
+ 4. **Diff** - `content_diff.diff_presentation()` detects changes and copies
155
+ 5. **Generate requests** - `content_requests.generate_batch_requests()` creates API requests
156
+ 6. **Push** (if not dry-run) - Send to `transport.batch_update()`
157
+
158
+ ### Copy Detection
159
+
160
+ The copy-based workflow detects copies by:
161
+ - Same element ID appearing multiple times
162
+ - Copy has x,y but **omits w,h** (signals "copy from original")
163
+ - System duplicates the element and moves to new position
164
+
165
+ ### Key Design Decisions
166
+
167
+ - **Async-first**: All transport and client methods are async
168
+ - **Single API call for pull**: Slides API returns everything in one call
169
+ - **save_raw=True default**: Always saves raw responses for debugging/testing
170
+ - **No mocking in tests**: Use `LocalFileTransport` with golden files instead
171
+ - **Per-slide files**: Each slide is a separate content.sml for easier editing
172
+ - **Styles in JSON**: Styles stored separately, auto-applied on copy
173
+
174
+ ### Dependencies
175
+
176
+ - `httpx` - Async HTTP client for API calls
177
+ - `certifi` - SSL certificates
178
+ - `keyring` - OS keyring for token caching (via credentials.py)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Sripathi Krishnan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,272 @@
1
+ Metadata-Version: 2.4
2
+ Name: extraslide
3
+ Version: 0.1.0
4
+ Summary: A Python library that simplifies editing Google Slides through an HTML-like abstraction
5
+ Project-URL: Homepage, https://github.com/think41/extrasuite
6
+ Project-URL: Documentation, https://extrasuite.think41.com
7
+ Project-URL: Repository, https://github.com/think41/extrasuite
8
+ Project-URL: Issues, https://github.com/think41/extrasuite/issues
9
+ Author-email: Sripathi Krishnan <Sripathi.Krishnan@gmail.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Typing :: Typed
22
+ Requires-Python: >=3.10
23
+ Requires-Dist: extrasuite>=0.4.0
24
+ Requires-Dist: httpx>=0.27.0
25
+ Requires-Dist: pyfpgrowth>=1.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: mypy>=1.0.0; extra == 'dev'
28
+ Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
30
+ Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
31
+ Requires-Dist: pytest>=7.0.0; extra == 'dev'
32
+ Requires-Dist: ruff>=0.1.0; extra == 'dev'
33
+ Description-Content-Type: text/markdown
34
+
35
+ # extraslide
36
+
37
+ A Python library that converts Google Slides to/from SML (Slide Markup Language) - an XML-based format optimized for AI agent editing.
38
+
39
+ ## Overview
40
+
41
+ extraslide enables AI agents (Claude Code, Codex, etc.) to read and edit Google Slides through a simple workflow:
42
+
43
+ 1. **Pull** - Download a presentation as editable SML files
44
+ 2. **Edit** - Agent modifies the SML based on user instructions
45
+ 3. **Diff** - Preview changes before applying (dry run)
46
+ 4. **Push** - Apply changes to Google Slides
47
+
48
+ The library handles all the complexity of the Google Slides API - agents just edit XML.
49
+
50
+ ## Installation
51
+
52
+ ```bash
53
+ pip install extraslide
54
+ # or
55
+ uvx extraslide --help
56
+ ```
57
+
58
+ ## Authentication
59
+
60
+ extraslide uses [extrasuite](https://github.com/think41/extrasuite) for authentication. Each user gets a dedicated service account:
61
+
62
+ ```bash
63
+ # One-time login (opens browser)
64
+ uvx extrasuite login
65
+
66
+ # Share your Google Slides file with the service account email shown after login
67
+ ```
68
+
69
+ ## The Pull-Edit-Diff-Push Workflow
70
+
71
+ ### Step 1: Pull
72
+
73
+ Download a presentation to a local folder:
74
+
75
+ ```bash
76
+ uvx extraslide pull "https://docs.google.com/presentation/d/1abc.../edit"
77
+ ```
78
+
79
+ This creates a folder structure:
80
+
81
+ ```
82
+ 1abc.../
83
+ presentation.json # Metadata (title, ID, dimensions)
84
+ id_mapping.json # clean_id -> google_object_id mapping
85
+ styles.json # Styles for each element (position, fill, stroke, text)
86
+ slides/
87
+ 01/content.sml # Slide 1 content
88
+ 02/content.sml # Slide 2 content
89
+ ...
90
+ .pristine/
91
+ presentation.zip # Original state for diff comparison
92
+ .raw/
93
+ presentation.json # Raw API response (for debugging)
94
+ ```
95
+
96
+ ### Step 2: Edit
97
+
98
+ The agent edits `slides/NN/content.sml` files based on user instructions. SML uses a minimal XML syntax:
99
+
100
+ ```xml
101
+ <Slide>
102
+ <TextBox id="e1" x="100" y="50" w="500" h="80">
103
+ <P><T>Quarterly Report</T></P>
104
+ </TextBox>
105
+ <Rect id="e2" x="50" y="200" w="300" h="150"/>
106
+ </Slide>
107
+ ```
108
+
109
+ Common edits:
110
+ - Change text content inside `<T>` tags
111
+ - Modify positions (`x`, `y`) or sizes (`w`, `h`)
112
+ - Add/delete elements
113
+ - Copy elements (duplicate with same ID, new position, omit w/h)
114
+
115
+ See [copy-workflow.md](./docs/copy-workflow.md) for the copy-based editing guide.
116
+
117
+ ### Step 3: Diff (Preview)
118
+
119
+ See what changes will be applied without modifying the original:
120
+
121
+ ```bash
122
+ uvx extraslide diff 1abc.../
123
+ ```
124
+
125
+ This compares the current `slides/` content against the `.pristine/` copy and outputs the Google Slides API requests that would be generated. No API calls are made.
126
+
127
+ ### Step 4: Push
128
+
129
+ Apply the changes to Google Slides:
130
+
131
+ ```bash
132
+ uvx extraslide push 1abc.../
133
+ ```
134
+
135
+ The library sends a `batchUpdate` request to the Google Slides API. All edits appear in Google Drive version history with proper attribution.
136
+
137
+ ## SML Format
138
+
139
+ Each slide is stored as a separate `content.sml` file with minimal XML:
140
+
141
+ ```xml
142
+ <Slide>
143
+ <TextBox id="e1" x="100" y="50" w="500" h="80">
144
+ <P><T>Title text</T></P>
145
+ </TextBox>
146
+ <Rect id="e2" x="50" y="200" w="300" h="150"/>
147
+ <Group id="g1" x="400" y="100">
148
+ <Ellipse id="e3" x="0" y="0" w="50" h="50"/>
149
+ <Ellipse id="e4" x="60" y="0" w="50" h="50"/>
150
+ </Group>
151
+ </Slide>
152
+ ```
153
+
154
+ ### Elements
155
+
156
+ | Element | Description |
157
+ |---------|-------------|
158
+ | `<TextBox>` | Text container with paragraphs |
159
+ | `<Rect>`, `<Ellipse>`, `<RoundRect>` | Basic shapes |
160
+ | `<Line>` | Lines and connectors |
161
+ | `<Image>` | Images |
162
+ | `<Table>` | Tables with `<Row>` and `<Cell>` |
163
+ | `<Group>` | Grouped elements (children use relative positions) |
164
+ | `<Video>` | Embedded videos |
165
+ | `<SheetsChart>` | Linked Google Sheets charts |
166
+
167
+ ### Text Content
168
+
169
+ Text uses a paragraph (`<P>`) and text run (`<T>`) structure:
170
+
171
+ ```xml
172
+ <TextBox id="e1" x="50" y="100" w="400" h="200">
173
+ <P><T>Regular text </T><T>more text</T></P>
174
+ <P><T>Second paragraph</T></P>
175
+ </TextBox>
176
+ ```
177
+
178
+ ### Positions and Sizes
179
+
180
+ All positions (`x`, `y`) and sizes (`w`, `h`) are in points:
181
+
182
+ ```xml
183
+ <Rect id="e1" x="100" y="200" w="300" h="150"/>
184
+ ```
185
+
186
+ For elements inside groups, positions are relative to the group's origin.
187
+
188
+ ### Copying Elements
189
+
190
+ To copy an element, duplicate its XML with:
191
+ - Same `id` as the source element
192
+ - New `x`, `y` position
193
+ - **Omit `w` and `h`** (this signals a copy)
194
+
195
+ ```xml
196
+ <!-- Original -->
197
+ <Rect id="e1" x="100" y="100" w="200" h="100"/>
198
+
199
+ <!-- Copy (omit w/h to signal copy) -->
200
+ <Rect id="e1" x="400" y="100"/>
201
+ ```
202
+
203
+ See [copy-workflow.md](./docs/copy-workflow.md) for details.
204
+
205
+ ## CLI Reference
206
+
207
+ ```bash
208
+ # Pull a presentation
209
+ uvx extraslide pull <url_or_id> [output_dir]
210
+ uvx extraslide pull "https://docs.google.com/presentation/d/1abc.../edit"
211
+ uvx extraslide pull "https://docs.google.com/presentation/d/1abc.../edit" ./my-folder
212
+
213
+ # Preview changes (dry run)
214
+ uvx extraslide diff <folder>
215
+ uvx extraslide diff ./1abc.../
216
+
217
+ # Apply changes
218
+ uvx extraslide push <folder>
219
+ uvx extraslide push ./1abc.../
220
+ ```
221
+
222
+ Also works as a Python module:
223
+ ```bash
224
+ python -m extraslide pull ...
225
+ python -m extraslide diff ...
226
+ python -m extraslide push ...
227
+ ```
228
+
229
+ ## Important Notes
230
+
231
+ ### Re-pull After Push
232
+
233
+ After pushing changes, the local SML becomes stale. Always re-pull before making more edits:
234
+
235
+ ```bash
236
+ uvx extraslide push ./1abc.../
237
+ uvx extraslide pull "https://docs.google.com/presentation/d/1abc.../edit" # Re-pull!
238
+ # Now safe to edit again
239
+ ```
240
+
241
+ ### What Can Be Edited
242
+
243
+ - Text content and styling
244
+ - Shape properties (fill, stroke, position, size)
245
+ - Add/delete slides
246
+ - Add/delete elements
247
+ - Copy existing elements
248
+ - Tables (content and basic styling)
249
+ - Images (can reference existing, limited creation support)
250
+ - Charts (read-only, linked to source spreadsheet)
251
+
252
+ ### Styles
253
+
254
+ Element styles (fill, stroke, text formatting) are stored in `styles.json` and automatically applied when copying elements. You don't need to specify styles in the SML - just copy an element and modify the text/position.
255
+
256
+ ## Documentation
257
+
258
+ - [Copy-Based Editing Guide](./docs/copy-workflow.md) - How to copy and modify elements
259
+ - [SML Syntax Specification](./docs/markup-syntax-design.md) - Full format reference
260
+
261
+ ## Requirements
262
+
263
+ - Python 3.10+
264
+ - Google Slides file shared with your service account
265
+
266
+ ## Project Status
267
+
268
+ **Alpha** - The library is functional but still evolving. SML format and CLI may change.
269
+
270
+ ## License
271
+
272
+ [MIT License](LICENSE)