llming-stage 0.1.1__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 (185) hide show
  1. llming_stage-0.1.1/LICENSE +21 -0
  2. llming_stage-0.1.1/PKG-INFO +147 -0
  3. llming_stage-0.1.1/README.md +123 -0
  4. llming_stage-0.1.1/THIRD_PARTY.md +50 -0
  5. llming_stage-0.1.1/llming_stage/__init__.py +34 -0
  6. llming_stage-0.1.1/llming_stage/asset_server.py +131 -0
  7. llming_stage-0.1.1/llming_stage/assets/noto-emoji.zip +0 -0
  8. llming_stage-0.1.1/llming_stage/assets/phosphor-icons.zip +0 -0
  9. llming_stage-0.1.1/llming_stage/assets/tabler-icons.zip +0 -0
  10. llming_stage-0.1.1/llming_stage/cli.py +76 -0
  11. llming_stage-0.1.1/llming_stage/dev_reload.py +457 -0
  12. llming_stage-0.1.1/llming_stage/fonts/01d40c9fd448face.woff2 +0 -0
  13. llming_stage-0.1.1/llming_stage/fonts/042d031f00f025d3.woff2 +0 -0
  14. llming_stage-0.1.1/llming_stage/fonts/0953a0ea5ec2fbe6.woff2 +0 -0
  15. llming_stage-0.1.1/llming_stage/fonts/096bf4e001f30f3d.woff2 +0 -0
  16. llming_stage-0.1.1/llming_stage/fonts/0c19a63c7528cc1a.woff2 +0 -0
  17. llming_stage-0.1.1/llming_stage/fonts/0ca83f77c744e2a2.woff2 +0 -0
  18. llming_stage-0.1.1/llming_stage/fonts/18cea8b33059950e.woff2 +0 -0
  19. llming_stage-0.1.1/llming_stage/fonts/1e891d4a2a7f2f96.woff2 +0 -0
  20. llming_stage-0.1.1/llming_stage/fonts/28b81f220ca048e2.woff2 +0 -0
  21. llming_stage-0.1.1/llming_stage/fonts/2af55f54d9fac778.woff2 +0 -0
  22. llming_stage-0.1.1/llming_stage/fonts/2c8ec1765f13887d.woff2 +0 -0
  23. llming_stage-0.1.1/llming_stage/fonts/2e2ca037aef7b51d.woff2 +0 -0
  24. llming_stage-0.1.1/llming_stage/fonts/334116c5190f1716.woff2 +0 -0
  25. llming_stage-0.1.1/llming_stage/fonts/34d83f10ce3fa7b4.woff2 +0 -0
  26. llming_stage-0.1.1/llming_stage/fonts/425b348f7eab066c.woff2 +0 -0
  27. llming_stage-0.1.1/llming_stage/fonts/47c853f95b759bef.woff2 +0 -0
  28. llming_stage-0.1.1/llming_stage/fonts/4ad9ee1c03eed305.woff2 +0 -0
  29. llming_stage-0.1.1/llming_stage/fonts/4c401738b2e90c99.woff2 +0 -0
  30. llming_stage-0.1.1/llming_stage/fonts/55a208f32a6be540.woff2 +0 -0
  31. llming_stage-0.1.1/llming_stage/fonts/5f02a1396c9fc305.woff2 +0 -0
  32. llming_stage-0.1.1/llming_stage/fonts/616907143b870cb1.woff2 +0 -0
  33. llming_stage-0.1.1/llming_stage/fonts/62873223be6b39e0.woff2 +0 -0
  34. llming_stage-0.1.1/llming_stage/fonts/66c1c422e62faf1d.woff2 +0 -0
  35. llming_stage-0.1.1/llming_stage/fonts/6d6ebdc60d2cd01e.woff2 +0 -0
  36. llming_stage-0.1.1/llming_stage/fonts/707a7c6749d7c089.woff2 +0 -0
  37. llming_stage-0.1.1/llming_stage/fonts/7104372e0a34311e.woff2 +0 -0
  38. llming_stage-0.1.1/llming_stage/fonts/7420c823209e4609.woff2 +0 -0
  39. llming_stage-0.1.1/llming_stage/fonts/7c7f3bb965e2deae.woff2 +0 -0
  40. llming_stage-0.1.1/llming_stage/fonts/7f5c1101832b02f5.woff2 +0 -0
  41. llming_stage-0.1.1/llming_stage/fonts/81471017108054b9.woff2 +0 -0
  42. llming_stage-0.1.1/llming_stage/fonts/851ed7636af29416.woff2 +0 -0
  43. llming_stage-0.1.1/llming_stage/fonts/8d21fc3a9d063f00.woff2 +0 -0
  44. llming_stage-0.1.1/llming_stage/fonts/8e146ffc255e03c3.woff2 +0 -0
  45. llming_stage-0.1.1/llming_stage/fonts/935e23d0cd87ac6f.woff2 +0 -0
  46. llming_stage-0.1.1/llming_stage/fonts/9b5609bd55cc78e3.woff2 +0 -0
  47. llming_stage-0.1.1/llming_stage/fonts/a35a05f781345176.woff2 +0 -0
  48. llming_stage-0.1.1/llming_stage/fonts/ab8cf1fb84e33cd6.woff2 +0 -0
  49. llming_stage-0.1.1/llming_stage/fonts/afd447df78322d0d.woff2 +0 -0
  50. llming_stage-0.1.1/llming_stage/fonts/bb828b03a1f11130.woff2 +0 -0
  51. llming_stage-0.1.1/llming_stage/fonts/c3f2f68bb77a7944.woff2 +0 -0
  52. llming_stage-0.1.1/llming_stage/fonts/c854362e488d0bec.woff2 +0 -0
  53. llming_stage-0.1.1/llming_stage/fonts/cf8e1fe457b4cc05.woff2 +0 -0
  54. llming_stage-0.1.1/llming_stage/fonts/d4e71f49ac06b20e.woff2 +0 -0
  55. llming_stage-0.1.1/llming_stage/fonts/d5ec90d9ff9fdc02.woff2 +0 -0
  56. llming_stage-0.1.1/llming_stage/fonts/dcbef151cf127173.woff2 +0 -0
  57. llming_stage-0.1.1/llming_stage/fonts/e62204447c1ed92a.woff2 +0 -0
  58. llming_stage-0.1.1/llming_stage/fonts/ea7496e801e7e453.woff2 +0 -0
  59. llming_stage-0.1.1/llming_stage/fonts/f3d429b4c95dd3ce.woff2 +0 -0
  60. llming_stage-0.1.1/llming_stage/fonts/fdbd937e991f101f.woff2 +0 -0
  61. llming_stage-0.1.1/llming_stage/fonts/fonts.css +506 -0
  62. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
  63. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
  64. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
  65. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
  66. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
  67. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
  68. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
  69. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
  70. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
  71. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
  72. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
  73. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
  74. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
  75. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
  76. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
  77. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
  78. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
  79. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
  80. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
  81. llming_stage-0.1.1/llming_stage/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
  82. llming_stage-0.1.1/llming_stage/lang/ar-TN.umd.prod.js +7 -0
  83. llming_stage-0.1.1/llming_stage/lang/ar.umd.prod.js +7 -0
  84. llming_stage-0.1.1/llming_stage/lang/az-Latn.umd.prod.js +7 -0
  85. llming_stage-0.1.1/llming_stage/lang/bg.umd.prod.js +7 -0
  86. llming_stage-0.1.1/llming_stage/lang/bn.umd.prod.js +7 -0
  87. llming_stage-0.1.1/llming_stage/lang/bs-BA.umd.prod.js +7 -0
  88. llming_stage-0.1.1/llming_stage/lang/ca.umd.prod.js +7 -0
  89. llming_stage-0.1.1/llming_stage/lang/cs.umd.prod.js +7 -0
  90. llming_stage-0.1.1/llming_stage/lang/da.umd.prod.js +7 -0
  91. llming_stage-0.1.1/llming_stage/lang/de-CH.umd.prod.js +7 -0
  92. llming_stage-0.1.1/llming_stage/lang/de-DE.umd.prod.js +7 -0
  93. llming_stage-0.1.1/llming_stage/lang/de.umd.prod.js +7 -0
  94. llming_stage-0.1.1/llming_stage/lang/el.umd.prod.js +7 -0
  95. llming_stage-0.1.1/llming_stage/lang/en-GB.umd.prod.js +7 -0
  96. llming_stage-0.1.1/llming_stage/lang/en-US.umd.prod.js +7 -0
  97. llming_stage-0.1.1/llming_stage/lang/eo.umd.prod.js +7 -0
  98. llming_stage-0.1.1/llming_stage/lang/es.umd.prod.js +7 -0
  99. llming_stage-0.1.1/llming_stage/lang/et.umd.prod.js +7 -0
  100. llming_stage-0.1.1/llming_stage/lang/eu.umd.prod.js +7 -0
  101. llming_stage-0.1.1/llming_stage/lang/fa-IR.umd.prod.js +7 -0
  102. llming_stage-0.1.1/llming_stage/lang/fa.umd.prod.js +7 -0
  103. llming_stage-0.1.1/llming_stage/lang/fi.umd.prod.js +7 -0
  104. llming_stage-0.1.1/llming_stage/lang/fr.umd.prod.js +7 -0
  105. llming_stage-0.1.1/llming_stage/lang/gn.umd.prod.js +7 -0
  106. llming_stage-0.1.1/llming_stage/lang/he.umd.prod.js +7 -0
  107. llming_stage-0.1.1/llming_stage/lang/hi.umd.prod.js +7 -0
  108. llming_stage-0.1.1/llming_stage/lang/hr.umd.prod.js +7 -0
  109. llming_stage-0.1.1/llming_stage/lang/hu.umd.prod.js +7 -0
  110. llming_stage-0.1.1/llming_stage/lang/id.umd.prod.js +7 -0
  111. llming_stage-0.1.1/llming_stage/lang/is.umd.prod.js +7 -0
  112. llming_stage-0.1.1/llming_stage/lang/it.umd.prod.js +7 -0
  113. llming_stage-0.1.1/llming_stage/lang/ja.umd.prod.js +7 -0
  114. llming_stage-0.1.1/llming_stage/lang/kk.umd.prod.js +7 -0
  115. llming_stage-0.1.1/llming_stage/lang/km.umd.prod.js +7 -0
  116. llming_stage-0.1.1/llming_stage/lang/ko-KR.umd.prod.js +7 -0
  117. llming_stage-0.1.1/llming_stage/lang/kur-CKB.umd.prod.js +7 -0
  118. llming_stage-0.1.1/llming_stage/lang/lt.umd.prod.js +7 -0
  119. llming_stage-0.1.1/llming_stage/lang/lu.umd.prod.js +7 -0
  120. llming_stage-0.1.1/llming_stage/lang/lv.umd.prod.js +7 -0
  121. llming_stage-0.1.1/llming_stage/lang/mk.umd.prod.js +7 -0
  122. llming_stage-0.1.1/llming_stage/lang/ml.umd.prod.js +7 -0
  123. llming_stage-0.1.1/llming_stage/lang/mm.umd.prod.js +7 -0
  124. llming_stage-0.1.1/llming_stage/lang/ms-MY.umd.prod.js +7 -0
  125. llming_stage-0.1.1/llming_stage/lang/ms.umd.prod.js +7 -0
  126. llming_stage-0.1.1/llming_stage/lang/my.umd.prod.js +7 -0
  127. llming_stage-0.1.1/llming_stage/lang/nb-NO.umd.prod.js +7 -0
  128. llming_stage-0.1.1/llming_stage/lang/nl.umd.prod.js +7 -0
  129. llming_stage-0.1.1/llming_stage/lang/pl.umd.prod.js +7 -0
  130. llming_stage-0.1.1/llming_stage/lang/pt-BR.umd.prod.js +7 -0
  131. llming_stage-0.1.1/llming_stage/lang/pt.umd.prod.js +7 -0
  132. llming_stage-0.1.1/llming_stage/lang/ro.umd.prod.js +7 -0
  133. llming_stage-0.1.1/llming_stage/lang/ru.umd.prod.js +7 -0
  134. llming_stage-0.1.1/llming_stage/lang/sk.umd.prod.js +7 -0
  135. llming_stage-0.1.1/llming_stage/lang/sl.umd.prod.js +7 -0
  136. llming_stage-0.1.1/llming_stage/lang/sm.umd.prod.js +7 -0
  137. llming_stage-0.1.1/llming_stage/lang/sq.umd.prod.js +7 -0
  138. llming_stage-0.1.1/llming_stage/lang/sr-CYR.umd.prod.js +7 -0
  139. llming_stage-0.1.1/llming_stage/lang/sr.umd.prod.js +7 -0
  140. llming_stage-0.1.1/llming_stage/lang/sv.umd.prod.js +7 -0
  141. llming_stage-0.1.1/llming_stage/lang/ta.umd.prod.js +7 -0
  142. llming_stage-0.1.1/llming_stage/lang/th.umd.prod.js +7 -0
  143. llming_stage-0.1.1/llming_stage/lang/tl.umd.prod.js +7 -0
  144. llming_stage-0.1.1/llming_stage/lang/tr.umd.prod.js +7 -0
  145. llming_stage-0.1.1/llming_stage/lang/ug.umd.prod.js +7 -0
  146. llming_stage-0.1.1/llming_stage/lang/uk.umd.prod.js +7 -0
  147. llming_stage-0.1.1/llming_stage/lang/ur-PK.umd.prod.js +7 -0
  148. llming_stage-0.1.1/llming_stage/lang/uz-Cyrl.umd.prod.js +7 -0
  149. llming_stage-0.1.1/llming_stage/lang/uz-Latn.umd.prod.js +7 -0
  150. llming_stage-0.1.1/llming_stage/lang/vi.umd.prod.js +7 -0
  151. llming_stage-0.1.1/llming_stage/lang/zh-CN.umd.prod.js +7 -0
  152. llming_stage-0.1.1/llming_stage/lang/zh-TW.umd.prod.js +7 -0
  153. llming_stage-0.1.1/llming_stage/shell.py +317 -0
  154. llming_stage-0.1.1/llming_stage/stage.py +875 -0
  155. llming_stage-0.1.1/llming_stage/static/loader.js +236 -0
  156. llming_stage-0.1.1/llming_stage/static/router.js +95 -0
  157. llming_stage-0.1.1/llming_stage/vendor/codemirror-addon-closebrackets.js +201 -0
  158. llming_stage-0.1.1/llming_stage/vendor/codemirror-addon-matchbrackets.js +160 -0
  159. llming_stage-0.1.1/llming_stage/vendor/codemirror-mode-javascript.js +960 -0
  160. llming_stage-0.1.1/llming_stage/vendor/codemirror.css +344 -0
  161. llming_stage-0.1.1/llming_stage/vendor/codemirror.js +9884 -0
  162. llming_stage-0.1.1/llming_stage/vendor/dompurify.min.js +3 -0
  163. llming_stage-0.1.1/llming_stage/vendor/drawflow.min.css +1 -0
  164. llming_stage-0.1.1/llming_stage/vendor/drawflow.min.js +1 -0
  165. llming_stage-0.1.1/llming_stage/vendor/echarts.min.js +45 -0
  166. llming_stage-0.1.1/llming_stage/vendor/katex.min.css +1 -0
  167. llming_stage-0.1.1/llming_stage/vendor/katex.min.js +1 -0
  168. llming_stage-0.1.1/llming_stage/vendor/marked.umd.js +79 -0
  169. llming_stage-0.1.1/llming_stage/vendor/mermaid.min.js +3298 -0
  170. llming_stage-0.1.1/llming_stage/vendor/plotly-basic.min.js +8 -0
  171. llming_stage-0.1.1/llming_stage/vendor/plotly-full.min.js +3882 -0
  172. llming_stage-0.1.1/llming_stage/vendor/quasar.prod.css +1 -0
  173. llming_stage-0.1.1/llming_stage/vendor/quasar.umd.prod.js +127 -0
  174. llming_stage-0.1.1/llming_stage/vendor/tailwindcss.browser.global.js +947 -0
  175. llming_stage-0.1.1/llming_stage/vendor/three-orbit-controls.module.js +1963 -0
  176. llming_stage-0.1.1/llming_stage/vendor/three.core.min.js +6 -0
  177. llming_stage-0.1.1/llming_stage/vendor/three.module.min.js +6 -0
  178. llming_stage-0.1.1/llming_stage/vendor/vue.global.prod.js +9 -0
  179. llming_stage-0.1.1/llming_stage/vendor/xterm-addon-fit.js +2 -0
  180. llming_stage-0.1.1/llming_stage/vendor/xterm-addon-web-links.js +2 -0
  181. llming_stage-0.1.1/llming_stage/vendor/xterm-addon-webgl.js +2 -0
  182. llming_stage-0.1.1/llming_stage/vendor/xterm.css +285 -0
  183. llming_stage-0.1.1/llming_stage/vendor/xterm.js +2 -0
  184. llming_stage-0.1.1/llming_stage/zip_server.py +95 -0
  185. llming_stage-0.1.1/pyproject.toml +62 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Michael Ikemann
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,147 @@
1
+ Metadata-Version: 2.3
2
+ Name: llming-stage
3
+ Version: 0.1.1
4
+ Summary: Shared frontend foundation and SPA app shell for the llming ecosystem — vendor JS, fonts, icons, and a lazy-loading client-side router
5
+ License: MIT
6
+ Keywords: spa,frontend,assets,vue,quasar,starlette,lazy-loading
7
+ Author: Michael Ikemann
8
+ Requires-Python: >=3.14,<4
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Topic :: Internet :: WWW/HTTP
15
+ Classifier: Topic :: Software Development :: Libraries
16
+ Requires-Dist: llming-com (>=0.1.5,<0.2)
17
+ Requires-Dist: starlette (>=0.27)
18
+ Requires-Dist: uvicorn[standard] (>=0.27)
19
+ Project-URL: Author, https://github.com/Alyxion
20
+ Project-URL: Homepage, https://github.com/Alyxion/llming-stage
21
+ Project-URL: Repository, https://github.com/Alyxion/llming-stage
22
+ Description-Content-Type: text/markdown
23
+
24
+ <p align="center"><img src="https://raw.githubusercontent.com/Alyxion/llming-stage/main/media/logo-small.png" alt="LLMing Stage" width="400"></p>
25
+
26
+ # llming-stage
27
+
28
+ [![Python 3.14+](https://img.shields.io/badge/python-3.14%2B-blue.svg)](https://www.python.org/downloads/)
29
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/Alyxion/llming-stage/blob/main/LICENSE)
30
+ [![PyPI version](https://img.shields.io/pypi/v/llming-stage.svg)](https://pypi.org/project/llming-stage/)
31
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-purple.svg)](https://github.com/astral-sh/ruff)
32
+
33
+ ### Build Vue + Quasar UIs that AI can build, drive, and debug.
34
+
35
+ `llming-stage` is the SPA foundation for AI-assisted frontend work. You write a real Vue + Quasar app, not a Python shim. Reactive traffic and sessions flow through [`llming-com`](https://github.com/Alyxion/llming-com), and that's the same channel an AI assistant uses to inspect state, invoke commands, and push events into the running page.
36
+
37
+ ```mermaid
38
+ flowchart LR
39
+ User(["👤 User"]) --> Browser
40
+ AI(["🤖 AI assistant"]) <--> Com
41
+ Browser["<b>Browser</b><br/>Vue + Quasar SPA<br/>(llming-stage shell)"]
42
+ Com["<b>Python server</b><br/>llming-com<br/>sessions · auth · commands<br/>HTTP / MCP debug API"]
43
+ Browser <-->|"per-user WebSocket<br/>reactive commands · server pushes"| Com
44
+
45
+ classDef live fill:#1976d2,stroke:#1565c0,color:#fff,stroke-width:1.5px
46
+ classDef ai fill:#a855f7,stroke:#7c3aed,color:#fff,stroke-width:1.5px
47
+ class Browser,Com live
48
+ class AI ai
49
+ ```
50
+
51
+ `llming-com` ships the sessions, auth, command dispatcher, and the debug surface — without it, the AI side of the picture goes away. When you eventually deploy and the app no longer needs server reactivity, the same shell + view modules can ship as a static bundle to any CDN.
52
+
53
+ ---
54
+
55
+ ### What you get
56
+
57
+ - **An AI-debuggable runtime** — every reactive command, session, and event is browseable, invokable, and observable through one HTTP / MCP surface.
58
+ - **FastAPI-native app mounting** — create your own `FastAPI()` app and attach `Stage(app)`, or let `Stage()` create the default FastAPI app for compact demos. The internal `/_stage` routes and development reload are ensured once; server startup stays normal FastAPI/ASGI (`uvicorn main:app --reload`, deployment servers, etc.).
59
+ - **Modern frontend, zero boilerplate** — Vue 3 + Quasar 2 + bundled Tailwind utilities with a lazy-load orchestrator and an SPA router that keeps the WebSocket and view state alive across navigations.
60
+ - **Per-user sessions out of the box** — `llming-com` runs the wire and the auth; you write JS views and Python handlers.
61
+ - **Static-deployable** — when there's no server-side reactivity at runtime, the same code ships to GitHub Pages, S3, or any CDN.
62
+ - **No third-party network** — every asset is vendored. A page loaded from an `llming-stage` app makes zero requests to Google Fonts, jsDelivr, or any other external host. Privacy/GDPR-friendly by default; enforced by static and runtime tests.
63
+
64
+ ---
65
+
66
+ ### See it in action
67
+
68
+ ```bash
69
+ poetry install
70
+ ./samples/run.sh # opens a web gallery at http://localhost:8000
71
+ ```
72
+
73
+ Fourteen sample apps — from a tiny static app to generated decorator views, llming-com reactive loops, a Three.js particle tornado, an 8-chart ECharts dashboard, Plotly full-bundle charts, a core component workbench, and an optional-extension workbench — with dark/light theme, hot reload, and AI-debug control.
74
+
75
+ ---
76
+
77
+ ### Minimal app
78
+
79
+ ```python
80
+ from llming_stage import Stage
81
+
82
+ if __name__ == "__main__":
83
+ Stage(title="Hello world").add_view("/", "hello.vue").run()
84
+ ```
85
+
86
+ ```vue
87
+ <template>
88
+ <main class="min-h-screen grid place-items-center p-8">
89
+ <h1 class="text-5xl font-bold">Hello llming-stage</h1>
90
+ </main>
91
+ </template>
92
+ ```
93
+
94
+ For a purely static app, no Python file is needed:
95
+
96
+ ```bash
97
+ llming-stage serve hello.vue
98
+ ```
99
+
100
+ `Stage()` mounts the bundled assets, the Vue + Quasar shell, the SPA
101
+ router, bundled Tailwind utilities, and content-hash development reload
102
+ by default.
103
+
104
+ `stage.run()` is a thin local-development wrapper around `uvicorn.run`.
105
+ If you need workers, custom logging, TLS, or deployment process
106
+ management, run the same app directly with normal ASGI tooling.
107
+
108
+ Reactive apps add a typed session router and let Stage mount the
109
+ conventional session routes:
110
+
111
+ ```python
112
+ from fastapi import FastAPI
113
+ from llming_stage import Stage
114
+
115
+ app = FastAPI()
116
+ stage = Stage(app)
117
+ sessions = stage.session()
118
+ counter = sessions.add_router("counter")
119
+
120
+ @counter.handler("inc")
121
+ async def inc(session, by: int = 1):
122
+ value = int(session.state.get("count", 0)) + by
123
+ session.state["count"] = value
124
+ await session.call("home.setCounter", value)
125
+ return {"ok": True}
126
+
127
+ stage.add_view("/", "home.vue")
128
+ ```
129
+
130
+ The browser gets a real Vue + Quasar SPA from `.vue` view files.
131
+ `llming-com` carries the wire, the sessions, and the debug surface the
132
+ AI uses.
133
+
134
+ ---
135
+
136
+ ### Learn more
137
+
138
+ Full docs in [`docs/content/`](docs/content):
139
+
140
+ - [Quick start](docs/content/installation.md) · [Stage apps](docs/content/stage.md) · [App shell](docs/content/shell.md) · [Communication model](docs/content/communication-model.md)
141
+ - [llming-com integration](docs/content/llming-com.md) · [Assets & lazy loading](docs/content/lazy-loading.md)
142
+ - [Security](docs/content/security.md) · [API reference](docs/content/api.md)
143
+
144
+ ---
145
+
146
+ MIT licensed. © 2026 [Michael Ikemann](https://github.com/Alyxion). Bundled third-party files are listed in [`THIRD_PARTY.md`](THIRD_PARTY.md); no AGPL/GPL/LGPL is ever permitted.
147
+
@@ -0,0 +1,123 @@
1
+ <p align="center"><img src="https://raw.githubusercontent.com/Alyxion/llming-stage/main/media/logo-small.png" alt="LLMing Stage" width="400"></p>
2
+
3
+ # llming-stage
4
+
5
+ [![Python 3.14+](https://img.shields.io/badge/python-3.14%2B-blue.svg)](https://www.python.org/downloads/)
6
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/Alyxion/llming-stage/blob/main/LICENSE)
7
+ [![PyPI version](https://img.shields.io/pypi/v/llming-stage.svg)](https://pypi.org/project/llming-stage/)
8
+ [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-purple.svg)](https://github.com/astral-sh/ruff)
9
+
10
+ ### Build Vue + Quasar UIs that AI can build, drive, and debug.
11
+
12
+ `llming-stage` is the SPA foundation for AI-assisted frontend work. You write a real Vue + Quasar app, not a Python shim. Reactive traffic and sessions flow through [`llming-com`](https://github.com/Alyxion/llming-com), and that's the same channel an AI assistant uses to inspect state, invoke commands, and push events into the running page.
13
+
14
+ ```mermaid
15
+ flowchart LR
16
+ User(["👤 User"]) --> Browser
17
+ AI(["🤖 AI assistant"]) <--> Com
18
+ Browser["<b>Browser</b><br/>Vue + Quasar SPA<br/>(llming-stage shell)"]
19
+ Com["<b>Python server</b><br/>llming-com<br/>sessions · auth · commands<br/>HTTP / MCP debug API"]
20
+ Browser <-->|"per-user WebSocket<br/>reactive commands · server pushes"| Com
21
+
22
+ classDef live fill:#1976d2,stroke:#1565c0,color:#fff,stroke-width:1.5px
23
+ classDef ai fill:#a855f7,stroke:#7c3aed,color:#fff,stroke-width:1.5px
24
+ class Browser,Com live
25
+ class AI ai
26
+ ```
27
+
28
+ `llming-com` ships the sessions, auth, command dispatcher, and the debug surface — without it, the AI side of the picture goes away. When you eventually deploy and the app no longer needs server reactivity, the same shell + view modules can ship as a static bundle to any CDN.
29
+
30
+ ---
31
+
32
+ ### What you get
33
+
34
+ - **An AI-debuggable runtime** — every reactive command, session, and event is browseable, invokable, and observable through one HTTP / MCP surface.
35
+ - **FastAPI-native app mounting** — create your own `FastAPI()` app and attach `Stage(app)`, or let `Stage()` create the default FastAPI app for compact demos. The internal `/_stage` routes and development reload are ensured once; server startup stays normal FastAPI/ASGI (`uvicorn main:app --reload`, deployment servers, etc.).
36
+ - **Modern frontend, zero boilerplate** — Vue 3 + Quasar 2 + bundled Tailwind utilities with a lazy-load orchestrator and an SPA router that keeps the WebSocket and view state alive across navigations.
37
+ - **Per-user sessions out of the box** — `llming-com` runs the wire and the auth; you write JS views and Python handlers.
38
+ - **Static-deployable** — when there's no server-side reactivity at runtime, the same code ships to GitHub Pages, S3, or any CDN.
39
+ - **No third-party network** — every asset is vendored. A page loaded from an `llming-stage` app makes zero requests to Google Fonts, jsDelivr, or any other external host. Privacy/GDPR-friendly by default; enforced by static and runtime tests.
40
+
41
+ ---
42
+
43
+ ### See it in action
44
+
45
+ ```bash
46
+ poetry install
47
+ ./samples/run.sh # opens a web gallery at http://localhost:8000
48
+ ```
49
+
50
+ Fourteen sample apps — from a tiny static app to generated decorator views, llming-com reactive loops, a Three.js particle tornado, an 8-chart ECharts dashboard, Plotly full-bundle charts, a core component workbench, and an optional-extension workbench — with dark/light theme, hot reload, and AI-debug control.
51
+
52
+ ---
53
+
54
+ ### Minimal app
55
+
56
+ ```python
57
+ from llming_stage import Stage
58
+
59
+ if __name__ == "__main__":
60
+ Stage(title="Hello world").add_view("/", "hello.vue").run()
61
+ ```
62
+
63
+ ```vue
64
+ <template>
65
+ <main class="min-h-screen grid place-items-center p-8">
66
+ <h1 class="text-5xl font-bold">Hello llming-stage</h1>
67
+ </main>
68
+ </template>
69
+ ```
70
+
71
+ For a purely static app, no Python file is needed:
72
+
73
+ ```bash
74
+ llming-stage serve hello.vue
75
+ ```
76
+
77
+ `Stage()` mounts the bundled assets, the Vue + Quasar shell, the SPA
78
+ router, bundled Tailwind utilities, and content-hash development reload
79
+ by default.
80
+
81
+ `stage.run()` is a thin local-development wrapper around `uvicorn.run`.
82
+ If you need workers, custom logging, TLS, or deployment process
83
+ management, run the same app directly with normal ASGI tooling.
84
+
85
+ Reactive apps add a typed session router and let Stage mount the
86
+ conventional session routes:
87
+
88
+ ```python
89
+ from fastapi import FastAPI
90
+ from llming_stage import Stage
91
+
92
+ app = FastAPI()
93
+ stage = Stage(app)
94
+ sessions = stage.session()
95
+ counter = sessions.add_router("counter")
96
+
97
+ @counter.handler("inc")
98
+ async def inc(session, by: int = 1):
99
+ value = int(session.state.get("count", 0)) + by
100
+ session.state["count"] = value
101
+ await session.call("home.setCounter", value)
102
+ return {"ok": True}
103
+
104
+ stage.add_view("/", "home.vue")
105
+ ```
106
+
107
+ The browser gets a real Vue + Quasar SPA from `.vue` view files.
108
+ `llming-com` carries the wire, the sessions, and the debug surface the
109
+ AI uses.
110
+
111
+ ---
112
+
113
+ ### Learn more
114
+
115
+ Full docs in [`docs/content/`](docs/content):
116
+
117
+ - [Quick start](docs/content/installation.md) · [Stage apps](docs/content/stage.md) · [App shell](docs/content/shell.md) · [Communication model](docs/content/communication-model.md)
118
+ - [llming-com integration](docs/content/llming-com.md) · [Assets & lazy loading](docs/content/lazy-loading.md)
119
+ - [Security](docs/content/security.md) · [API reference](docs/content/api.md)
120
+
121
+ ---
122
+
123
+ MIT licensed. © 2026 [Michael Ikemann](https://github.com/Alyxion). Bundled third-party files are listed in [`THIRD_PARTY.md`](THIRD_PARTY.md); no AGPL/GPL/LGPL is ever permitted.
@@ -0,0 +1,50 @@
1
+ # Third-Party Resources
2
+
3
+ All bundled third-party resources and their licenses. This file MUST be updated
4
+ whenever a vendored file or asset is added, removed, or upgraded.
5
+
6
+ No AGPL, GPL, or LGPL resource is ever permitted in this repository.
7
+
8
+ ## Vendor Libraries (`llming_stage/vendor/`)
9
+
10
+ | Resource | Version | License | Source | Purpose |
11
+ |----------|---------|---------|--------|---------|
12
+ | Vue.js | 3.x | MIT | https://github.com/vuejs/core | Reactive UI framework |
13
+ | Quasar Framework | 2.x | MIT | https://github.com/quasarframework/quasar | UI component library |
14
+ | Tailwind CSS browser bundle | 4.2.4 | MIT | https://registry.npmjs.org/@tailwindcss/browser/-/browser-4.2.4.tgz (sha1 `e43e14e347d7b68a6c3afc7d77d8c18a335e98c3`) | Utility CSS runtime. The shell imports theme + utilities only, not preflight, to avoid global resets. |
15
+ | Three.js | 0.184.0 | MIT | https://registry.npmjs.org/three/-/three-0.184.0.tgz (sha1 `5bca0a3851eea5345e4c205567b40dfa49b791b5`) | 3D rendering. Ships as two files — `three.module.min.js` re-exports from the co-located `three.core.min.js`; both are required. |
16
+ | Three.js OrbitControls | 0.184.0 | MIT | `examples/jsm/controls/OrbitControls.js` from the same Three.js tarball | Mouse / touch / wheel camera controls (vendored as `three-orbit-controls.module.js`). |
17
+ | Apache ECharts | 6.0.0 | Apache 2.0 | https://registry.npmjs.org/echarts/-/echarts-6.0.0.tgz (sha1 `2935aa7751c282d1abbbf7d719d397199a15b9e7`) | Chart library — line, bar, pie, gauge, radar, heatmap, scatter, candlestick. |
18
+ | Plotly.js (basic) | 2.x | MIT | https://github.com/plotly/plotly.js | Charts and data visualization (lighter bundle — scatter, bar, pie). |
19
+ | Plotly.js (full) | 3.5.0 | MIT | https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-3.5.0.tgz (sha1 `d81d1c77dd93c927de2f84cdcd3f8697fb47583b`) | Full Plotly bundle — every chart type incl. heatmap, candlestick, choropleth. Loaded only when an app calls `__stage.load('plotly/full')`. |
20
+ | KaTeX | 0.16.45 | MIT | https://registry.npmjs.org/katex/-/katex-0.16.45.tgz (sha1 `ba60d39c54746b6b8d39ce0e7f6eace07143149c`) | LaTeX math rendering. Ships `katex.min.{js,css}` plus 20 woff2 font files under `fonts/katex/` (CSS rewritten to `url(../fonts/katex/…)` so every font ends up under the unified `/_stage/fonts/` URL tree). |
21
+ | Mermaid | 11.14.0 | MIT | https://registry.npmjs.org/mermaid/-/mermaid-11.14.0.tgz (sha1 `ce81b22bc10f3117ef7737406ef2d10ee1741769`) | Diagram rendering — UMD bundle (`mermaid.min.js`) so a single fetch covers every diagram type. |
22
+ | marked | 18.0.2 | MIT | https://registry.npmjs.org/marked/-/marked-18.0.2.tgz (sha1 `180cb158a2d2dc377821cfb088a10ca1b5630ef0`) | Markdown parser (UMD). |
23
+ | DOMPurify | 3.x | Apache 2.0 / MPL 2.0 | https://github.com/cure53/DOMPurify | HTML sanitization |
24
+ | xterm.js | 6.0.0 | MIT | https://registry.npmjs.org/@xterm/xterm/-/xterm-6.0.0.tgz (sha1 `93637b0f2ee3a70718b5746a27c9c506af16745b`) | Terminal emulator. |
25
+ | xterm.js fit addon | 0.11.0 | MIT | https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz (sha1 `ba4778b69fcc9044a060c2176bbe077657d7b37e`) | Terminal autosize. |
26
+ | xterm.js web-links addon | 0.12.0 | MIT | https://registry.npmjs.org/@xterm/addon-web-links/-/addon-web-links-0.12.0.tgz (sha1 `bc34ae46f4c12012256d451ac2b9be791c0b6bfa`) | Click-through URL detection. |
27
+ | xterm.js webgl addon | 0.19.0 | MIT | https://registry.npmjs.org/@xterm/addon-webgl/-/addon-webgl-0.19.0.tgz (sha1 `02533c3f7c0af3ac9a44bbb095cbd47d48e90c25`) | GPU-accelerated terminal renderer. |
28
+ | Drawflow | 0.0.60 | MIT | https://registry.npmjs.org/drawflow/-/drawflow-0.0.60.tgz (sha1 `313eadb43190a04ebba16d2cb49d1c9516030a15`) | Visual node-graph editor. |
29
+ | CodeMirror 5 | 5.65.21 | MIT | https://registry.npmjs.org/codemirror/-/codemirror-5.65.21.tgz (sha1 `cacf320606c5450ad3b3da34bb9c666afec21068`) | In-browser code editor. Vendored files: `codemirror.{js,css}`, `codemirror-mode-javascript.js`, `codemirror-addon-matchbrackets.js`, `codemirror-addon-closebrackets.js`. |
30
+
31
+ ## Fonts (`llming_stage/fonts/`)
32
+
33
+ | Resource | Version | License | Source | Purpose |
34
+ |----------|---------|---------|--------|---------|
35
+ | Roboto | 3.x | Apache 2.0 | https://github.com/googlefonts/roboto | Default UI font |
36
+ | Material Icons | 4.x | Apache 2.0 | https://github.com/google/material-design-icons | UI icons (filled, outlined, round, sharp) |
37
+ | Material Symbols | 1.x | Apache 2.0 | https://github.com/google/material-design-icons | UI icons (outlined, rounded, sharp) |
38
+
39
+ ## Icon & Emoji Sets (`llming_stage/assets/*.zip`)
40
+
41
+ | Resource | Version | License | Source | Purpose |
42
+ |----------|---------|---------|--------|---------|
43
+ | Phosphor Icons | 2.x | MIT | https://github.com/phosphor-icons/core | UI icons (6 weights: bold, duotone, fill, light, regular, thin) |
44
+ | Noto Emoji | 15.x | SIL OFL 1.1 / Apache 2.0 | https://github.com/googlefonts/noto-emoji | Emoji rendering |
45
+ | Tabler Icons | 3.41.1 | MIT | https://registry.npmjs.org/@tabler/icons/-/icons-3.41.1.tgz (sha1 `dff9c77af287c73c2fcab4074cfa9f2069f9b7ee`) | UI icons (~6 000 SVGs, outline + filled). Served via `tabler-icons.zip` at `/_stage/tabler/<style>/<name>.svg`. |
46
+
47
+ ## Quasar Locale Packs (`llming_stage/lang/`)
48
+
49
+ Bundled with Quasar — MIT licensed, sourced from the Quasar distribution
50
+ (https://github.com/quasarframework/quasar).
@@ -0,0 +1,34 @@
1
+ """llming-stage — shared frontend foundation and SPA app shell.
2
+
3
+ Public API:
4
+ - :func:`mount_assets` — attach path-hardened asset routes to any
5
+ Starlette/FastAPI app.
6
+ - :func:`mount_shell` — serve the SPA shell HTML document.
7
+ - :func:`mount_dev_reload` — attach development-only reload routes.
8
+ - :class:`Stage` — OOP helper for FastAPI-native stage apps.
9
+ - :class:`StageSession` — Stage-owned llming-com session wiring.
10
+ - :class:`VueResponse` — response type for generated Vue views.
11
+ - :class:`ShellConfig` — configuration dataclass for the shell.
12
+ - :func:`render_shell` — render the shell HTML (for custom wiring).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from .dev_reload import DevReloadConfig, dev_reload_head, mount_dev_reload
18
+ from .shell import ShellConfig, mount_assets, mount_shell, render_shell
19
+ from .stage import Stage, StageSession, VueResponse
20
+
21
+ __all__ = [
22
+ "DevReloadConfig",
23
+ "Stage",
24
+ "StageSession",
25
+ "ShellConfig",
26
+ "VueResponse",
27
+ "dev_reload_head",
28
+ "mount_assets",
29
+ "mount_dev_reload",
30
+ "mount_shell",
31
+ "render_shell",
32
+ ]
33
+
34
+ __version__ = "0.1.0"
@@ -0,0 +1,131 @@
1
+ """Path-hardened static asset serving.
2
+
3
+ Each asset group (vendor, fonts, lang) is a separate whitelist-scoped
4
+ handler. No generic file serving, no catch-all static mount.
5
+
6
+ Security guarantees
7
+ -------------------
8
+ 1. **Whitelist-only routing** — each route maps to one fixed root directory.
9
+ 2. **Path canonicalization** — requested paths are resolved against the root;
10
+ requests that escape the root (via ``..``, absolute paths, or symlinks)
11
+ return 403/404.
12
+ 3. **Dangerous pattern rejection** — ``..``, backslashes, null bytes, and
13
+ non-ASCII control characters are rejected before the filesystem is touched.
14
+ 4. **Extension whitelist** — only ``.js``, ``.css``, ``.woff2``, ``.svg``,
15
+ ``.json``, ``.mjs`` are served. Anything else is 404.
16
+ 5. **No directory listing** — directory requests return 404.
17
+ 6. **Symlink rejection** — any path component that is a symlink pointing
18
+ outside the root is rejected.
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import mimetypes
24
+ from pathlib import Path
25
+
26
+ from starlette.requests import Request
27
+ from starlette.responses import FileResponse, Response
28
+
29
+ ALLOWED_EXTENSIONS: frozenset[str] = frozenset(
30
+ {".js", ".mjs", ".css", ".woff2", ".svg", ".json"}
31
+ )
32
+
33
+ _MIME_OVERRIDES: dict[str, str] = {
34
+ ".js": "application/javascript; charset=utf-8",
35
+ ".mjs": "application/javascript; charset=utf-8",
36
+ ".css": "text/css; charset=utf-8",
37
+ ".woff2": "font/woff2",
38
+ ".svg": "image/svg+xml; charset=utf-8",
39
+ ".json": "application/json; charset=utf-8",
40
+ }
41
+
42
+ _FORBIDDEN_SUBSTRINGS: tuple[str, ...] = ("..", "\\", "\x00")
43
+
44
+
45
+ def validate_relative_path(path: str) -> str | None:
46
+ """Return the cleaned path, or ``None`` if the input is unsafe.
47
+
48
+ Applies only textual checks — the caller must still resolve against a
49
+ root and verify containment (see :func:`resolve_under_root`).
50
+ """
51
+ if not path:
52
+ return None
53
+ if path.startswith("/"):
54
+ return None
55
+ for bad in _FORBIDDEN_SUBSTRINGS:
56
+ if bad in path:
57
+ return None
58
+ for ch in path:
59
+ code = ord(ch)
60
+ if code < 0x20 or code == 0x7F:
61
+ return None
62
+ for segment in path.split("/"):
63
+ if not segment or segment in (".", ".."):
64
+ return None
65
+ return path
66
+
67
+
68
+ def resolve_under_root(root: Path, rel: str) -> Path | None:
69
+ """Resolve *rel* against *root*, ensuring the result stays inside.
70
+
71
+ Returns ``None`` if containment cannot be verified, if any component
72
+ is a symlink pointing outside *root*, or if the resolved path does not
73
+ exist as a regular file.
74
+ """
75
+ root_resolved = root.resolve(strict=True)
76
+ candidate = (root_resolved / rel).resolve(strict=False)
77
+ try:
78
+ candidate.relative_to(root_resolved)
79
+ except ValueError:
80
+ return None
81
+ if not candidate.exists():
82
+ return None
83
+ if not candidate.is_file():
84
+ return None
85
+ current = root_resolved
86
+ for part in Path(rel).parts:
87
+ current = current / part
88
+ try:
89
+ if current.is_symlink():
90
+ real = current.resolve(strict=True)
91
+ real.relative_to(root_resolved)
92
+ except (OSError, ValueError):
93
+ return None
94
+ return candidate
95
+
96
+
97
+ def _media_type_for(path: Path) -> str:
98
+ suffix = path.suffix.lower()
99
+ if suffix in _MIME_OVERRIDES:
100
+ return _MIME_OVERRIDES[suffix]
101
+ guessed, _ = mimetypes.guess_type(path.name)
102
+ return guessed or "application/octet-stream"
103
+
104
+
105
+ def make_dir_handler(root: Path, cache_max_age: int = 31536000):
106
+ """Build an async Starlette handler that serves files under *root*.
107
+
108
+ The handler reads the relative path from the ``path`` route parameter,
109
+ validates it, resolves it under *root*, and returns the file — or 404
110
+ on any failure. Never returns 403: "not found" is the single observable
111
+ outcome for every rejection path, to avoid leaking structure.
112
+ """
113
+ root = root.resolve(strict=True)
114
+ cache_control = f"public, max-age={cache_max_age}, immutable"
115
+
116
+ async def handler(request: Request) -> Response:
117
+ rel = validate_relative_path(request.path_params.get("path", ""))
118
+ if rel is None:
119
+ return Response(status_code=404)
120
+ if Path(rel).suffix.lower() not in ALLOWED_EXTENSIONS:
121
+ return Response(status_code=404)
122
+ resolved = resolve_under_root(root, rel)
123
+ if resolved is None:
124
+ return Response(status_code=404)
125
+ return FileResponse(
126
+ resolved,
127
+ media_type=_media_type_for(resolved),
128
+ headers={"Cache-Control": cache_control},
129
+ )
130
+
131
+ return handler
@@ -0,0 +1,76 @@
1
+ """Command line tools for no-Python llming-stage apps."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from pathlib import Path
7
+
8
+ from .stage import Stage
9
+
10
+ _VIEW_EXTENSIONS = {".vue", ".html", ".htm", ".js"}
11
+
12
+ def build_app(root: Path, *, dev: bool = True):
13
+ title = _title_for_root(root)
14
+ if root.is_file():
15
+ stage = Stage(root=root.parent, title=title, dev=dev)
16
+ stage.add_view("/", root)
17
+ return stage.app
18
+
19
+ views = root / "views"
20
+ stage = Stage(root=root, title=title, dev=dev)
21
+ if views.is_dir():
22
+ stage.discover(views)
23
+ return stage.app
24
+
25
+ view_files = sorted(
26
+ path
27
+ for path in root.rglob("*")
28
+ if path.is_file() and path.suffix.lower() in _VIEW_EXTENSIONS
29
+ )
30
+ if len(view_files) == 1:
31
+ stage.add_view("/", view_files[0])
32
+ else:
33
+ stage.discover(root)
34
+ return stage.app
35
+
36
+
37
+ def _title_for_root(root: Path) -> str:
38
+ raw = root.stem if root.is_file() else root.name
39
+ text = raw.replace("_", " ").replace("-", " ").strip()
40
+ return text[:1].upper() + text[1:] if text else "llming"
41
+
42
+
43
+ def main(argv: list[str] | None = None) -> int:
44
+ parser = argparse.ArgumentParser(prog="llming-stage")
45
+ sub = parser.add_subparsers(dest="cmd", required=True)
46
+
47
+ serve = sub.add_parser("serve", help="serve a no-Python stage app")
48
+ serve.add_argument("root", nargs="?", default=".")
49
+ serve.add_argument("--host", default="127.0.0.1")
50
+ serve.add_argument("--port", type=int, default=8765)
51
+
52
+ build = sub.add_parser("build", help="build a static no-Python stage app")
53
+ build.add_argument("root", nargs="?", default=".")
54
+ build.add_argument("--out", default="dist")
55
+
56
+ args = parser.parse_args(argv)
57
+ root = Path(args.root).resolve()
58
+
59
+ if args.cmd == "serve":
60
+ try:
61
+ import uvicorn
62
+ except ImportError as exc:
63
+ raise SystemExit(
64
+ "llming-stage serve requires uvicorn. Install it with `pip install uvicorn`."
65
+ ) from exc
66
+ uvicorn.run(build_app(root, dev=True), host=args.host, port=args.port)
67
+ return 0
68
+
69
+ app = build_app(root, dev=False)
70
+ stage = getattr(app.state, "llming_stage_instance")
71
+ stage.build(Path(args.out))
72
+ return 0
73
+
74
+
75
+ if __name__ == "__main__":
76
+ raise SystemExit(main())