scrolly 0.2.3__tar.gz → 0.2.5__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 (125) hide show
  1. {scrolly-0.2.3 → scrolly-0.2.5}/.gitignore +3 -0
  2. scrolly-0.2.5/PKG-INFO +118 -0
  3. scrolly-0.2.5/README.md +85 -0
  4. {scrolly-0.2.3 → scrolly-0.2.5}/pyproject.toml +3 -1
  5. scrolly-0.2.5/scrolly/_cli/_ai_help.py +124 -0
  6. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/_cli.py +20 -46
  7. scrolly-0.2.5/scrolly/_cli/_schema.py +184 -0
  8. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E003.md +2 -2
  9. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E299.md +2 -2
  10. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E601.md +3 -2
  11. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/assets/canvas.css +1 -1
  12. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/assets/canvas.js +15 -1
  13. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/__init__.py +2 -1
  14. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/ir/__init__.py +2 -0
  15. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/ir/_framework/element.py +49 -1
  16. scrolly-0.2.3/PKG-INFO +0 -81
  17. scrolly-0.2.3/README.md +0 -50
  18. {scrolly-0.2.3 → scrolly-0.2.5}/LICENSE +0 -0
  19. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/__init__.py +0 -0
  20. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/__init__.py +0 -0
  21. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/_errors.py +0 -0
  22. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/_introspect/__init__.py +0 -0
  23. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/_introspect/_assets.py +0 -0
  24. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/_introspect/_common.py +0 -0
  25. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/_introspect/_dom.py +0 -0
  26. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/_introspect/_elements.py +0 -0
  27. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/_introspect/_slides.py +0 -0
  28. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/_introspect/_snaps.py +0 -0
  29. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/_introspect/_snapshot.py +0 -0
  30. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/_cli/_introspect/_timeline.py +0 -0
  31. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/deck/__init__.py +0 -0
  32. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/deck/inference.py +0 -0
  33. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/deck/introspect.py +0 -0
  34. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/deck/model.py +0 -0
  35. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/deck/parser.py +0 -0
  36. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/deck/schema.py +0 -0
  37. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/deck/validator.py +0 -0
  38. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/__init__.py +0 -0
  39. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/_catalog.py +0 -0
  40. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/_codes.py +0 -0
  41. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/_report.py +0 -0
  42. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/_validation_error.py +0 -0
  43. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E001.md +0 -0
  44. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E002.md +0 -0
  45. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E004.md +0 -0
  46. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E005.md +0 -0
  47. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E006.md +0 -0
  48. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E007.md +0 -0
  49. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E008.md +0 -0
  50. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E009.md +0 -0
  51. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E010.md +0 -0
  52. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E011.md +0 -0
  53. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E012.md +0 -0
  54. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E101.md +0 -0
  55. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E102.md +0 -0
  56. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E103.md +0 -0
  57. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E201.md +0 -0
  58. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E202.md +0 -0
  59. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E203.md +0 -0
  60. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E204.md +0 -0
  61. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E205.md +0 -0
  62. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E206.md +0 -0
  63. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E207.md +0 -0
  64. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E301.md +0 -0
  65. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E302.md +0 -0
  66. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E303.md +0 -0
  67. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E304.md +0 -0
  68. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E305.md +0 -0
  69. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E306.md +0 -0
  70. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E307.md +0 -0
  71. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E308.md +0 -0
  72. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E401.md +0 -0
  73. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E402.md +0 -0
  74. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E403.md +0 -0
  75. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E501.md +0 -0
  76. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E502.md +0 -0
  77. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E503.md +0 -0
  78. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E504.md +0 -0
  79. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E505.md +0 -0
  80. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E602.md +0 -0
  81. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E603.md +0 -0
  82. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E701.md +0 -0
  83. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/E702.md +0 -0
  84. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/errors/catalog/__init__.py +0 -0
  85. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/pipeline/__init__.py +0 -0
  86. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/pipeline/_bundler.py +0 -0
  87. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/pipeline/assets.py +0 -0
  88. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/pipeline/introspect.py +0 -0
  89. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/pipeline/lint.py +0 -0
  90. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/pipeline/loader.py +0 -0
  91. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/pipeline/orchestrator.py +0 -0
  92. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/pipeline/writer.py +0 -0
  93. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/__init__.py +0 -0
  94. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/assembler.py +0 -0
  95. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/assets/mermaid-LICENSE +0 -0
  96. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/assets/mermaid.min.js +0 -0
  97. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/bundled_assets.py +0 -0
  98. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/color.py +0 -0
  99. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/fan.py +0 -0
  100. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/nav_data.py +0 -0
  101. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/templates/index.html.j2 +0 -0
  102. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/render/zoom_control.py +0 -0
  103. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/__init__.py +0 -0
  104. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/ir.py +0 -0
  105. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/processor.py +0 -0
  106. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/registry.py +0 -0
  107. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/rendered.py +0 -0
  108. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/renderers/__init__.py +0 -0
  109. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/renderers/_shared.py +0 -0
  110. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/renderers/html.py +0 -0
  111. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/renderers/iframe.py +0 -0
  112. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/renderers/image.py +0 -0
  113. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/renderers/image_sequence.py +0 -0
  114. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/renderers/markdown.py +0 -0
  115. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/element_ir/renderers/mermaid.py +0 -0
  116. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/html.py +0 -0
  117. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/introspect.py +0 -0
  118. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/ir/_framework/__init__.py +0 -0
  119. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/ir/_framework/animated_values.py +0 -0
  120. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/ir/_framework/utils.py +0 -0
  121. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/ir/slide.py +0 -0
  122. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/processor.py +0 -0
  123. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/registry.py +0 -0
  124. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/renderers/__init__.py +0 -0
  125. {scrolly-0.2.3 → scrolly-0.2.5}/scrolly/slide/renderers/slide.py +0 -0
@@ -5,6 +5,9 @@ dist/
5
5
  .pytest_cache/
6
6
  .ruff_cache/
7
7
 
8
+ # Docs site build output (mkdocs build)
9
+ /site/
10
+
8
11
  # Golden-test debug output (written next to the fixture on mismatch)
9
12
  *.actual.html
10
13
 
scrolly-0.2.5/PKG-INFO ADDED
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: scrolly
3
+ Version: 0.2.5
4
+ Summary: CLI that compiles a JSON5 deck + slide files into a self-contained 2D-canvas HTML presentation.
5
+ Project-URL: Homepage, https://scrolly.readthedocs.io/en/stable/
6
+ Project-URL: Documentation, https://scrolly.readthedocs.io/en/stable/
7
+ Project-URL: Source, https://github.com/bertpl/scrolly
8
+ Project-URL: Changelog, https://github.com/bertpl/scrolly/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/bertpl/scrolly/issues
10
+ Author-email: Bert Pluymers <bert.pluymers@gmail.com>
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: cli,html,presentation,scrollytelling,slides
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: End Users/Desktop
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
22
+ Classifier: Topic :: Multimedia :: Graphics :: Presentation
23
+ Classifier: Topic :: Text Processing :: Markup :: HTML
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: click>=8.2.0
26
+ Requires-Dist: jinja2>=3.1
27
+ Requires-Dist: json5>=0.13.0
28
+ Requires-Dist: markdown>=3.4
29
+ Requires-Dist: pydantic>=2.0
30
+ Requires-Dist: pyyaml>=6.0
31
+ Requires-Dist: rich>=13.0.0
32
+ Description-Content-Type: text/markdown
33
+
34
+ # scrolly
35
+
36
+ Compile a JSON5 deck into a single, self-contained, scrollable 2D-canvas HTML presentation.
37
+
38
+ *Liked by humans; understood by agents.*
39
+
40
+ [![scrolly — scroll-driven 2D-canvas presentations](https://raw.githubusercontent.com/bertpl/scrolly/main/docs/assets/hero-v4.webp)](https://scrolly.readthedocs.io/en/stable/)
41
+
42
+ [![CI](https://img.shields.io/github/actions/workflow/status/bertpl/scrolly/push_to_main.yml?branch=main&label=CI)](https://github.com/bertpl/scrolly/actions/workflows/push_to_main.yml)
43
+ [![PyPI](https://img.shields.io/pypi/v/scrolly.svg)](https://pypi.org/project/scrolly/)
44
+ [![Python](https://img.shields.io/pypi/pyversions/scrolly.svg)](https://pypi.org/project/scrolly/)
45
+ [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/bertpl/scrolly/blob/main/LICENSE)
46
+
47
+ ## Why it's different
48
+
49
+ - **A 2D canvas you fly around**, not a linear reel. Slides sit on an
50
+ integer grid connected by edges; readers zoom out to a deck map and
51
+ zoom into any slide to scroll through it.
52
+ - **Scroll-driven keyframe animation.** Element properties (position,
53
+ size, opacity, scale, angle) interpolate as the reader scrolls — no
54
+ timeline, no autoplay.
55
+ - **One self-contained HTML file.** A default build inlines every
56
+ asset into a single `index.html` with zero external loads — open it
57
+ by double-clicking, host it anywhere, email it.
58
+ - **Agent-friendly.** The CLI exposes full help, input schemas, deck
59
+ introspection, and numbered error codes, so agentic coding tools
60
+ immediately understand how to author and debug scrolly decks.
61
+
62
+ ## Installation
63
+
64
+ ```bash
65
+ pip install scrolly # or: uv tool install scrolly
66
+ ```
67
+
68
+ ## Quickstart
69
+
70
+ ```bash
71
+ scrolly init my-deck # scaffold a starter deck
72
+ scrolly build my-deck/deck.deck.json --out out --force # compile to one HTML file
73
+ open out/index.html # double-click anywhere
74
+ ```
75
+
76
+ A deck is a `.deck.json` manifest plus one `.slide.json` per slide (both
77
+ parsed as JSON5):
78
+
79
+ ```json5
80
+ // deck.deck.json
81
+ {
82
+ title: "My Deck",
83
+ slides: [
84
+ { id: "intro", position: [0, 0], source: "slides/intro.slide.json" },
85
+ ],
86
+ edges: [],
87
+ }
88
+ ```
89
+
90
+ ```json5
91
+ // slides/intro.slide.json
92
+ {
93
+ title: "My Deck",
94
+ elements: [
95
+ { markdown: "# My Deck\n\nWelcome to your new presentation." },
96
+ ],
97
+ }
98
+ ```
99
+
100
+ ## Elements
101
+
102
+ Slides hold elements of six types — `markdown`, `image`, `image_sequence`,
103
+ `mermaid`, `html`, and `iframe` — each animatable via scroll-driven
104
+ keyframes. See the [element reference](https://scrolly.readthedocs.io/en/stable/reference/elements/)
105
+ for fields and examples.
106
+
107
+ ## Learn more
108
+
109
+ - **[Documentation](https://scrolly.readthedocs.io/en/stable/)** — guided
110
+ walkthrough, concepts, authoring, and the full reference, with a live
111
+ interactive deck right on the homepage.
112
+ - **[Examples](examples/)** — including the
113
+ [stacked-diffs](examples/stacked-diffs/) hero deck.
114
+ - **[Changelog](CHANGELOG.md)**.
115
+
116
+ ## License
117
+
118
+ [MIT](https://github.com/bertpl/scrolly/blob/main/LICENSE).
@@ -0,0 +1,85 @@
1
+ # scrolly
2
+
3
+ Compile a JSON5 deck into a single, self-contained, scrollable 2D-canvas HTML presentation.
4
+
5
+ *Liked by humans; understood by agents.*
6
+
7
+ [![scrolly — scroll-driven 2D-canvas presentations](https://raw.githubusercontent.com/bertpl/scrolly/main/docs/assets/hero-v4.webp)](https://scrolly.readthedocs.io/en/stable/)
8
+
9
+ [![CI](https://img.shields.io/github/actions/workflow/status/bertpl/scrolly/push_to_main.yml?branch=main&label=CI)](https://github.com/bertpl/scrolly/actions/workflows/push_to_main.yml)
10
+ [![PyPI](https://img.shields.io/pypi/v/scrolly.svg)](https://pypi.org/project/scrolly/)
11
+ [![Python](https://img.shields.io/pypi/pyversions/scrolly.svg)](https://pypi.org/project/scrolly/)
12
+ [![License](https://img.shields.io/badge/license-MIT-blue)](https://github.com/bertpl/scrolly/blob/main/LICENSE)
13
+
14
+ ## Why it's different
15
+
16
+ - **A 2D canvas you fly around**, not a linear reel. Slides sit on an
17
+ integer grid connected by edges; readers zoom out to a deck map and
18
+ zoom into any slide to scroll through it.
19
+ - **Scroll-driven keyframe animation.** Element properties (position,
20
+ size, opacity, scale, angle) interpolate as the reader scrolls — no
21
+ timeline, no autoplay.
22
+ - **One self-contained HTML file.** A default build inlines every
23
+ asset into a single `index.html` with zero external loads — open it
24
+ by double-clicking, host it anywhere, email it.
25
+ - **Agent-friendly.** The CLI exposes full help, input schemas, deck
26
+ introspection, and numbered error codes, so agentic coding tools
27
+ immediately understand how to author and debug scrolly decks.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ pip install scrolly # or: uv tool install scrolly
33
+ ```
34
+
35
+ ## Quickstart
36
+
37
+ ```bash
38
+ scrolly init my-deck # scaffold a starter deck
39
+ scrolly build my-deck/deck.deck.json --out out --force # compile to one HTML file
40
+ open out/index.html # double-click anywhere
41
+ ```
42
+
43
+ A deck is a `.deck.json` manifest plus one `.slide.json` per slide (both
44
+ parsed as JSON5):
45
+
46
+ ```json5
47
+ // deck.deck.json
48
+ {
49
+ title: "My Deck",
50
+ slides: [
51
+ { id: "intro", position: [0, 0], source: "slides/intro.slide.json" },
52
+ ],
53
+ edges: [],
54
+ }
55
+ ```
56
+
57
+ ```json5
58
+ // slides/intro.slide.json
59
+ {
60
+ title: "My Deck",
61
+ elements: [
62
+ { markdown: "# My Deck\n\nWelcome to your new presentation." },
63
+ ],
64
+ }
65
+ ```
66
+
67
+ ## Elements
68
+
69
+ Slides hold elements of six types — `markdown`, `image`, `image_sequence`,
70
+ `mermaid`, `html`, and `iframe` — each animatable via scroll-driven
71
+ keyframes. See the [element reference](https://scrolly.readthedocs.io/en/stable/reference/elements/)
72
+ for fields and examples.
73
+
74
+ ## Learn more
75
+
76
+ - **[Documentation](https://scrolly.readthedocs.io/en/stable/)** — guided
77
+ walkthrough, concepts, authoring, and the full reference, with a live
78
+ interactive deck right on the homepage.
79
+ - **[Examples](examples/)** — including the
80
+ [stacked-diffs](examples/stacked-diffs/) hero deck.
81
+ - **[Changelog](CHANGELOG.md)**.
82
+
83
+ ## License
84
+
85
+ [MIT](https://github.com/bertpl/scrolly/blob/main/LICENSE).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "scrolly"
3
- version = "0.2.3"
3
+ version = "0.2.5"
4
4
  description = "CLI that compiles a JSON5 deck + slide files into a self-contained 2D-canvas HTML presentation."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -54,6 +54,8 @@ capture = [
54
54
  scrolly = "scrolly._cli._cli:cli"
55
55
 
56
56
  [project.urls]
57
+ Homepage = "https://scrolly.readthedocs.io/en/stable/"
58
+ Documentation = "https://scrolly.readthedocs.io/en/stable/"
57
59
  Source = "https://github.com/bertpl/scrolly"
58
60
  Changelog = "https://github.com/bertpl/scrolly/blob/main/CHANGELOG.md"
59
61
  Issues = "https://github.com/bertpl/scrolly/issues"
@@ -0,0 +1,124 @@
1
+ """Builds the ``scrolly --help-for-ai-tools`` document.
2
+
3
+ One markdown document covering the entire CLI surface in a single read —
4
+ the command tree, every source-file and element schema, and every error
5
+ code — so an LLM agent gets the whole picture without round-tripping
6
+ through ``scrolly schema`` / ``scrolly errors`` per type and code. It is
7
+ a pure aggregator: every section is the output the individual commands
8
+ already produce, merged under markdown headers, never re-rendered.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Iterator
14
+
15
+ import click
16
+
17
+ from scrolly._cli._schema import element_schema_json, file_schema_json, file_type_names
18
+ from scrolly.errors import registered_codes
19
+ from scrolly.errors._catalog import load_body
20
+ from scrolly.slide import element_source_types
21
+
22
+
23
+ # ==================================================================================================
24
+ # Document assembly
25
+ # ==================================================================================================
26
+ def build_ai_help(root_command: click.Command, version: str) -> str:
27
+ """Render the whole CLI reference as one self-contained markdown document.
28
+
29
+ Args:
30
+ root_command: The top-level ``scrolly`` Click group, walked for the command tree.
31
+ version: The scrolly version string, shown in the document header.
32
+
33
+ Returns:
34
+ A single markdown document: header, command tree, file schemas,
35
+ element schemas, and error codes — heading levels nested so the
36
+ whole document forms one consistent hierarchy.
37
+ """
38
+ sections = [
39
+ _header(version),
40
+ _commands_section(root_command),
41
+ _file_schemas_section(),
42
+ _element_schemas_section(),
43
+ _error_codes_section(),
44
+ ]
45
+ return "\n\n".join(sections) + "\n"
46
+
47
+
48
+ def _header(version: str) -> str:
49
+ """Render the document title and one-paragraph orientation."""
50
+ return (
51
+ f"# scrolly {version} — CLI reference for AI tools\n\n"
52
+ "The complete scrolly command-line surface in one document: every command, "
53
+ "every source-file and element schema, and every error code. Generated from "
54
+ "the installed scrolly, so it matches this version exactly."
55
+ )
56
+
57
+
58
+ # ==================================================================================================
59
+ # Sections
60
+ # ==================================================================================================
61
+ def _commands_section(root_command: click.Command) -> str:
62
+ """Render every command's help text, walking the full command tree."""
63
+ blocks = ["## Commands"]
64
+ for path, help_text in _walk_commands(root_command, "scrolly", None):
65
+ blocks.append(f"### `{path}`\n\n```\n{help_text.rstrip()}\n```")
66
+ return "\n\n".join(blocks)
67
+
68
+
69
+ def _file_schemas_section() -> str:
70
+ """Render the JSON Schema for every source-file type (deck, slide)."""
71
+ blocks = ["## File schemas"]
72
+ for name in file_type_names():
73
+ blocks.append(f"### `{name}`\n\n```json\n{file_schema_json(name)}\n```")
74
+ return "\n\n".join(blocks)
75
+
76
+
77
+ def _element_schemas_section() -> str:
78
+ """Render the JSON Schema for every slide-element type."""
79
+ blocks = ["## Element schemas"]
80
+ for key in element_source_types():
81
+ blocks.append(f"### `{key}`\n\n```json\n{element_schema_json(key)}\n```")
82
+ return "\n\n".join(blocks)
83
+
84
+
85
+ def _error_codes_section() -> str:
86
+ """Render the catalog entry for every registered error code.
87
+
88
+ Each catalog body is verbatim markdown starting at its own ``# E…``
89
+ heading; demoting by two levels nests every entry under this section.
90
+ """
91
+ blocks = ["## Error codes"]
92
+ for code in sorted(registered_codes()):
93
+ blocks.append(_demote_headings(load_body(code).rstrip(), 2))
94
+ return "\n\n".join(blocks)
95
+
96
+
97
+ # ==================================================================================================
98
+ # Helpers
99
+ # ==================================================================================================
100
+ def _walk_commands(
101
+ command: click.Command, info_name: str, parent_ctx: click.Context | None
102
+ ) -> Iterator[tuple[str, str]]:
103
+ """Yield ``(command_path, help_text)`` for a command and all its subcommands, depth-first."""
104
+ ctx = click.Context(command, info_name=info_name, parent=parent_ctx)
105
+ yield ctx.command_path, command.get_help(ctx)
106
+ if isinstance(command, click.Group):
107
+ for name in command.list_commands(ctx):
108
+ sub = command.get_command(ctx, name)
109
+ if sub is None or sub.hidden:
110
+ continue
111
+ yield from _walk_commands(sub, name, ctx)
112
+
113
+
114
+ def _demote_headings(markdown: str, levels: int) -> str:
115
+ """Deepen every ATX heading by ``levels`` ``#``, leaving fenced code blocks untouched."""
116
+ out = []
117
+ in_fence = False
118
+ for line in markdown.splitlines():
119
+ if line.lstrip().startswith("```"):
120
+ in_fence = not in_fence
121
+ if not in_fence and line.startswith("#"):
122
+ line = "#" * levels + line
123
+ out.append(line)
124
+ return "\n".join(out)
@@ -8,6 +8,7 @@ from rich.console import Console
8
8
  from scrolly import __version__
9
9
  from scrolly._cli._errors import errors_command
10
10
  from scrolly._cli._introspect import introspect
11
+ from scrolly._cli._schema import schema
11
12
  from scrolly.errors import ScrollyError, ValidationError
12
13
  from scrolly.pipeline import build_deck, load_deck
13
14
  from scrolly.pipeline.lint import lint_deck
@@ -15,8 +16,26 @@ from scrolly.pipeline.lint import lint_deck
15
16
  _err_console = Console(stderr=True, highlight=False)
16
17
 
17
18
 
19
+ def _emit_ai_help(ctx: click.Context, param: click.Parameter, value: bool) -> None:
20
+ """Eager ``--help-for-ai-tools`` callback: print the full CLI reference and exit."""
21
+ if not value or ctx.resilient_parsing:
22
+ return
23
+ from scrolly._cli._ai_help import build_ai_help
24
+
25
+ click.echo(build_ai_help(ctx.find_root().command, __version__))
26
+ ctx.exit()
27
+
28
+
18
29
  @click.group()
19
30
  @click.version_option(__version__, prog_name="scrolly")
31
+ @click.option(
32
+ "--help-for-ai-tools",
33
+ is_flag=True,
34
+ is_eager=True,
35
+ expose_value=False,
36
+ callback=_emit_ai_help,
37
+ help="Print the entire CLI reference (commands, schemas, error codes) as one markdown document for AI agents.",
38
+ )
20
39
  def cli() -> None:
21
40
  """scrolly — compile a JSON5 deck into a self-contained 2D-canvas HTML presentation."""
22
41
 
@@ -82,52 +101,6 @@ def build(
82
101
  click.echo(f"Built '{deck.title or '(untitled)'}': {len(deck.slides)} slides, {len(deck.edges)} edges → {out_dir}")
83
102
 
84
103
 
85
- @cli.command()
86
- @click.argument("type_name", required=False)
87
- @click.option(
88
- "--list-types",
89
- "list_types",
90
- is_flag=True,
91
- help="Print bare type names one per line (no descriptions) for scripting use.",
92
- )
93
- def schema(type_name: str | None, list_types: bool) -> None:
94
- """Show source file schemas.
95
-
96
- \b
97
- scrolly schema → formatted index of available types
98
- scrolly schema <type> → JSON Schema for <type>
99
- scrolly schema --list-types → bare type names, one per line (agent / scripting)
100
- """
101
- from scrolly.deck import deck_source_schema
102
- from scrolly.slide import registered_ir_types
103
-
104
- ir_types = registered_ir_types()
105
- all_type_names = sorted(["deck", *ir_types])
106
-
107
- if list_types:
108
- for name in all_type_names:
109
- click.echo(name)
110
- return
111
-
112
- if type_name is None:
113
- click.echo("Available schemas:\n")
114
- click.echo(f" {'deck':<17}{'.deck.json':<24}Deck structure (slides + edges)")
115
- for name in sorted(ir_types):
116
- cls = ir_types[name]
117
- click.echo(f" {name:<17}{cls.SUFFIX:<24}{cls.DESCRIPTION}")
118
- return
119
-
120
- if type_name == "deck":
121
- click.echo(json.dumps(deck_source_schema(), indent=2))
122
- return
123
-
124
- if type_name not in ir_types:
125
- _err_console.print(f"[red]error:[/red] unknown type '{type_name}' (known: {', '.join(all_type_names)})")
126
- sys.exit(1)
127
-
128
- click.echo(json.dumps(ir_types[type_name].source_schema(), indent=2))
129
-
130
-
131
104
  @cli.command()
132
105
  @click.argument("deck_path", type=click.Path(exists=True, dir_okay=False, path_type=Path))
133
106
  @click.option("--strict", is_flag=True, help="Enable additional lint checks (e.g. out-of-range keyframes).")
@@ -217,3 +190,4 @@ def init(dir_path: Path) -> None:
217
190
 
218
191
  cli.add_command(errors_command)
219
192
  cli.add_command(introspect)
193
+ cli.add_command(schema)
@@ -0,0 +1,184 @@
1
+ """``scrolly schema`` — show source-file and slide-element schemas from the CLI.
2
+
3
+ A Click group with two parallel subcommands, mirroring the
4
+ subcommand-group structure of ``scrolly introspect``:
5
+
6
+ * ``scrolly schema`` — combined index of file and element schemas.
7
+ * ``scrolly schema file`` — index of source-file schemas (deck, slide).
8
+ * ``scrolly schema file <type>`` — JSON Schema for a source-file type.
9
+ * ``scrolly schema file --list-types`` — bare file-type names, one per line.
10
+ * ``scrolly schema element`` — index of element schemas.
11
+ * ``scrolly schema element <type>`` — JSON Schema for an element type.
12
+ * ``scrolly schema element --list-types`` — bare element keys, one per line.
13
+
14
+ ``schema`` shows static *type* definitions; ``introspect`` inspects a
15
+ specific *resolved deck instance* — different surfaces, kept distinct.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import json
21
+ import sys
22
+
23
+ import click
24
+ from rich.console import Console
25
+
26
+ _err_console = Console(stderr=True, highlight=False)
27
+
28
+ # Width of the name / suffix columns in the human-readable index, matching
29
+ # the alignment used by ``scrolly errors``.
30
+ _NAME_COL = 17
31
+ _SUFFIX_COL = 24
32
+
33
+
34
+ # ==================================================================================================
35
+ # schema group
36
+ # ==================================================================================================
37
+ @click.group(name="schema", invoke_without_command=True)
38
+ @click.pass_context
39
+ def schema(ctx: click.Context) -> None:
40
+ """Show source-file and slide-element schemas.
41
+
42
+ \b
43
+ scrolly schema → combined index (file + element schemas)
44
+ scrolly schema file [<type>] → source-file schemas (deck, slide)
45
+ scrolly schema element [<type>] → slide-element schemas (markdown, image, …)
46
+
47
+ Append --list-types to either subcommand for bare names (agent / scripting).
48
+ Shows static type definitions; use `scrolly introspect` for a resolved deck.
49
+ """
50
+ if ctx.invoked_subcommand is None:
51
+ _print_file_index()
52
+ click.echo()
53
+ _print_element_index()
54
+
55
+
56
+ # --------------------------------------------------------------------------
57
+ # Subcommands
58
+ # --------------------------------------------------------------------------
59
+ @schema.command(name="file")
60
+ @click.argument("type_name", required=False)
61
+ @click.option(
62
+ "--list-types",
63
+ "list_types",
64
+ is_flag=True,
65
+ help="Print bare file-type names one per line (no descriptions) for scripting use.",
66
+ )
67
+ def schema_file(type_name: str | None, list_types: bool) -> None:
68
+ """Source-file schemas (deck, slide).
69
+
70
+ \b
71
+ scrolly schema file → formatted index of file types
72
+ scrolly schema file <type> → JSON Schema for <type>
73
+ scrolly schema file --list-types → bare type names, one per line (agent / scripting)
74
+ """
75
+ names = file_type_names()
76
+
77
+ if list_types:
78
+ for name in names:
79
+ click.echo(name)
80
+ return
81
+
82
+ if type_name is None:
83
+ _print_file_index()
84
+ return
85
+
86
+ schema_text = file_schema_json(type_name)
87
+ if schema_text is None:
88
+ _err_console.print(f"[red]error:[/red] unknown file type '{type_name}' (known: {', '.join(names)})")
89
+ sys.exit(1)
90
+ click.echo(schema_text)
91
+
92
+
93
+ @schema.command(name="element")
94
+ @click.argument("type_name", required=False)
95
+ @click.option(
96
+ "--list-types",
97
+ "list_types",
98
+ is_flag=True,
99
+ help="Print bare element keys one per line (no descriptions) for scripting use.",
100
+ )
101
+ def schema_element(type_name: str | None, list_types: bool) -> None:
102
+ """Slide-element schemas (markdown, image, …).
103
+
104
+ \b
105
+ scrolly schema element → formatted index of element types
106
+ scrolly schema element <type> → JSON Schema for <type>
107
+ scrolly schema element --list-types → bare element keys, one per line (agent / scripting)
108
+ """
109
+ from scrolly.slide import element_source_types
110
+
111
+ elements = element_source_types()
112
+
113
+ if list_types:
114
+ for key in elements:
115
+ click.echo(key)
116
+ return
117
+
118
+ if type_name is None:
119
+ _print_element_index()
120
+ return
121
+
122
+ schema_text = element_schema_json(type_name)
123
+ if schema_text is None:
124
+ _err_console.print(f"[red]error:[/red] unknown element type '{type_name}' (known: {', '.join(elements)})")
125
+ sys.exit(1)
126
+ click.echo(schema_text)
127
+
128
+
129
+ # --------------------------------------------------------------------------
130
+ # Schema lookup + index rendering
131
+ # --------------------------------------------------------------------------
132
+ def file_type_names() -> list[str]:
133
+ """Return the sorted source-file type names (deck + registered slide types)."""
134
+ from scrolly.slide import registered_ir_types
135
+
136
+ return sorted(["deck", *registered_ir_types()])
137
+
138
+
139
+ def _file_schema(type_name: str) -> dict | None:
140
+ """Return the JSON Schema for a source-file type, or ``None`` if unknown."""
141
+ from scrolly.deck import deck_source_schema
142
+ from scrolly.slide import registered_ir_types
143
+
144
+ if type_name == "deck":
145
+ return deck_source_schema()
146
+ ir_types = registered_ir_types()
147
+ if type_name in ir_types:
148
+ return ir_types[type_name].source_schema()
149
+ return None
150
+
151
+
152
+ def file_schema_json(type_name: str) -> str | None:
153
+ """Render a source-file type's JSON Schema as indented JSON text, or ``None`` if unknown."""
154
+ schema_dict = _file_schema(type_name)
155
+ return None if schema_dict is None else json.dumps(schema_dict, indent=2)
156
+
157
+
158
+ def element_schema_json(type_name: str) -> str | None:
159
+ """Render an element type's JSON Schema as indented JSON text, or ``None`` if unknown."""
160
+ from scrolly.slide import element_source_types
161
+
162
+ elements = element_source_types()
163
+ if type_name not in elements:
164
+ return None
165
+ return json.dumps(elements[type_name].source_schema(), indent=2)
166
+
167
+
168
+ def _print_file_index() -> None:
169
+ """Print the human-readable index of source-file schemas."""
170
+ from scrolly.slide import registered_ir_types
171
+
172
+ click.echo("File schemas (source files):\n")
173
+ click.echo(f" {'deck':<{_NAME_COL}}{'.deck.json':<{_SUFFIX_COL}}Deck structure (slides + edges)")
174
+ for name, cls in sorted(registered_ir_types().items()):
175
+ click.echo(f" {name:<{_NAME_COL}}{cls.SUFFIX:<{_SUFFIX_COL}}{cls.DESCRIPTION}")
176
+
177
+
178
+ def _print_element_index() -> None:
179
+ """Print the human-readable index of slide-element schemas."""
180
+ from scrolly.slide import element_source_types
181
+
182
+ click.echo("Element schemas (slide elements):\n")
183
+ for key, cls in element_source_types().items():
184
+ click.echo(f" {key:<{_NAME_COL}}{cls.DESCRIPTION}")
@@ -14,5 +14,5 @@ require `title` and `elements`; per-slide entries in a deck require
14
14
 
15
15
  ## How to fix
16
16
 
17
- Add the missing field. Run `scrolly schema deck` or `scrolly schema
18
- slide` to see the full required shape for each file type.
17
+ Add the missing field. Run `scrolly schema file deck` or `scrolly schema
18
+ file slide` to see the full required schema for each file type.
@@ -17,5 +17,5 @@ path embedded in the error message.
17
17
  ## How to fix
18
18
 
19
19
  Read the embedded Pydantic message — it names the field path and the
20
- constraint that failed. Cross-reference with `scrolly schema slide`
21
- for the expected shape of the slide source.
20
+ constraint that failed. Cross-reference with `scrolly schema file slide`
21
+ for the expected schema of the slide source.
@@ -13,5 +13,6 @@ An element object with no recognised type field — no `markdown:`,
13
13
 
14
14
  ## How to fix
15
15
 
16
- Check the element JSON against `scrolly schema slide`. Each element
17
- must include the field that names its type.
16
+ Run `scrolly schema element --list-types` to see the valid element
17
+ types, then `scrolly schema element <type>` for one's fields. Each
18
+ element must include the field that names its type.
@@ -1023,7 +1023,7 @@ body.pan-transitioning .canvas {
1023
1023
  left: 0;
1024
1024
  width: 100%;
1025
1025
  height: 100%;
1026
- background: rgba(60, 60, 60, 0.5);
1026
+ background: rgba(200, 200, 200, 0.5);
1027
1027
  backdrop-filter: blur(16px);
1028
1028
  -webkit-backdrop-filter: blur(16px);
1029
1029
  }