colloq 0.2.0__py3-none-any.whl

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 (224) hide show
  1. colloq/__init__.py +12 -0
  2. colloq/__main__.py +315 -0
  3. colloq/_app/.colloq-dist.json +66 -0
  4. colloq/_app/LICENSE +21 -0
  5. colloq/_app/THIRD_PARTY_NOTICES.md +106 -0
  6. colloq/_app/cli/colloq.mjs +2517 -0
  7. colloq/_app/cli/launch.mjs +1572 -0
  8. colloq/_app/cli/package.json +5 -0
  9. colloq/_app/cli/public-url-lease.mjs +198 -0
  10. colloq/_app/kernel/Dockerfile +93 -0
  11. colloq/_app/kernel/environments/base-gpu.txt +21 -0
  12. colloq/_app/kernel/environments/base.txt +12 -0
  13. colloq/_app/kernel/environments/cv.txt +15 -0
  14. colloq/_app/kernel/environments/gpu.txt +17 -0
  15. colloq/_app/kernel/environments/kaggle-base.txt +20 -0
  16. colloq/_app/kernel/environments/pvz-geo.txt +6 -0
  17. colloq/_app/kernel/requirements.txt +18 -0
  18. colloq/_app/package.json +20 -0
  19. colloq/_app/scripts/backup-local.sh +165 -0
  20. colloq/_app/scripts/backup.sh +34 -0
  21. colloq/_app/scripts/cluster.sh +492 -0
  22. colloq/_app/scripts/dns.sh +251 -0
  23. colloq/_app/scripts/host.sh +1514 -0
  24. colloq/_app/scripts/lib.sh +62 -0
  25. colloq/_app/scripts/restore.sh +338 -0
  26. colloq/_app/scripts/runtime-backup.py +373 -0
  27. colloq/_app/scripts/state-lock.py +85 -0
  28. colloq/_app/server/assets/fonts/HSESans-Black.otf +0 -0
  29. colloq/_app/server/assets/fonts/HSESans-Bold.otf +0 -0
  30. colloq/_app/server/assets/fonts/HSESans-Regular.otf +0 -0
  31. colloq/_app/server/assets/fonts/JetBrainsMono-Medium.ttf +0 -0
  32. colloq/_app/server/assets/fonts/JetBrainsMono-OFL.txt +93 -0
  33. colloq/_app/server/dist/local-cleanup.js +15987 -0
  34. colloq/_app/server/dist/runtime-smoke.js +17026 -0
  35. colloq/_app/server/dist/server.js +54333 -0
  36. colloq/_app/server/package.json +6 -0
  37. colloq/_app/web/dist/assets/AdminScreen-C-cg0GlJ.css +1 -0
  38. colloq/_app/web/dist/assets/AdminScreen-C-cg0GlJ.css.br +0 -0
  39. colloq/_app/web/dist/assets/AdminScreen-C-cg0GlJ.css.gz +0 -0
  40. colloq/_app/web/dist/assets/AdminScreen-fu4cB6Ra.js +105 -0
  41. colloq/_app/web/dist/assets/AdminScreen-fu4cB6Ra.js.br +0 -0
  42. colloq/_app/web/dist/assets/AdminScreen-fu4cB6Ra.js.gz +0 -0
  43. colloq/_app/web/dist/assets/App-CMXNqOjp.css +1 -0
  44. colloq/_app/web/dist/assets/App-CMXNqOjp.css.br +0 -0
  45. colloq/_app/web/dist/assets/App-CMXNqOjp.css.gz +0 -0
  46. colloq/_app/web/dist/assets/App-DvCPzIRO.js +26 -0
  47. colloq/_app/web/dist/assets/App-DvCPzIRO.js.br +0 -0
  48. colloq/_app/web/dist/assets/App-DvCPzIRO.js.gz +0 -0
  49. colloq/_app/web/dist/assets/CellOutputs-Dl-AZL45.js +42 -0
  50. colloq/_app/web/dist/assets/CellOutputs-Dl-AZL45.js.br +0 -0
  51. colloq/_app/web/dist/assets/CellOutputs-Dl-AZL45.js.gz +0 -0
  52. colloq/_app/web/dist/assets/CommandPalette-DhBGho7_.js +3 -0
  53. colloq/_app/web/dist/assets/CommandPalette-DhBGho7_.js.br +0 -0
  54. colloq/_app/web/dist/assets/CommandPalette-DhBGho7_.js.gz +0 -0
  55. colloq/_app/web/dist/assets/CompetitionsScreen-Vu3RpHmf.js +3 -0
  56. colloq/_app/web/dist/assets/CompetitionsScreen-Vu3RpHmf.js.br +0 -0
  57. colloq/_app/web/dist/assets/CompetitionsScreen-Vu3RpHmf.js.gz +0 -0
  58. colloq/_app/web/dist/assets/ConsoleView-B2uyYvFh.js +1 -0
  59. colloq/_app/web/dist/assets/ConsoleView-B2uyYvFh.js.br +0 -0
  60. colloq/_app/web/dist/assets/ConsoleView-B2uyYvFh.js.gz +0 -0
  61. colloq/_app/web/dist/assets/ConsoleView-DLbp0Jul.css +1 -0
  62. colloq/_app/web/dist/assets/DependencyBundleCard-B392XMVZ.js +4 -0
  63. colloq/_app/web/dist/assets/DependencyBundleCard-B392XMVZ.js.br +0 -0
  64. colloq/_app/web/dist/assets/DependencyBundleCard-B392XMVZ.js.gz +0 -0
  65. colloq/_app/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  66. colloq/_app/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  67. colloq/_app/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  68. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  69. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  70. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  71. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  72. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  73. colloq/_app/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  74. colloq/_app/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  75. colloq/_app/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  76. colloq/_app/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  77. colloq/_app/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  78. colloq/_app/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  79. colloq/_app/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  80. colloq/_app/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  81. colloq/_app/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  82. colloq/_app/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  83. colloq/_app/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  84. colloq/_app/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  85. colloq/_app/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  86. colloq/_app/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  87. colloq/_app/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  88. colloq/_app/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  89. colloq/_app/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  90. colloq/_app/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  91. colloq/_app/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  92. colloq/_app/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  93. colloq/_app/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  94. colloq/_app/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  95. colloq/_app/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  96. colloq/_app/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  97. colloq/_app/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  98. colloq/_app/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  99. colloq/_app/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  100. colloq/_app/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  101. colloq/_app/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  102. colloq/_app/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  103. colloq/_app/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  104. colloq/_app/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  105. colloq/_app/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  106. colloq/_app/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  107. colloq/_app/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  108. colloq/_app/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  109. colloq/_app/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  110. colloq/_app/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  111. colloq/_app/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  112. colloq/_app/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  113. colloq/_app/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  114. colloq/_app/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  115. colloq/_app/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  116. colloq/_app/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  117. colloq/_app/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  118. colloq/_app/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  119. colloq/_app/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  120. colloq/_app/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  121. colloq/_app/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  122. colloq/_app/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  123. colloq/_app/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  124. colloq/_app/web/dist/assets/PultWindow-B3cc8537.css +1 -0
  125. colloq/_app/web/dist/assets/PultWindow-B3cc8537.css.br +0 -0
  126. colloq/_app/web/dist/assets/PultWindow-B3cc8537.css.gz +0 -0
  127. colloq/_app/web/dist/assets/PultWindow-DPOUhSqt.js +8 -0
  128. colloq/_app/web/dist/assets/PultWindow-DPOUhSqt.js.br +0 -0
  129. colloq/_app/web/dist/assets/PultWindow-DPOUhSqt.js.gz +0 -0
  130. colloq/_app/web/dist/assets/ReaderScreen-CV_0VAvW.js +9 -0
  131. colloq/_app/web/dist/assets/ReaderScreen-CV_0VAvW.js.br +0 -0
  132. colloq/_app/web/dist/assets/ReaderScreen-CV_0VAvW.js.gz +0 -0
  133. colloq/_app/web/dist/assets/SessionScreen-BrcB6CLH.js +132 -0
  134. colloq/_app/web/dist/assets/SessionScreen-BrcB6CLH.js.br +0 -0
  135. colloq/_app/web/dist/assets/SessionScreen-BrcB6CLH.js.gz +0 -0
  136. colloq/_app/web/dist/assets/SessionScreen-Dek9dK3C.css +1 -0
  137. colloq/_app/web/dist/assets/SessionScreen-Dek9dK3C.css.br +0 -0
  138. colloq/_app/web/dist/assets/SessionScreen-Dek9dK3C.css.gz +0 -0
  139. colloq/_app/web/dist/assets/actions-DLPyolgo.js +1 -0
  140. colloq/_app/web/dist/assets/admin-en-BHmR7Co0.js +9 -0
  141. colloq/_app/web/dist/assets/admin-en-BHmR7Co0.js.br +0 -0
  142. colloq/_app/web/dist/assets/admin-en-BHmR7Co0.js.gz +0 -0
  143. colloq/_app/web/dist/assets/admin-ru-Dr6Z36Zi.js +11 -0
  144. colloq/_app/web/dist/assets/admin-ru-Dr6Z36Zi.js.br +0 -0
  145. colloq/_app/web/dist/assets/admin-ru-Dr6Z36Zi.js.gz +0 -0
  146. colloq/_app/web/dist/assets/codemirror-6LUh0q1l.js +32 -0
  147. colloq/_app/web/dist/assets/codemirror-6LUh0q1l.js.br +0 -0
  148. colloq/_app/web/dist/assets/codemirror-6LUh0q1l.js.gz +0 -0
  149. colloq/_app/web/dist/assets/competitions-en-zw57uAGL.js +5 -0
  150. colloq/_app/web/dist/assets/competitions-en-zw57uAGL.js.br +0 -0
  151. colloq/_app/web/dist/assets/competitions-en-zw57uAGL.js.gz +0 -0
  152. colloq/_app/web/dist/assets/competitions-ru-BBQUjAx0.js +5 -0
  153. colloq/_app/web/dist/assets/competitions-ru-BBQUjAx0.js.br +0 -0
  154. colloq/_app/web/dist/assets/competitions-ru-BBQUjAx0.js.gz +0 -0
  155. colloq/_app/web/dist/assets/editor-theme-CVYtpZDy.js +1 -0
  156. colloq/_app/web/dist/assets/editor-theme-CVYtpZDy.js.br +0 -0
  157. colloq/_app/web/dist/assets/editor-theme-CVYtpZDy.js.gz +0 -0
  158. colloq/_app/web/dist/assets/index-CwbYK31L.css +1 -0
  159. colloq/_app/web/dist/assets/index-CwbYK31L.css.br +0 -0
  160. colloq/_app/web/dist/assets/index-CwbYK31L.css.gz +0 -0
  161. colloq/_app/web/dist/assets/index-Di16xdmz.js +2 -0
  162. colloq/_app/web/dist/assets/index-Di16xdmz.js.br +0 -0
  163. colloq/_app/web/dist/assets/index-Di16xdmz.js.gz +0 -0
  164. colloq/_app/web/dist/assets/katex-CS_ezkZp.css +1 -0
  165. colloq/_app/web/dist/assets/katex-CS_ezkZp.css.br +0 -0
  166. colloq/_app/web/dist/assets/katex-CS_ezkZp.css.gz +0 -0
  167. colloq/_app/web/dist/assets/katex-DLorvT8M.js +257 -0
  168. colloq/_app/web/dist/assets/katex-DLorvT8M.js.br +0 -0
  169. colloq/_app/web/dist/assets/katex-DLorvT8M.js.gz +0 -0
  170. colloq/_app/web/dist/assets/pdf-DEp6sk-T.js +55 -0
  171. colloq/_app/web/dist/assets/pdf-DEp6sk-T.js.br +0 -0
  172. colloq/_app/web/dist/assets/pdf-DEp6sk-T.js.gz +0 -0
  173. colloq/_app/web/dist/assets/persistence.svelte-vcuCBcT5.js +1 -0
  174. colloq/_app/web/dist/assets/persistence.svelte-vcuCBcT5.js.br +0 -0
  175. colloq/_app/web/dist/assets/persistence.svelte-vcuCBcT5.js.gz +0 -0
  176. colloq/_app/web/dist/assets/reader-en-UCGE3U6V.js +3 -0
  177. colloq/_app/web/dist/assets/reader-en-UCGE3U6V.js.br +0 -0
  178. colloq/_app/web/dist/assets/reader-en-UCGE3U6V.js.gz +0 -0
  179. colloq/_app/web/dist/assets/reader-ru-CaiNj0K8.js +3 -0
  180. colloq/_app/web/dist/assets/reader-ru-CaiNj0K8.js.br +0 -0
  181. colloq/_app/web/dist/assets/reader-ru-CaiNj0K8.js.gz +0 -0
  182. colloq/_app/web/dist/assets/render-CjP3RuRT.js +155 -0
  183. colloq/_app/web/dist/assets/render-CjP3RuRT.js.br +0 -0
  184. colloq/_app/web/dist/assets/render-CjP3RuRT.js.gz +0 -0
  185. colloq/_app/web/dist/assets/render.svelte-B4_hAJmX.js +0 -0
  186. colloq/_app/web/dist/assets/render.svelte-B4_hAJmX.js.br +0 -0
  187. colloq/_app/web/dist/assets/render.svelte-B4_hAJmX.js.gz +0 -0
  188. colloq/_app/web/dist/assets/room-en-Bh8L6NcO.js +3 -0
  189. colloq/_app/web/dist/assets/room-en-Bh8L6NcO.js.br +0 -0
  190. colloq/_app/web/dist/assets/room-en-Bh8L6NcO.js.gz +0 -0
  191. colloq/_app/web/dist/assets/room-ru-Bar2GgS0.js +3 -0
  192. colloq/_app/web/dist/assets/room-ru-Bar2GgS0.js.br +0 -0
  193. colloq/_app/web/dist/assets/room-ru-Bar2GgS0.js.gz +0 -0
  194. colloq/_app/web/dist/assets/seminar-link-CRaZl9Ku.js +1 -0
  195. colloq/_app/web/dist/assets/seminar-link-CRaZl9Ku.js.br +0 -0
  196. colloq/_app/web/dist/assets/seminar-link-CRaZl9Ku.js.gz +0 -0
  197. colloq/_app/web/dist/assets/seminar-link-DoTugxxA.css +1 -0
  198. colloq/_app/web/dist/assets/seminar-link-DoTugxxA.css.br +0 -0
  199. colloq/_app/web/dist/assets/seminar-link-DoTugxxA.css.gz +0 -0
  200. colloq/_app/web/dist/assets/yjs-rjB3hdnL.js +6 -0
  201. colloq/_app/web/dist/assets/yjs-rjB3hdnL.js.br +0 -0
  202. colloq/_app/web/dist/assets/yjs-rjB3hdnL.js.gz +0 -0
  203. colloq/_app/web/dist/fonts/JetBrainsMono-OFL.txt +93 -0
  204. colloq/_app/web/dist/fonts/hse-sans-400.woff2 +0 -0
  205. colloq/_app/web/dist/fonts/hse-sans-600.woff2 +0 -0
  206. colloq/_app/web/dist/fonts/hse-sans-700.woff2 +0 -0
  207. colloq/_app/web/dist/fonts/hse-sans-900.woff2 +0 -0
  208. colloq/_app/web/dist/fonts/jetbrains-mono-cyrillic-ext.woff2 +0 -0
  209. colloq/_app/web/dist/fonts/jetbrains-mono-cyrillic.woff2 +0 -0
  210. colloq/_app/web/dist/fonts/jetbrains-mono-latin-ext.woff2 +0 -0
  211. colloq/_app/web/dist/fonts/jetbrains-mono-latin.woff2 +0 -0
  212. colloq/_app/web/dist/index.html +268 -0
  213. colloq/_app/web/dist/og/colloq.png +0 -0
  214. colloq/_app/web/dist/pdf/pdf.worker.min.mjs +29 -0
  215. colloq/_app/web/dist/pdf/pdf.worker.min.mjs.br +0 -0
  216. colloq/_app/web/dist/pdf/pdf.worker.min.mjs.gz +0 -0
  217. colloq/_app/web/dist/plotly/plotly.min.js +3620 -0
  218. colloq/_app/web/dist/plotly/plotly.min.js.br +0 -0
  219. colloq/_app/web/dist/plotly/plotly.min.js.gz +0 -0
  220. colloq/_version.py +5 -0
  221. colloq-0.2.0.dist-info/METADATA +124 -0
  222. colloq-0.2.0.dist-info/RECORD +224 -0
  223. colloq-0.2.0.dist-info/WHEEL +4 -0
  224. colloq-0.2.0.dist-info/entry_points.txt +2 -0
colloq/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ """Colloq — совместная среда для семинаров.
2
+
3
+ Пакет не содержит логики занятия: она вся в CLI, который лежит рядом собранным
4
+ (_app/cli/colloq.mjs) и исполняется Node. Здесь только упаковка — то, что
5
+ позволяет поставить всё это одной строкой `pip install colloq`.
6
+
7
+ Точка входа: colloq.__main__:main.
8
+ """
9
+
10
+ from ._version import __version__
11
+
12
+ __all__ = ["__version__"]
colloq/__main__.py ADDED
@@ -0,0 +1,315 @@
1
+ """Шим: найти Node, при первом запуске поставить node_modules, отдать управление CLI.
2
+
3
+ Больше здесь не происходит ничего, и это намеренно. Логика занятия — сборка,
4
+ ядра, порты, туннель, остановка — живёт в CLI (_app/cli/colloq.mjs, собран из
5
+ cli/src) и в супервизоре занятия, которым пользуется и `npm run dev` в
6
+ репозитории. Второй, питонский, слой правил означал бы две программы на одно
7
+ дело, и чинить пришлось бы обе.
8
+
9
+ Что шим делает по шагам:
10
+
11
+ 1. ищет node — сначала в PATH, потом там, где его кладут установщики;
12
+ проверяет, что версия не ниже 22 (better-sqlite3 13 заявляет node >=22, а
13
+ на 18 сервер и вовсе падает на ходу, а не при старте);
14
+ 2. если рядом с приложением нет node_modules — ставит их один раз npm-ом и
15
+ говорит об этом вслух: молчаливая минута на первом запуске читается как
16
+ «зависло»;
17
+ 3. выставляет COLLOQ_APP_DIR (где лежит приложение) и COLLOQ_CWD (откуда
18
+ позвали);
19
+ 4. заменяет собой процесс Node через execve — чтобы Ctrl+C уходил прямо в
20
+ CLI, а код возврата приходил прямо от него. Прослойка-надзиратель здесь
21
+ сломала бы и то, и другое: Ctrl+C на паре означает «сохрани и останови
22
+ занятие», и он должен дойти до того, кто умеет это сделать.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ import os
29
+ import re
30
+ import shutil
31
+ import subprocess
32
+ import sys
33
+ import time
34
+ from pathlib import Path
35
+ from typing import Iterator, NoReturn, Optional, Tuple
36
+
37
+ #: Приложение лежит внутри пакета: так его кладёт в колесо scripts/pack.mts.
38
+ APP = Path(__file__).resolve().parent / "_app"
39
+ ENTRY = APP / "cli" / "colloq.mjs"
40
+ MARK = APP / ".colloq-dist.json"
41
+ STAMP = APP / ".node-modules.json"
42
+ LOCK = APP / ".node-modules.lock"
43
+
44
+ #: Ниже 22 не бывает: better-sqlite3 13 заявляет node >=22 (server/package.json),
45
+ #: и это же версия образа в Dockerfile. С 13-й он на N-API: один готовый бинарь
46
+ #: на все версии Node выше, и пересборка под «свой» Node преподавателю не грозит.
47
+ MIN_NODE = 22
48
+ #: Сколько ждать чужую установку, прежде чем считать замок брошенным.
49
+ LOCK_WAIT_SEC = 900
50
+ #: Слова, ради которых незачем ставить окружение: они только рассказывают.
51
+ #: `colloq --help` сразу после установки — самое частое первое действие, и
52
+ #: минута npm в ответ на просьбу показать список команд была бы издевательством.
53
+ #: Сам CLI собран без единой внешней зависимости (это стережёт scripts/pack.mts),
54
+ #: так что ответить ему есть чем и без node_modules.
55
+ #:
56
+ #: Голый `colloq` в этот список НЕ входит, хотя тоже только рассказывает. Он —
57
+ #: первое, что набирают после `pip install colloq`, и это лучший момент для
58
+ #: разовой минуты: занятие начинается словом `colloq start`, и вот тогда ждать
59
+ #: уже нечего — до пары остаются минуты, а не вечер.
60
+ TELLING = frozenset(["--help", "-h", "help", "--version", "-V"])
61
+
62
+
63
+ def fail(*lines: str) -> NoReturn:
64
+ """Отказ словами, без трассировки: её здесь некому читать."""
65
+ for line in lines:
66
+ print(line, file=sys.stderr, flush=True)
67
+ raise SystemExit(3)
68
+
69
+
70
+ def _version_key(path: Path) -> Tuple[int, ...]:
71
+ """Ключ сортировки для каталогов вида node-20.11.1: числами, а не строкой."""
72
+ numbers = re.findall(r"\d+", str(path))
73
+ return tuple(int(number) for number in numbers[-3:]) or (0,)
74
+
75
+
76
+ def _candidates() -> Iterator[str]:
77
+ """Где бывает node, в порядке убывания доверия.
78
+
79
+ PATH — первым: если преподаватель поставил себе Node сам, он ожидает именно
80
+ свой. Дальше обычные места установщиков: тот же Homebrew кладёт node в
81
+ /opt/homebrew/bin, которого нет в PATH у процессов, запущенных не из
82
+ оболочки, — а pip-скрипт вполне может быть запущен из чего угодно.
83
+ Менеджеры версий (nvm, fnm, volta, asdf) перебираются последними и сверху
84
+ вниз по версии: у них node в PATH появляется только после `source`, и без
85
+ этого списка `colloq` в свежем терминале говорил бы «поставьте Node»
86
+ человеку, у которого он стоит.
87
+ """
88
+ found = shutil.which("node")
89
+ if found:
90
+ yield found
91
+ for fixed in (
92
+ "/opt/homebrew/bin/node",
93
+ "/usr/local/bin/node",
94
+ "/usr/bin/node",
95
+ "/opt/local/bin/node",
96
+ "/snap/bin/node",
97
+ ):
98
+ yield fixed
99
+ home = Path.home()
100
+ for pattern in (
101
+ ".nvm/versions/node/*/bin/node",
102
+ ".local/share/fnm/node-versions/*/installation/bin/node",
103
+ ".fnm/node-versions/*/installation/bin/node",
104
+ ".volta/tools/image/node/*/bin/node",
105
+ ".asdf/installs/nodejs/*/bin/node",
106
+ ):
107
+ for path in sorted(home.glob(pattern), key=_version_key, reverse=True):
108
+ yield str(path)
109
+
110
+
111
+ def _node_version(node: str) -> Optional[Tuple[int, int, int]]:
112
+ try:
113
+ done = subprocess.run(
114
+ [node, "--version"], capture_output=True, text=True, timeout=15
115
+ )
116
+ except (OSError, subprocess.SubprocessError):
117
+ return None
118
+ match = re.match(r"v(\d+)\.(\d+)\.(\d+)", (done.stdout or "").strip())
119
+ if done.returncode != 0 or not match:
120
+ return None
121
+ return (int(match.group(1)), int(match.group(2)), int(match.group(3)))
122
+
123
+
124
+ def find_node() -> str:
125
+ """Первый работающий node не ниже MIN_NODE.
126
+
127
+ Отдельно разбирается случай «node есть, но старый»: сказать такому человеку
128
+ «Node не найден» — значит отправить его ставить второй раз то, что у него
129
+ стоит, и удивляться, почему не помогает.
130
+ """
131
+ seen = set()
132
+ old: Optional[str] = None
133
+ for candidate in _candidates():
134
+ if candidate in seen:
135
+ continue
136
+ seen.add(candidate)
137
+ if not os.path.isfile(candidate) or not os.access(candidate, os.X_OK):
138
+ continue
139
+ version = _node_version(candidate)
140
+ if version is None:
141
+ continue
142
+ if version[0] >= MIN_NODE:
143
+ return candidate
144
+ if old is None:
145
+ old = "%s (v%d.%d.%d)" % (candidate, version[0], version[1], version[2])
146
+ if old:
147
+ fail(
148
+ "Node is too old: " + old,
149
+ "Colloq needs Node %d or newer." % MIN_NODE,
150
+ "macOS: brew install node · Debian/Ubuntu: see https://nodejs.org/en/download",
151
+ )
152
+ fail(
153
+ "Node.js not found — Colloq cannot start without it.",
154
+ "Node %d or newer is required." % MIN_NODE,
155
+ "macOS: brew install node · Debian/Ubuntu: apt install nodejs (or https://nodejs.org/en/download)",
156
+ "If Node is installed somewhere unusual, add it to PATH.",
157
+ )
158
+
159
+
160
+ def find_npm(node: str) -> Optional[str]:
161
+ """npm рядом с node или в PATH.
162
+
163
+ Рядом — первым: у менеджеров версий (nvm, fnm) в PATH может лежать npm от
164
+ совсем другой версии Node, и ставить нативные модули той версией, которой
165
+ их потом не запускать, — это тихая поломка better-sqlite3 на ровном месте.
166
+ """
167
+ names = ["npm.cmd", "npm"] if os.name == "nt" else ["npm"]
168
+ for name in names:
169
+ near = Path(node).parent / name
170
+ if near.is_file():
171
+ return str(near)
172
+ for name in names:
173
+ found = shutil.which(name)
174
+ if found:
175
+ return found
176
+ return None
177
+
178
+
179
+ def _wanted() -> dict:
180
+ """Чего ждут node_modules: версия приложения и список зависимостей.
181
+
182
+ Сравнивается целиком, а не «каталог существует»: после обновления колеса
183
+ приложение новое, а node_modules рядом — от прошлой версии, и они молча
184
+ подходят по именам, но не по содержимому.
185
+ """
186
+ data = json.loads((APP / "package.json").read_text(encoding="utf-8"))
187
+ return {
188
+ "version": data.get("version"),
189
+ "dependencies": data.get("dependencies", {}),
190
+ }
191
+
192
+
193
+ def _ready(wanted: dict) -> bool:
194
+ if not (APP / "node_modules").is_dir():
195
+ return False
196
+ try:
197
+ return json.loads(STAMP.read_text(encoding="utf-8")) == wanted
198
+ except (OSError, ValueError):
199
+ return False
200
+
201
+
202
+ def _install(npm: str, wanted: dict) -> None:
203
+ # flush у каждой строки — не суеверие. Дальше идёт чужой процесс (npm), а
204
+ # в конце os.execve, который выбрасывает буфер стандартного вывода не
205
+ # напечатав: при выводе в файл или в конвейер обе эти строки пропадали
206
+ # целиком, и разовая минута ожидания выглядела зависанием без объяснений.
207
+ print("Installing Colloq's Node environment — once, a minute or two.", flush=True)
208
+ print("Directory: %s" % APP, flush=True)
209
+ done = subprocess.run(
210
+ [npm, "install", "--omit=dev", "--no-audit", "--no-fund"],
211
+ cwd=str(APP),
212
+ )
213
+ if done.returncode != 0:
214
+ fail(
215
+ "",
216
+ "Could not install the Node environment (npm exited with %d)." % done.returncode,
217
+ "Run it by hand: cd %s && npm install --omit=dev" % APP,
218
+ )
219
+ STAMP.write_text(json.dumps(wanted, ensure_ascii=False, indent=2), encoding="utf-8")
220
+ print("Environment ready.\n", flush=True)
221
+
222
+
223
+ def ensure_modules(node: str) -> None:
224
+ """node_modules рядом с приложением — ставятся один раз, на этой машине.
225
+
226
+ Почему их нет в колесе: better-sqlite3 и @resvg/resvg-js — нативные, и
227
+ колесо с ними перестало бы быть одним на всех (своё на macOS arm64, своё на
228
+ Linux x86_64, своё на каждую версию Node ABI). Цена такого решения — одна
229
+ минута на первом запуске, и она названа вслух, а не проведена молча.
230
+ """
231
+ wanted = _wanted()
232
+ if _ready(wanted):
233
+ return
234
+ npm = find_npm(node)
235
+ if not npm:
236
+ fail(
237
+ "No npm next to Node, and the server environment cannot be installed without it.",
238
+ "npm normally ships with Node: check `npm --version`.",
239
+ "What should happen: cd %s && npm install --omit=dev" % APP,
240
+ )
241
+ if not os.access(str(APP), os.W_OK):
242
+ fail(
243
+ "The application directory is read-only: %s" % APP,
244
+ "The first run installs the server's node_modules there, so it needs to be writable.",
245
+ "Install Colloq into an environment of your own (python3 -m venv ~/colloq && ~/colloq/bin/pip install colloq),",
246
+ "or pip install --user colloq — and run it as yourself.",
247
+ )
248
+ # Замок на случай двух терминалов сразу: npm в одном каталоге двумя
249
+ # процессами — это половина дерева от одного и половина от другого.
250
+ deadline = time.time() + LOCK_WAIT_SEC
251
+ while True:
252
+ try:
253
+ handle = os.open(str(LOCK), os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o644)
254
+ except FileExistsError:
255
+ if _ready(wanted):
256
+ return
257
+ try:
258
+ stale = time.time() - LOCK.stat().st_mtime > LOCK_WAIT_SEC
259
+ except OSError:
260
+ stale = False
261
+ if stale:
262
+ LOCK.unlink(missing_ok=True)
263
+ continue
264
+ if time.time() > deadline:
265
+ fail(
266
+ "Another process is already installing the Node environment, and it is taking too long.",
267
+ "If that process died, remove the lock: rm %s" % LOCK,
268
+ )
269
+ print("Another run is installing the Node environment — waiting…", flush=True)
270
+ time.sleep(2)
271
+ continue
272
+ os.close(handle)
273
+ try:
274
+ if _ready(wanted):
275
+ return
276
+ _install(npm, wanted)
277
+ finally:
278
+ LOCK.unlink(missing_ok=True)
279
+ return
280
+
281
+
282
+ def main() -> int:
283
+ if not ENTRY.is_file() or not MARK.is_file():
284
+ fail(
285
+ "The package carries no built application (%s)." % APP,
286
+ "That happens to a wheel built without `make pack`. Rebuild it: make wheel.",
287
+ )
288
+ node = find_node()
289
+ arguments = sys.argv[1:]
290
+ if not arguments or not set(arguments) & TELLING:
291
+ ensure_modules(node)
292
+
293
+ environment = dict(os.environ)
294
+ # Где лежит приложение. Всё остальное — состояние занятия — CLI ищет сам:
295
+ # у него для этого COLLOQ_HOME.
296
+ environment["COLLOQ_APP_DIR"] = str(APP)
297
+ # Каталог, из которого позвали: относительные пути в аргументах
298
+ # (`colloq restore --db backups/…`) должны считаться от него, а не от
299
+ # приложения.
300
+ environment.setdefault("COLLOQ_CWD", os.getcwd())
301
+
302
+ argv = [str(node), str(ENTRY)] + arguments
303
+ # Перед подменой процесса — вытолкнуть всё своё: execve не возвращается и
304
+ # ничего не дописывает.
305
+ sys.stdout.flush()
306
+ sys.stderr.flush()
307
+ if os.name == "nt":
308
+ # На Windows execve не заменяет процесс, а порождает второй, и родитель
309
+ # возвращается немедленно: там приходится ждать ребёнка руками.
310
+ return subprocess.call(argv, env=environment)
311
+ os.execve(str(node), argv, environment)
312
+
313
+
314
+ if __name__ == "__main__":
315
+ sys.exit(main())
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "colloq",
3
+ "version": "0.2.0",
4
+ "builtAt": "2026-09-23T12:57:49.000Z",
5
+ "builtBy": "scripts/pack.mts",
6
+ "node": "v22.23.2",
7
+ "layout": {
8
+ "cli": "cli/colloq.mjs",
9
+ "launch": "cli/launch.mjs",
10
+ "lease": "cli/public-url-lease.mjs",
11
+ "server": "server/dist/server.js",
12
+ "web": "web/dist",
13
+ "kernel": "kernel",
14
+ "scripts": "scripts"
15
+ },
16
+ "install": {
17
+ "manager": "npm",
18
+ "args": [
19
+ "install",
20
+ "--omit=dev"
21
+ ],
22
+ "cwd": "."
23
+ },
24
+ "parts": [
25
+ {
26
+ "path": "cli/colloq.mjs",
27
+ "files": 1,
28
+ "bytes": 100112
29
+ },
30
+ {
31
+ "path": "cli/launch.mjs",
32
+ "files": 1,
33
+ "bytes": 59864
34
+ },
35
+ {
36
+ "path": "cli/public-url-lease.mjs",
37
+ "files": 1,
38
+ "bytes": 6892
39
+ },
40
+ {
41
+ "path": "server/dist",
42
+ "files": 3,
43
+ "bytes": 5451220
44
+ },
45
+ {
46
+ "path": "server/assets",
47
+ "files": 5,
48
+ "bytes": 220703
49
+ },
50
+ {
51
+ "path": "web/dist",
52
+ "files": 183,
53
+ "bytes": 17461331
54
+ },
55
+ {
56
+ "path": "kernel",
57
+ "files": 8,
58
+ "bytes": 12776
59
+ },
60
+ {
61
+ "path": "scripts",
62
+ "files": 9,
63
+ "bytes": 199615
64
+ }
65
+ ]
66
+ }
colloq/_app/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aleksandr Kalashnikov
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,106 @@
1
+ # Third-party notices
2
+
3
+ Colloq's own code is licensed under the [MIT License](LICENSE). This repository
4
+ and everything built from it also contain third-party work under other licenses:
5
+
6
+ - the web bundle (`web/dist`);
7
+ - the server bundle;
8
+ - the Docker images;
9
+ - the pip wheel;
10
+ - the colloq.ru site.
11
+
12
+ This file lists the assets that are committed to the repository and the
13
+ non-MIT components that ship in the builds.
14
+
15
+ ## Fonts committed to the repository
16
+
17
+ ### JetBrains Mono: SIL Open Font License 1.1
18
+
19
+ Copyright 2020 The JetBrains Mono Project Authors
20
+ (<https://github.com/JetBrains/JetBrainsMono>).
21
+
22
+ | Files | License text |
23
+ | --- | --- |
24
+ | `web/public/fonts/jetbrains-mono-*.woff2` | `web/public/fonts/JetBrainsMono-OFL.txt` |
25
+ | `site/fonts/jetbrains-mono-*.woff2` | `site/fonts/JetBrainsMono-OFL.txt` |
26
+ | `server/assets/fonts/JetBrainsMono-Medium.ttf` (link-preview cards) | `server/assets/fonts/JetBrainsMono-OFL.txt` |
27
+
28
+ ### HSE Sans: not covered by the MIT license
29
+
30
+ | Files |
31
+ | --- |
32
+ | `web/public/fonts/hse-sans-{400,600,700,900}.woff2` |
33
+ | `site/fonts/hse-sans-{400,600,700,900}.woff2` |
34
+ | `server/assets/fonts/HSESans-{Regular,Bold,Black}.otf` |
35
+
36
+ HSE Sans is the corporate typeface of HSE University (National Research
37
+ University Higher School of Economics). It is **not** covered by this project's
38
+ MIT license, and this repository grants no rights to it: the files are here only
39
+ to render Colloq's own interface. Do not reuse them outside Colloq. A fork that
40
+ needs a freely licensed typeface can swap these files and the `@font-face`
41
+ rules in `web/index.html`, `site/styles.css` and `docs/build.py`; the font stacks
42
+ already fall back to system fonts.
43
+
44
+ ## Other assets committed to the repository
45
+
46
+ ### pdf.js worker: Apache License 2.0
47
+
48
+ `web/public/pdf/pdf.worker.min.mjs` is copied unmodified from `pdfjs-dist`
49
+ 6.2.108 by `npm run pdf:worker` (Copyright Mozilla Foundation). The file keeps
50
+ its license header. License: <https://www.apache.org/licenses/LICENSE-2.0>.
51
+
52
+ ### plotly.js strict bundle: MIT
53
+
54
+ `web/public/plotly/plotly.min.js` is copied unmodified from
55
+ `plotly.js-strict-dist-min` 4.1.1 by `npm run plotly:dist` (Copyright Plotly,
56
+ Inc.) and keeps its license header. It is **not** committed — the copy step runs
57
+ on every build, and the file is ignored in `.gitignore`. The strict bundle is the
58
+ one without function constructors, so the sandboxed frame that renders a figure
59
+ needs no `unsafe-eval` in its policy (`server/src/plotly-frame.ts`).
60
+
61
+ ## Components shipped in builds under non-MIT licenses
62
+
63
+ npm installs these; they are not committed. Each package carries its own license
64
+ file in `node_modules`, and the Docker images and a pip installation contain
65
+ them there.
66
+
67
+ | Component | License | Where it ends up |
68
+ | --- | --- | --- |
69
+ | `pdfjs-dist` | Apache-2.0 | web bundle (PDF viewer) |
70
+ | `dompurify` | MPL-2.0 OR Apache-2.0 | web bundle (HTML sanitising) |
71
+ | `satori` | MPL-2.0 | server (link-preview cards) |
72
+ | `@resvg/resvg-js` and its platform binaries | MPL-2.0 | server (link-preview cards) |
73
+ | `openai` | Apache-2.0 | server (Oracle) |
74
+ | `dotenv`, `webidl-conversions` | BSD-2-Clause | server |
75
+ | `qs`, `ieee754` | BSD-3-Clause | server |
76
+
77
+ Every other runtime npm dependency is MIT or ISC, per `package-lock.json`.
78
+ The KaTeX fonts in the web bundle are part of KaTeX (MIT).
79
+
80
+ ## The vast.ai image
81
+
82
+ `deploy/vast/Dockerfile` adds prebuilt binaries to the Node base image. The
83
+ versions and checksums of `frpc` and `cloudflared` are pinned in that file:
84
+
85
+ | Component | License | Source |
86
+ | --- | --- | --- |
87
+ | `frpc` (frp) | Apache-2.0 | <https://github.com/fatedier/frp> |
88
+ | `cloudflared` | Apache-2.0 | <https://github.com/cloudflare/cloudflared> |
89
+ | Docker CLI and `docker-buildx` | Apache-2.0 | copied from the official `docker:*-cli` image |
90
+
91
+ The Debian packages installed with `apt-get` (`tini`, `curl`, `sqlite3`,
92
+ `procps`) and those of the `node:22-bookworm-slim` base keep their copyright
93
+ files under `/usr/share/doc` in the image.
94
+
95
+ ## Kernel images
96
+
97
+ Room kernel images install Python packages from PyPI at build time:
98
+
99
+ - `kernel/requirements.txt`: Jupyter Server, ipykernel, NumPy, pandas,
100
+ Matplotlib, Plotly, scikit-learn, requests and Black;
101
+ - `kernel/environments/*.txt`, for example seaborn, PyTorch (the CPU build in
102
+ `cv`, the CUDA build in `base-gpu`), torchvision, timm, OpenCV and
103
+ Transformers.
104
+
105
+ Each package keeps its own license inside the image. The CUDA build of PyTorch
106
+ brings NVIDIA CUDA libraries, which are covered by NVIDIA's license terms.