@mavea/mavea 1.0.7

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 (557) hide show
  1. package/CHANGELOG.md +159 -0
  2. package/DISCLAIMER.md +16 -0
  3. package/LICENSE +75 -0
  4. package/PRIVACY.md +127 -0
  5. package/README.md +248 -0
  6. package/SECURITY.md +72 -0
  7. package/SUPPORT.md +13 -0
  8. package/TERMS.md +122 -0
  9. package/THIRD-PARTY.txt +639 -0
  10. package/TRADEMARKS.md +15 -0
  11. package/bin/mavea.mjs +1079 -0
  12. package/dist/.vite/manifest.json +4261 -0
  13. package/dist/_headers +52 -0
  14. package/dist/assets/AppCommandPalette-DUMSp3rd.js +1 -0
  15. package/dist/assets/AtlasView-B5fkQhO6.css +1 -0
  16. package/dist/assets/AtlasView-Dl2fssaG.js +1 -0
  17. package/dist/assets/BlockEmpty-CLtVQf6c.js +1 -0
  18. package/dist/assets/BriefingPlayer-DO205Qkh.js +1 -0
  19. package/dist/assets/BriefingPlayer-TpFZB77N.css +1 -0
  20. package/dist/assets/CanvasSkeleton-BZZPCuRZ.js +2 -0
  21. package/dist/assets/CanvasSkeleton-iy6DBvde.css +1 -0
  22. package/dist/assets/CardEditor-BY1bZiV6.css +1 -0
  23. package/dist/assets/CardEditor-DzJXb0G7.js +1 -0
  24. package/dist/assets/CheckLogRail-F4AlgAcc.css +1 -0
  25. package/dist/assets/CheckLogRail-wUfby67Z.js +1 -0
  26. package/dist/assets/CodeOutput-Kh6ldxQz.js +97 -0
  27. package/dist/assets/CommandPalette-Bb-eErih.css +1 -0
  28. package/dist/assets/CommandPalette-Kl00mMZm.js +1 -0
  29. package/dist/assets/CourseLessonReader-COmTQdSK.css +1 -0
  30. package/dist/assets/CourseLessonReader-MBHoknUz.js +2 -0
  31. package/dist/assets/CourseRail-CUa7uIGG.css +1 -0
  32. package/dist/assets/CourseRail-q6x5cCpW.js +1 -0
  33. package/dist/assets/CoursesApp-v89OVJvg.js +2 -0
  34. package/dist/assets/CrossExamPanel-CZ0ShGma.css +1 -0
  35. package/dist/assets/CrossExamPanel-DH04p6VS.js +1 -0
  36. package/dist/assets/DashPresent-BYXbPuGj.js +1 -0
  37. package/dist/assets/DashPresent-DQYcgp-W.css +1 -0
  38. package/dist/assets/DashboardDetail-B50ZkeVK.js +2 -0
  39. package/dist/assets/DashboardLoopRuntime-CFXPx3v_.js +1 -0
  40. package/dist/assets/DashboardOverview-C-FdGWM5.js +1 -0
  41. package/dist/assets/DashboardSettings-Dmw6JTbI.js +1 -0
  42. package/dist/assets/DashboardsApp-Dx-EsaPW.css +1 -0
  43. package/dist/assets/DashboardsApp-JSY_bVfi.js +2 -0
  44. package/dist/assets/DeepZoomApp-BFNwli7l.js +37 -0
  45. package/dist/assets/DeepZoomApp-fRXCJHHZ.css +1 -0
  46. package/dist/assets/DelegatePanel-BK7rEhIc.js +26 -0
  47. package/dist/assets/DelegatePanel-_9RZiLHM.css +1 -0
  48. package/dist/assets/DemoGallery-BPtVG7_y.js +1 -0
  49. package/dist/assets/DropSelect-DUhdQSta.js +1 -0
  50. package/dist/assets/ExportModal-D4WpAg2c.css +1 -0
  51. package/dist/assets/ExportModal-D65cGyj_.js +2 -0
  52. package/dist/assets/ExtractionPreview-Iwfg2Ogl.js +18 -0
  53. package/dist/assets/FallbackCard-B489rTd9.js +1 -0
  54. package/dist/assets/FallbackCard-D_6ps1xA.css +1 -0
  55. package/dist/assets/FeatureUseNotice-CWt7gOwS.css +1 -0
  56. package/dist/assets/FeatureUseNotice-DPtQ0OGb.js +1 -0
  57. package/dist/assets/FlagshipCTA-BXntKhxb.js +1 -0
  58. package/dist/assets/FlagshipCommandPalette-BDJfasI-.js +1 -0
  59. package/dist/assets/FlagshipShowcase-DJ8Gxk_G.js +1 -0
  60. package/dist/assets/FlashcardsApp-CLNo4WX7.js +1 -0
  61. package/dist/assets/ForecastPanel-Da2ZBUrE.css +1 -0
  62. package/dist/assets/ForecastPanel-PBO2-dgS.js +1 -0
  63. package/dist/assets/GalleryApp-B1CB3_81.css +1 -0
  64. package/dist/assets/GalleryApp-DM03CMRy.js +1 -0
  65. package/dist/assets/HonestByDesign-Dqyth6nW.js +1 -0
  66. package/dist/assets/HowItWorks-C0zosIvq.js +1 -0
  67. package/dist/assets/LegalApp-DZBdExTy.js +1 -0
  68. package/dist/assets/LegalMarkdownDocument-CSZhRG5r.js +3 -0
  69. package/dist/assets/LegalPageShell-C_HottbD.css +1 -0
  70. package/dist/assets/LegalPageShell-DTeXqWyc.js +1 -0
  71. package/dist/assets/LeverPanel-DhpJY76O.css +1 -0
  72. package/dist/assets/LeverPanel-aZfhtVJX.js +1 -0
  73. package/dist/assets/Library-D2XGNXz7.js +1 -0
  74. package/dist/assets/Library-DmrtHjUf.css +1 -0
  75. package/dist/assets/LibraryOverlay-B2WQSgwr.js +1 -0
  76. package/dist/assets/LibraryOverlay-CnKWDMG_.css +1 -0
  77. package/dist/assets/LiveApp-B9zwXlGC.js +61 -0
  78. package/dist/assets/LiveApp-BVZGpJ1S.css +1 -0
  79. package/dist/assets/LiveSettings-CmgrMqvK.css +1 -0
  80. package/dist/assets/LiveSettings-D9OcWlm6.js +6 -0
  81. package/dist/assets/MindMapViewerDrawer-YYyZqmAd.js +1 -0
  82. package/dist/assets/MindShape-Ca3MXEgK.css +1 -0
  83. package/dist/assets/MindShape-mTlN5mPZ.js +1 -0
  84. package/dist/assets/Orb-B0NEVfqL.js +1 -0
  85. package/dist/assets/PinToDashboard-_kIOHs0k.js +1 -0
  86. package/dist/assets/PlanReview-33V8GTNB.js +1 -0
  87. package/dist/assets/Presence-CzLJIeSD.js +1 -0
  88. package/dist/assets/PresentationDeck-BDoGecxr.js +1 -0
  89. package/dist/assets/PrismApp-PfBTFhMF.js +2 -0
  90. package/dist/assets/PrismAskController-DBoxQSJR.css +1 -0
  91. package/dist/assets/PrismAskController-DbQDrlU9.js +37 -0
  92. package/dist/assets/PrismBriefingPlayer-1fvlfLR8.js +2 -0
  93. package/dist/assets/PrismOverlay-B7lEspdu.js +131 -0
  94. package/dist/assets/PrismOverlay-BTfAicBt.css +1 -0
  95. package/dist/assets/PrivacyApp-CR4lRs0L.js +128 -0
  96. package/dist/assets/Quiz-CUwtekfu.js +1 -0
  97. package/dist/assets/Recap-DCiiWNiV.css +1 -0
  98. package/dist/assets/Recap-H5fhj5Tc.js +1 -0
  99. package/dist/assets/ReelJelly-3jSVsJsy.js +1 -0
  100. package/dist/assets/RehearsalPanel-BCU615wg.css +1 -0
  101. package/dist/assets/RehearsalPanel-BVgjrOPg.js +6 -0
  102. package/dist/assets/ReplayCard-BpYO5vX-.js +2 -0
  103. package/dist/assets/ReplayOverlay-DXmVdeIr.js +1 -0
  104. package/dist/assets/RewindOverlay-BusfWeFq.js +1 -0
  105. package/dist/assets/RewindOverlay-C92qgvmi.css +1 -0
  106. package/dist/assets/RippleApp-9KrJPYgN.js +2 -0
  107. package/dist/assets/RippleAskController-BNAUoIW0.css +1 -0
  108. package/dist/assets/RippleAskController-CO9wE7hU.js +34 -0
  109. package/dist/assets/RippleOverlay-DMHLbfh8.js +6 -0
  110. package/dist/assets/RippleOverlay-_iiZsQVZ.css +1 -0
  111. package/dist/assets/SeeDontRead-C389viue.js +1 -0
  112. package/dist/assets/ShareModal-C8S7ySls.css +1 -0
  113. package/dist/assets/ShareModal-ClnEkudw.js +32 -0
  114. package/dist/assets/ShipCascade-BUtwvQoW.js +1 -0
  115. package/dist/assets/ShipCascade-CyFnrlET.css +1 -0
  116. package/dist/assets/ShipCourse-B7lG6DYW.js +1 -0
  117. package/dist/assets/ShipCourse-DcT7PUSm.css +1 -0
  118. package/dist/assets/ShipGate-BVZfCllb.css +1 -0
  119. package/dist/assets/ShipGate-BYYu9WVq.js +2 -0
  120. package/dist/assets/ShipHotspots-Bj2CovtT.js +1 -0
  121. package/dist/assets/ShipHotspots-CLOMutPv.css +1 -0
  122. package/dist/assets/ShipIncident-BUHvFFiC.css +1 -0
  123. package/dist/assets/ShipIncident-DcmindmE.js +2 -0
  124. package/dist/assets/ShipMigration-BznCskNg.css +1 -0
  125. package/dist/assets/ShipMigration-s0hVjDQl.js +1 -0
  126. package/dist/assets/ShipOnboarding-BC9Vxkpa.css +1 -0
  127. package/dist/assets/ShipOnboarding-ekbHL7OH.js +1 -0
  128. package/dist/assets/ShipRead-BVNgQT6L.js +1 -0
  129. package/dist/assets/ShipRollout-BdUQzMyP.js +1 -0
  130. package/dist/assets/ShipRollout-CqeObx5b.css +1 -0
  131. package/dist/assets/ShipSuggestions-B6fKYh8L.js +1 -0
  132. package/dist/assets/ShipSuggestions-BKRvk5Mj.css +1 -0
  133. package/dist/assets/ShipWorkspace-3D3Rrs1p.js +2 -0
  134. package/dist/assets/ShipWorkspace-D6XQpGEN.css +1 -0
  135. package/dist/assets/SignatureLoop-C60r8Wm5.js +1 -0
  136. package/dist/assets/SrsReview-BoaYb6XN.js +1 -0
  137. package/dist/assets/SrsReview-DmGdGxT0.css +1 -0
  138. package/dist/assets/SynthesisApp-vJ2Jq0Qe.js +2 -0
  139. package/dist/assets/SynthesisOverlay-CkERphvG.js +66 -0
  140. package/dist/assets/TermsApp-C70tW6m6.js +123 -0
  141. package/dist/assets/TopicCanvas-CT0eSj2M.js +2 -0
  142. package/dist/assets/TopicCanvas-D2Nax9II.css +1 -0
  143. package/dist/assets/TourPrism-BvucSkcs.js +1 -0
  144. package/dist/assets/TrackerTile-CasZNmt4.js +2 -0
  145. package/dist/assets/TwoSurfaces-C90X7Aj5.js +1 -0
  146. package/dist/assets/UnderstoodPanel-CHPD2Ttj.js +1 -0
  147. package/dist/assets/UnderstoodPanel-Q6GX1TFq.css +1 -0
  148. package/dist/assets/WhyMachineOverlay-BZk3PVTl.css +1 -0
  149. package/dist/assets/WhyMachineOverlay-CB0_n6Sy.js +1 -0
  150. package/dist/assets/WowFeatures-Ci_H5QYX.js +1 -0
  151. package/dist/assets/ZoomDeck-B2SjNwY7.css +1 -0
  152. package/dist/assets/ZoomDeck-BQHO79ZU.js +1 -0
  153. package/dist/assets/__vite-browser-external-BufR4LQa.js +1 -0
  154. package/dist/assets/ai-Ba4lZJ9Y.js +12 -0
  155. package/dist/assets/alternateFinishes-DZ-rkpkX.js +494 -0
  156. package/dist/assets/annotationAuto-B0nihA4F.js +1 -0
  157. package/dist/assets/annotationReel-BcU9Q0Sw.js +1 -0
  158. package/dist/assets/annotations-D2K9A5GB.js +1 -0
  159. package/dist/assets/backup-c-Btapzp.js +1 -0
  160. package/dist/assets/blockLabel-DAqaHKy3.js +1 -0
  161. package/dist/assets/c-DWGmR78Y.js +1 -0
  162. package/dist/assets/c-SSSlmE39.js +1 -0
  163. package/dist/assets/cache-BumECILO.js +1 -0
  164. package/dist/assets/canvas-QTEIiK23.js +1 -0
  165. package/dist/assets/catalog-BHjl-1uH.js +2 -0
  166. package/dist/assets/catalog-Dqaq2YFd.js +1 -0
  167. package/dist/assets/cfo.generated-Bjl5OGay.js +1 -0
  168. package/dist/assets/chapters-BiUsDClQ.js +1 -0
  169. package/dist/assets/charts1-EOoxU_K3.js +1 -0
  170. package/dist/assets/charts2-tyUiQPff.js +1 -0
  171. package/dist/assets/chunk-BVpWp9h4.js +1 -0
  172. package/dist/assets/citation-BcvmjKS8.js +1 -0
  173. package/dist/assets/code-CpjYCNS8.js +8 -0
  174. package/dist/assets/codeContext-0UMRSGRr.js +5 -0
  175. package/dist/assets/colors-D49Req-e.js +1 -0
  176. package/dist/assets/compose-BJ9EfcQS.js +3 -0
  177. package/dist/assets/contentVault-DzTYWcbD.js +1 -0
  178. package/dist/assets/core-DEFyprhs.js +1 -0
  179. package/dist/assets/corpus.generated-oX9uU93S.js +11 -0
  180. package/dist/assets/courseSeed-CZdNBTTs.js +1 -0
  181. package/dist/assets/courseStore-DfafnL-9.js +1 -0
  182. package/dist/assets/courses-BkH1ntDL.css +1 -0
  183. package/dist/assets/cpp-BGdv8dzs.js +1 -0
  184. package/dist/assets/cpp-CNAAXZl-.js +1 -0
  185. package/dist/assets/csharp-oqKa8noW.js +1 -0
  186. package/dist/assets/css-CdtkT07m.js +1 -0
  187. package/dist/assets/css-LbU1hoFO.js +1 -0
  188. package/dist/assets/dag-C3Xx8pOD.js +2 -0
  189. package/dist/assets/dashboardEvents-DR9d_c74.js +2 -0
  190. package/dist/assets/dashboards-rcWxdoXI.css +1 -0
  191. package/dist/assets/deckLine-NBd3MyGT.js +1 -0
  192. package/dist/assets/demoPaletteItems-NPCSaSU6.js +1 -0
  193. package/dist/assets/dev.generated-DYgVxL_4.js +73 -0
  194. package/dist/assets/diagrams-CaSfjT4I.js +1 -0
  195. package/dist/assets/diff-woXpYk--.js +1 -0
  196. package/dist/assets/display-OvC57CMc.js +9 -0
  197. package/dist/assets/dist-BFx3nZg4.js +2785 -0
  198. package/dist/assets/dist-xRGLOZwv.js +14 -0
  199. package/dist/assets/docs-B2aDtm5K.js +1 -0
  200. package/dist/assets/everyday-CoZa4kDZ.js +1 -0
  201. package/dist/assets/explode-WBykfhqW.js +13 -0
  202. package/dist/assets/extractNumbers-CuhagRkd.js +1 -0
  203. package/dist/assets/facts-BBTt3ImG.js +1 -0
  204. package/dist/assets/finance-Bxn_Q1p0.js +1 -0
  205. package/dist/assets/fit-0HxU-rtB.js +1 -0
  206. package/dist/assets/fitText-CDFmCQzl.js +1 -0
  207. package/dist/assets/flows-BL81J7yN.js +1 -0
  208. package/dist/assets/format-DlZTyeMy.js +1 -0
  209. package/dist/assets/format-hc2QOoIu.js +1 -0
  210. package/dist/assets/forms-DeoXOxdD.js +1 -0
  211. package/dist/assets/generate-8odaTyr4.js +17 -0
  212. package/dist/assets/generateBeats-b64CL54v.js +1 -0
  213. package/dist/assets/generateCourse-Cj_-vDWu.js +28 -0
  214. package/dist/assets/generateLive-D4B30fnF.js +52 -0
  215. package/dist/assets/geometry-DXAbnb0J.js +1 -0
  216. package/dist/assets/gesture-DabMpPcq.js +2 -0
  217. package/dist/assets/github-dark-C-LZuMrd.js +1 -0
  218. package/dist/assets/github-light-EUqPIrTm.js +1 -0
  219. package/dist/assets/githubBrowser-DywfDeYx.js +1 -0
  220. package/dist/assets/githubToken-DtcLYqwm.js +11 -0
  221. package/dist/assets/go-rLFTqkRN.js +1 -0
  222. package/dist/assets/homeTarget-BgWKI_E1.js +1 -0
  223. package/dist/assets/html-BcACZWSP.js +1 -0
  224. package/dist/assets/html-DDNsd9lV.js +1 -0
  225. package/dist/assets/http-CJCtjYpA.js +2 -0
  226. package/dist/assets/icons-CHVz6kv_.js +1 -0
  227. package/dist/assets/index-CepQEz75.css +1 -0
  228. package/dist/assets/index-y8GL67bB.js +22 -0
  229. package/dist/assets/index.es-CRLiWwiA.js +5 -0
  230. package/dist/assets/info-CiqHR5cs.js +1 -0
  231. package/dist/assets/java-BtIQNnN0.js +1 -0
  232. package/dist/assets/java-DxlWrXwe.js +1 -0
  233. package/dist/assets/javascript-BzbQS41l.js +1 -0
  234. package/dist/assets/javascript-Yelw-5ZN.js +1 -0
  235. package/dist/assets/json-B0N3XRZU.js +1 -0
  236. package/dist/assets/json-B88Oqtu5.js +1 -0
  237. package/dist/assets/json-CaljABEy.js +1 -0
  238. package/dist/assets/jspdf.es.min-C-Pf79px.js +79 -0
  239. package/dist/assets/jsx-D3KKoC5L.js +1 -0
  240. package/dist/assets/jsx-hEbJ82HF.js +1 -0
  241. package/dist/assets/jsx-runtime-DaSxXjOY.js +1 -0
  242. package/dist/assets/katex-BMCG0xok.js +257 -0
  243. package/dist/assets/keyVault-DEsNh9FR.js +1 -0
  244. package/dist/assets/kokoro-D4GKpSL0.js +1 -0
  245. package/dist/assets/kotlin-DhhofPvG.js +1 -0
  246. package/dist/assets/layout-BFQOzXtG.js +1 -0
  247. package/dist/assets/layout-CkO0Psde.js +1 -0
  248. package/dist/assets/lazyRetry-D50B1UMY.js +1 -0
  249. package/dist/assets/leaflet-src-C_xhOQY7.js +1 -0
  250. package/dist/assets/learn-DVfqN-nf.js +1 -0
  251. package/dist/assets/lib-DMAxsZKm.css +1 -0
  252. package/dist/assets/lib-VFqp6Y3w.js +1 -0
  253. package/dist/assets/links--u_phi0x.js +1 -0
  254. package/dist/assets/liveSchema-CJ49PX5R.js +98 -0
  255. package/dist/assets/markdown-BYOwaDjH.js +1 -0
  256. package/dist/assets/mastery-BPjxP0fN.js +4 -0
  257. package/dist/assets/media-CsrUl279.js +43 -0
  258. package/dist/assets/model-BE0UHu1w.js +27 -0
  259. package/dist/assets/motion-S0NRruNj.js +1 -0
  260. package/dist/assets/nav-Czm-NzPV.js +1 -0
  261. package/dist/assets/neighborhoods-C-1Tjr1r.js +1 -0
  262. package/dist/assets/notes-A9t4jdfx.css +1 -0
  263. package/dist/assets/notes-lROPCorS.js +1 -0
  264. package/dist/assets/notify-Bz4wuE4T.js +1 -0
  265. package/dist/assets/now-C9-UAzTC.js +1 -0
  266. package/dist/assets/number-DXgzss0v.js +1 -0
  267. package/dist/assets/officeDoc-B6nEhNp4.js +3 -0
  268. package/dist/assets/openchemlib-nMLMmg26.js +23 -0
  269. package/dist/assets/overlays-JxbHtA2o.js +1 -0
  270. package/dist/assets/owners-FXDGInVq.js +2 -0
  271. package/dist/assets/pageVisibility-BxUVsw7Z.js +1 -0
  272. package/dist/assets/pdf-Df7PCH7q.js +11 -0
  273. package/dist/assets/pdf.worker.min-yatZIOMy.mjs +21 -0
  274. package/dist/assets/penStrokes-9Q_l7NTt.js +1 -0
  275. package/dist/assets/perfProbe-COAp9PAa.js +1 -0
  276. package/dist/assets/php-CYrBA5iX.js +1 -0
  277. package/dist/assets/pickers-B4-3HOR8.js +1 -0
  278. package/dist/assets/pin-Bya3BL_a.css +1 -0
  279. package/dist/assets/pin-CXSuPJYL.js +1 -0
  280. package/dist/assets/planTracker-7AMCQPNx.js +1 -0
  281. package/dist/assets/portal-DMw7C32j.js +1 -0
  282. package/dist/assets/pptxgen.es-CvTILmV9.js +115 -0
  283. package/dist/assets/predictions-CYS7Vo39.js +1 -0
  284. package/dist/assets/prewarm--whjQhWJ.js +1 -0
  285. package/dist/assets/prism-app-CXYElq8-.css +1 -0
  286. package/dist/assets/prism.generated-BhbHDHR6.js +41 -0
  287. package/dist/assets/prismExtract.worker-Cws9K1NG.js +13 -0
  288. package/dist/assets/prismGround.worker-CIvKqxm2.js +1 -0
  289. package/dist/assets/prismLayout.worker-CZO2CY7B.js +1 -0
  290. package/dist/assets/providers-wzm7E8Pa.js +1 -0
  291. package/dist/assets/purify.es-adlwq8Pz.js +3 -0
  292. package/dist/assets/python-gzcpVVnB.js +1 -0
  293. package/dist/assets/queue-DHeyJ32F.css +1 -0
  294. package/dist/assets/queue-J4h7E3uc.js +1 -0
  295. package/dist/assets/quietHours-BvcsBKKK.js +1 -0
  296. package/dist/assets/react-BQSORdFI.js +1 -0
  297. package/dist/assets/reference-sJzyMiL4.js +1 -0
  298. package/dist/assets/registry-1GuPBfQK.js +1 -0
  299. package/dist/assets/registry-2GJZ46M8.css +1 -0
  300. package/dist/assets/registry-2v0jlh5Q.js +2 -0
  301. package/dist/assets/registry-B8DStMJk.js +1 -0
  302. package/dist/assets/registry-B940W2j5.js +1 -0
  303. package/dist/assets/registry-BAoRB8rd.css +1 -0
  304. package/dist/assets/registry-BPvIPLke.js +1 -0
  305. package/dist/assets/registry-BVHiKEwE.css +1 -0
  306. package/dist/assets/registry-BcZXBr3y.css +1 -0
  307. package/dist/assets/registry-BffpZK5w.js +1 -0
  308. package/dist/assets/registry-BgGeRiDA.js +2 -0
  309. package/dist/assets/registry-Buq5uuCv.js +1 -0
  310. package/dist/assets/registry-BzNLfje1.css +1 -0
  311. package/dist/assets/registry-C3ezxLR3.css +1 -0
  312. package/dist/assets/registry-CAusoyBh.js +1 -0
  313. package/dist/assets/registry-CEVrY72Z.js +1 -0
  314. package/dist/assets/registry-CKjKq6ct.js +1 -0
  315. package/dist/assets/registry-CNgybLQv.css +1 -0
  316. package/dist/assets/registry-CRM7y8QT.css +1 -0
  317. package/dist/assets/registry-CRisz4Ca.css +1 -0
  318. package/dist/assets/registry-Cg7qUZPS.js +1 -0
  319. package/dist/assets/registry-Cix-yqch.js +1 -0
  320. package/dist/assets/registry-CtbuZWYz.css +1 -0
  321. package/dist/assets/registry-DCI_BeFJ.js +5 -0
  322. package/dist/assets/registry-DDLSYDVA.js +1 -0
  323. package/dist/assets/registry-DI1wSG1Y.js +1 -0
  324. package/dist/assets/registry-DJinB5ye.js +2 -0
  325. package/dist/assets/registry-DUvXX_d0.js +1 -0
  326. package/dist/assets/registry-DVzLacl_.css +1 -0
  327. package/dist/assets/registry-DWcNVyI2.css +1 -0
  328. package/dist/assets/registry-D_URPoL4.js +1 -0
  329. package/dist/assets/registry-Dkc7hN5T.js +1 -0
  330. package/dist/assets/registry-DmQlNN3y.js +1 -0
  331. package/dist/assets/registry-DpaaVbmL.css +1 -0
  332. package/dist/assets/registry-Dpb8EXHn.js +49 -0
  333. package/dist/assets/registry-Dru0JcTa.css +1 -0
  334. package/dist/assets/registry-DujBAgn_.js +1 -0
  335. package/dist/assets/registry-Dye5m3qt.css +1 -0
  336. package/dist/assets/registry-GeSy4i7R.css +1 -0
  337. package/dist/assets/registry-QcULZpdE.js +4 -0
  338. package/dist/assets/registry-T3i2Q9SI.css +1 -0
  339. package/dist/assets/registry-UFIuwh3i.css +1 -0
  340. package/dist/assets/registry-Xzq_Rpmm.css +1 -0
  341. package/dist/assets/registry-ZOWzhOHk.css +1 -0
  342. package/dist/assets/registry-aIH-Im5Y.css +1 -0
  343. package/dist/assets/registry-aazwkBJp.js +1 -0
  344. package/dist/assets/registry-ei6fIgBT.css +1 -0
  345. package/dist/assets/registry-types-DMuK0vJ6.js +1 -0
  346. package/dist/assets/registry-upbw6pyj.css +1 -0
  347. package/dist/assets/registry-zRPjWNl5.js +1 -0
  348. package/dist/assets/relate-A9ZeaaAI.js +1 -0
  349. package/dist/assets/richText-BkxJhl9N.js +1 -0
  350. package/dist/assets/ruby-BhrksQMt.js +1 -0
  351. package/dist/assets/run-BYP2YlZT.js +23 -0
  352. package/dist/assets/run-C_0B_41W.js +21 -0
  353. package/dist/assets/run-DSVvshaB.js +21 -0
  354. package/dist/assets/rust-Cfkwpbl8.js +1 -0
  355. package/dist/assets/safeCssColor-di93BXgG.js +1 -0
  356. package/dist/assets/safeImageUrl-CUkucDUu.js +1 -0
  357. package/dist/assets/scale-Bcgfo6Hp.js +1 -0
  358. package/dist/assets/screen-DLR7p_yP.js +1 -0
  359. package/dist/assets/search-DnjHupRf.js +1 -0
  360. package/dist/assets/seed-b1xc0dj2.js +1 -0
  361. package/dist/assets/shapes-RqjqC0ay.js +1 -0
  362. package/dist/assets/shard000-DM_rqD7W.js +1 -0
  363. package/dist/assets/shard001-gQEQs7_F.js +1 -0
  364. package/dist/assets/shard002-DbDas-W-.js +1 -0
  365. package/dist/assets/shard003-DxMszG4_.js +1 -0
  366. package/dist/assets/shard004-DZMpLBw1.js +1 -0
  367. package/dist/assets/shard005-DLVkh9IH.js +1 -0
  368. package/dist/assets/shard006-B6efKHov.js +1 -0
  369. package/dist/assets/shard007-AFdZLe38.js +1 -0
  370. package/dist/assets/shard008-LbSBzwRx.js +1 -0
  371. package/dist/assets/shard009-D_6-SUDb.js +1 -0
  372. package/dist/assets/shard010-DbOj-uZW.js +1 -0
  373. package/dist/assets/shard011-BYSz7EEW.js +1 -0
  374. package/dist/assets/shard012-B5Ypj7OV.js +1 -0
  375. package/dist/assets/shard013-BtsliNIe.js +1 -0
  376. package/dist/assets/shard014-15sjmk68.js +1 -0
  377. package/dist/assets/shard015-nnXrqru_.js +1 -0
  378. package/dist/assets/shard016-BayXFE13.js +1 -0
  379. package/dist/assets/shard017-Cy4cGexs.js +1 -0
  380. package/dist/assets/shard018-aO-BYJ8H.js +1 -0
  381. package/dist/assets/shard019-gxaTeA3I.js +1 -0
  382. package/dist/assets/shard020-BibJdMI0.js +1 -0
  383. package/dist/assets/shard021-2FE-tze4.js +1 -0
  384. package/dist/assets/shard022-IXiyG-JK.js +1 -0
  385. package/dist/assets/shard023-314F6NJS.js +1 -0
  386. package/dist/assets/shard024-cN5rpQ_X.js +1 -0
  387. package/dist/assets/shard025-KKTaXOGE.js +1 -0
  388. package/dist/assets/shard026-C2Dkd1t6.js +1 -0
  389. package/dist/assets/shard027-Xy-8x9As.js +1 -0
  390. package/dist/assets/shard028-Bul-klUj.js +1 -0
  391. package/dist/assets/shard029-BbRSUcUZ.js +1 -0
  392. package/dist/assets/shard030-GYllsFVC.js +1 -0
  393. package/dist/assets/shard031-DMigeiyn.js +1 -0
  394. package/dist/assets/shard032-CcvmzK0l.js +1 -0
  395. package/dist/assets/shard033-11AU702M.js +1 -0
  396. package/dist/assets/shard034-Bx3urxjM.js +1 -0
  397. package/dist/assets/shard035-DBdEeyCt.js +1 -0
  398. package/dist/assets/shard036-vYgoRBqS.js +1 -0
  399. package/dist/assets/shard037-DP4MMw91.js +1 -0
  400. package/dist/assets/shard038-ffxsv7d6.js +1 -0
  401. package/dist/assets/shard039-C1JHHNp9.js +1 -0
  402. package/dist/assets/shard040-JbVvCSO_.js +1 -0
  403. package/dist/assets/shard041-5L_eFwGx.js +1 -0
  404. package/dist/assets/shard042-C9nsGL79.js +1 -0
  405. package/dist/assets/shard043-Dx_9HSKI.js +1 -0
  406. package/dist/assets/shard044-B3QQNC4A.js +1 -0
  407. package/dist/assets/shard045-B7z0WHO6.js +1 -0
  408. package/dist/assets/shard046-D1yDablJ.js +1 -0
  409. package/dist/assets/shard047-DJi3AgeT.js +1 -0
  410. package/dist/assets/shard048-D_2ipfUr.js +1 -0
  411. package/dist/assets/shard049-1aGgWZdd.js +1 -0
  412. package/dist/assets/shard050-Bjwa0Og4.js +1 -0
  413. package/dist/assets/shard051-Cx_GAk3v.js +1 -0
  414. package/dist/assets/shard052-LaeOD3V5.js +1 -0
  415. package/dist/assets/shard053-D-da7Foj.js +1 -0
  416. package/dist/assets/shard054-CM8eSRQk.js +1 -0
  417. package/dist/assets/shard055-QRpDl4Vs.js +1 -0
  418. package/dist/assets/shard056-DkY7CQm3.js +1 -0
  419. package/dist/assets/shard057-BVCgf7o0.js +1 -0
  420. package/dist/assets/shard058-93aGtdrX.js +1 -0
  421. package/dist/assets/shard059-rJE9X1_I.js +1 -0
  422. package/dist/assets/shard060-Lu_hfl1r.js +1 -0
  423. package/dist/assets/shard061-C9A8iNiq.js +1 -0
  424. package/dist/assets/shard062-BBIpEYZO.js +1 -0
  425. package/dist/assets/shard063-C_uen3Ub.js +1 -0
  426. package/dist/assets/shard064-DAHUsHST.js +1 -0
  427. package/dist/assets/shard065-C2edvdvh.js +1 -0
  428. package/dist/assets/shard066-DFcuVhZm.js +1 -0
  429. package/dist/assets/shard067-BN8DM8JZ.js +1 -0
  430. package/dist/assets/shard068-i464K77p.js +1 -0
  431. package/dist/assets/shard069-Dm9m2a5U.js +1 -0
  432. package/dist/assets/shard070-BPVvR8GH.js +1 -0
  433. package/dist/assets/shard071-Bal_0d9L.js +1 -0
  434. package/dist/assets/shard072-CBQu62kt.js +1 -0
  435. package/dist/assets/shard073-D5BRae0w.js +1 -0
  436. package/dist/assets/shard074-DtFvcBnZ.js +1 -0
  437. package/dist/assets/share-QHGHNDhQ.js +1 -0
  438. package/dist/assets/shellscript-BiGZg9Om.js +1 -0
  439. package/dist/assets/shellscript-CqHdAlCp.js +1 -0
  440. package/dist/assets/shikiHighlight-DUCYbsJ2.js +153 -0
  441. package/dist/assets/sourceHost-BV3t2XDb.js +1 -0
  442. package/dist/assets/speed-DwPw9KSv.js +1 -0
  443. package/dist/assets/spokenText-D4zPX9JY.js +1 -0
  444. package/dist/assets/sql-0Qcw9ht3.js +1 -0
  445. package/dist/assets/sql-CP0rg-5b.js +1 -0
  446. package/dist/assets/src-DbgF7cRH.js +2 -0
  447. package/dist/assets/stats-BeP5Bws0.js +1 -0
  448. package/dist/assets/status-BY02LIJt.js +1 -0
  449. package/dist/assets/stepDriver-DwjFet6U.js +1 -0
  450. package/dist/assets/store-4JNNn732.js +1 -0
  451. package/dist/assets/store-BFC7DZcg.js +1 -0
  452. package/dist/assets/store-BFrJaSxJ.js +1 -0
  453. package/dist/assets/store-DXmC_fP2.js +0 -0
  454. package/dist/assets/store-DgG0C1Qx.js +1 -0
  455. package/dist/assets/streamParse-DRgMHWye.js +2 -0
  456. package/dist/assets/student.generated-DS_acPaJ.js +1 -0
  457. package/dist/assets/swift-DonLKvLd.js +1 -0
  458. package/dist/assets/tables-Dzd3rTqG.js +1 -0
  459. package/dist/assets/time-BHTmJR1s.js +1 -0
  460. package/dist/assets/toast-BBNs2h3L.js +1 -0
  461. package/dist/assets/toml-CcmNWLt0.js +1 -0
  462. package/dist/assets/traveler.generated-Beun9X7y.js +1 -0
  463. package/dist/assets/tsx-5f3ulCja.js +1 -0
  464. package/dist/assets/tsx-BOvsFOI6.js +1 -0
  465. package/dist/assets/typeof-B5XbjTb1.js +1 -0
  466. package/dist/assets/typescript-Utq2Cl8c.js +1 -0
  467. package/dist/assets/typescript-j_1H8WHN.js +1 -0
  468. package/dist/assets/useDashboardLoop-B6ByHWoE.js +5 -0
  469. package/dist/assets/useFocusTrap-DbfG6zbM.js +1 -0
  470. package/dist/assets/useLiveConfig-X9Wxh90k.js +1 -0
  471. package/dist/assets/useSpatialCanvas-DogK9457.js +1 -0
  472. package/dist/assets/useSpotlightWalk-rMbmNBKK.js +1 -0
  473. package/dist/assets/useSrsCards-CagZefwX.js +1 -0
  474. package/dist/assets/useStudy-Bflreq2a.js +1 -0
  475. package/dist/assets/useTimeout-BRYvu2jQ.js +1 -0
  476. package/dist/assets/useValidatedImage-D2fhA198.js +1 -0
  477. package/dist/assets/useZoomGesture-CYc3tf2z.js +1 -0
  478. package/dist/assets/verbatim-CBMzUDlf.js +1 -0
  479. package/dist/assets/worker-BbLKfGzm.js +2 -0
  480. package/dist/assets/xml-WY0zc4PG.js +1 -0
  481. package/dist/assets/yaml-CxbubiML.js +1 -0
  482. package/dist/assets/yaml-DijMHRDA.js +1 -0
  483. package/dist/demo-assets/CREDITS.md +40 -0
  484. package/dist/demo-assets/images/basalt-cliffs.jpg +0 -0
  485. package/dist/demo-assets/images/capelinhos.jpg +0 -0
  486. package/dist/demo-assets/images/cliff-fields.jpg +0 -0
  487. package/dist/demo-assets/images/crater-panorama.jpg +0 -0
  488. package/dist/demo-assets/images/furnas-lake.jpg +0 -0
  489. package/dist/demo-assets/images/green-coast.jpg +0 -0
  490. package/dist/demo-assets/images/overcast-coast.jpg +0 -0
  491. package/dist/demo-assets/images/patchwork-fields.jpg +0 -0
  492. package/dist/demo-assets/images/ponta-delgada.jpg +0 -0
  493. package/dist/demo-assets/images/sete-cidades.jpg +0 -0
  494. package/dist/demo-assets/images/slide-placeholder-light.svg +9 -0
  495. package/dist/demo-assets/images/slide-placeholder.svg +9 -0
  496. package/dist/demo-assets/images/village-street.jpg +0 -0
  497. package/dist/demo-assets/pdf/cfd-primer.pdf +0 -0
  498. package/dist/demo-assets/video/azores-film.mp4 +0 -0
  499. package/dist/favicon.svg +19 -0
  500. package/dist/fonts/LICENSE.txt +34 -0
  501. package/dist/fonts/archivo-var-latin.woff2 +0 -0
  502. package/dist/fonts/bodoni-moda-400-italic-latin.woff2 +0 -0
  503. package/dist/fonts/bodoni-moda-var-latin.woff2 +0 -0
  504. package/dist/fonts/eb-garamond-var-italic-latin.woff2 +0 -0
  505. package/dist/fonts/eb-garamond-var-latin.woff2 +0 -0
  506. package/dist/fonts/export/families/archivo.css +12 -0
  507. package/dist/fonts/export/families/bodoni-moda.css +23 -0
  508. package/dist/fonts/export/families/eb-garamond.css +22 -0
  509. package/dist/fonts/export/families/ibm-plex-sans.css +12 -0
  510. package/dist/fonts/export/families/instrument-serif.css +22 -0
  511. package/dist/fonts/export/families/jetbrains-mono.css +12 -0
  512. package/dist/fonts/export/families/libre-franklin.css +12 -0
  513. package/dist/fonts/export/families/lora.css +22 -0
  514. package/dist/fonts/export/families/source-serif-4.css +12 -0
  515. package/dist/fonts/export/families/space-grotesk.css +12 -0
  516. package/dist/fonts/export/families/spectral.css +52 -0
  517. package/dist/fonts/fonts.css +141 -0
  518. package/dist/fonts/hanken-grotesk-var-latin.woff2 +0 -0
  519. package/dist/fonts/ibm-plex-sans-var-latin.woff2 +0 -0
  520. package/dist/fonts/instrument-serif-400-italic-latin.woff2 +0 -0
  521. package/dist/fonts/instrument-serif-400-latin.woff2 +0 -0
  522. package/dist/fonts/jetbrains-mono-var-latin.woff2 +0 -0
  523. package/dist/fonts/libre-franklin-var-latin.woff2 +0 -0
  524. package/dist/fonts/lora-var-italic-latin.woff2 +0 -0
  525. package/dist/fonts/lora-var-latin.woff2 +0 -0
  526. package/dist/fonts/newsreader-var-italic-latin.woff2 +0 -0
  527. package/dist/fonts/newsreader-var-latin.woff2 +0 -0
  528. package/dist/fonts/plex-mono-400-latin.woff2 +0 -0
  529. package/dist/fonts/plex-mono-500-latin.woff2 +0 -0
  530. package/dist/fonts/plex-mono-600-latin.woff2 +0 -0
  531. package/dist/fonts/source-serif-4-var-latin.woff2 +0 -0
  532. package/dist/fonts/space-grotesk-var-latin.woff2 +0 -0
  533. package/dist/fonts/spectral-400-italic-latin.woff2 +0 -0
  534. package/dist/fonts/spectral-400-latin.woff2 +0 -0
  535. package/dist/fonts/spectral-500-italic-latin.woff2 +0 -0
  536. package/dist/fonts/spectral-500-latin.woff2 +0 -0
  537. package/dist/fonts/spectral-600-latin.woff2 +0 -0
  538. package/dist/index.html +117 -0
  539. package/dist/legal/DISCLAIMER.md +16 -0
  540. package/dist/legal/LICENSE.txt +75 -0
  541. package/dist/legal/PRIVACY.md +127 -0
  542. package/dist/legal/SECURITY.md +72 -0
  543. package/dist/legal/SUPPORT.md +13 -0
  544. package/dist/legal/TERMS.md +122 -0
  545. package/dist/legal/THIRD-PARTY.txt +639 -0
  546. package/dist/legal/TRADEMARKS.md +15 -0
  547. package/dist/ort-wasm-simd-threaded.mjs +59 -0
  548. package/dist/sw.js +30 -0
  549. package/dist/vad.worklet.bundle.min.js +1 -0
  550. package/docker-compose.yml +33 -0
  551. package/gateway/README.md +99 -0
  552. package/gateway/actions-gateway.mjs +537 -0
  553. package/gateway/connectors.d.mts +27 -0
  554. package/gateway/connectors.mjs +463 -0
  555. package/gateway/guard.mjs +110 -0
  556. package/gateway/tokenStore.mjs +134 -0
  557. package/package.json +143 -0
package/bin/mavea.mjs ADDED
@@ -0,0 +1,1079 @@
1
+ #!/usr/bin/env node
2
+ // mavea CLI — serves the prebuilt app locally, same-origin proxies included, so `npx mavea`
3
+ // works exactly the way `pnpm dev` does in the source repo (see vite.config.ts, which this
4
+ // mirrors) without needing Node deps, a clone, or a build step.
5
+ import { createServer } from 'node:http';
6
+ import http from 'node:http';
7
+ import https from 'node:https';
8
+ import { createReadStream, existsSync, statSync, mkdirSync, renameSync, unlinkSync } from 'node:fs';
9
+ import { writeFile } from 'node:fs/promises';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { dirname, join, extname, normalize, resolve, sep } from 'node:path';
12
+ import { spawn, execSync } from 'node:child_process';
13
+ import { constants, createBrotliCompress, createGzip } from 'node:zlib';
14
+ import { homedir, platform, tmpdir } from 'node:os';
15
+ import readline from 'node:readline';
16
+
17
+ const __dirname = dirname(fileURLToPath(import.meta.url));
18
+ const ROOT = join(__dirname, '..');
19
+ const DIST = join(ROOT, 'dist');
20
+ const COMPOSE_FILE = join(ROOT, 'docker-compose.yml');
21
+
22
+ // The CLI is intentionally a local application, not a remotely reachable deployment server.
23
+ // Binding an API-key-forwarding proxy to 0.0.0.0 turns every laptop on a shared network into an
24
+ // unauthenticated relay. A hosted Mavéa deployment must put its own authenticated HTTPS edge in
25
+ // front of the app; this executable always stays on the IPv4 loopback interface.
26
+ export const LOOPBACK_HOST = '127.0.0.1';
27
+
28
+ const MAX_REQUEST_TARGET_BYTES = 8 * 1024;
29
+ const HOP_BY_HOP_HEADERS = new Set([
30
+ 'connection',
31
+ 'keep-alive',
32
+ 'proxy-authenticate',
33
+ 'proxy-authorization',
34
+ 'te',
35
+ 'trailer',
36
+ 'transfer-encoding',
37
+ 'upgrade',
38
+ ]);
39
+
40
+ // HSTS is deliberately absent: this server is plain HTTP on loopback. Hosted HTTPS deployments
41
+ // receive HSTS from public/_headers. The remaining protections are valid on both HTTP and HTTPS.
42
+ export const LOCAL_SECURITY_HEADERS = Object.freeze({
43
+ 'Content-Security-Policy': "frame-ancestors 'none'",
44
+ 'Permissions-Policy': 'microphone=(self), display-capture=(self), camera=(), geolocation=()',
45
+ 'Referrer-Policy': 'strict-origin-when-cross-origin',
46
+ 'X-Content-Type-Options': 'nosniff',
47
+ 'X-Frame-Options': 'DENY',
48
+ });
49
+
50
+ // The ONNX runtime WASM (13MB) and Silero VAD model (2.3MB) are real functional assets voice
51
+ // mode needs, not waste — but bundling them into every `npx mavea` download costs everyone that
52
+ // weight even if they never touch voice (most turns are text). Both are ALSO already public,
53
+ // permanently-versioned npm package assets, so instead of shipping them in dist/ (see the `files`
54
+ // exclusion in package.json), they're fetched ONCE from jsDelivr's npm CDN the first time voice
55
+ // actually starts, cached to a persistent per-OS cache dir, and served from there on every request
56
+ // after — a normal user who never uses voice never downloads either file. Pinned to the exact
57
+ // version this package was built against (see devDependencies) so a version bump here and a
58
+ // version bump there can never silently drift apart.
59
+ const LAZY_ASSETS = {
60
+ 'ort-wasm-simd-threaded.wasm': {
61
+ url: 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.26.0/dist/ort-wasm-simd-threaded.wasm',
62
+ mime: 'application/wasm',
63
+ },
64
+ 'silero_vad_v5.onnx': {
65
+ url: 'https://cdn.jsdelivr.net/npm/@ricky0123/vad-web@0.0.30/dist/silero_vad_v5.onnx',
66
+ mime: 'application/octet-stream',
67
+ },
68
+ };
69
+
70
+ // Standard per-OS cache location (the same convention env-paths/XDG use) — persists across
71
+ // `npx mavea` invocations (npx's own package cache does NOT persist these, since they're fetched
72
+ // at runtime, not installed), so a user only pays the download once, ever, per machine.
73
+ function lazyCacheDir() {
74
+ const plat = platform();
75
+ if (plat === 'darwin') return join(homedir(), 'Library', 'Caches', 'mavea');
76
+ if (plat === 'win32') {
77
+ return join(process.env.LOCALAPPDATA || join(homedir(), 'AppData', 'Local'), 'mavea', 'Cache');
78
+ }
79
+ return join(process.env.XDG_CACHE_HOME || join(homedir(), '.cache'), 'mavea');
80
+ }
81
+
82
+ // In-flight downloads by cache path, so two requests racing for the same missing asset (e.g. the
83
+ // .wasm and its already-loaded .mjs glue both kicking off near-simultaneously) share one fetch
84
+ // instead of downloading twice.
85
+ const inFlightDownloads = new Map();
86
+
87
+ async function fetchToCache(url, cachePath) {
88
+ if (existsSync(cachePath)) return;
89
+ const existing = inFlightDownloads.get(cachePath);
90
+ if (existing) return existing;
91
+ const task = (async () => {
92
+ mkdirSync(dirname(cachePath), { recursive: true });
93
+ const ctrl = new AbortController();
94
+ const timer = setTimeout(() => ctrl.abort(), 30_000);
95
+ // Download to a per-process tmp file, then rename — a request that arrives mid-download (or a
96
+ // process killed mid-download) never sees or leaves behind a truncated cache file.
97
+ const tmpPath = join(tmpdir(), `mavea-dl-${process.pid}-${Date.now()}.tmp`);
98
+ try {
99
+ const res = await fetch(url, { signal: ctrl.signal });
100
+ if (!res.ok) throw new Error(`fetch ${url} -> ${res.status}`);
101
+ await writeFile(tmpPath, Buffer.from(await res.arrayBuffer()));
102
+ renameSync(tmpPath, cachePath);
103
+ } finally {
104
+ clearTimeout(timer);
105
+ if (existsSync(tmpPath)) unlinkSync(tmpPath);
106
+ }
107
+ })();
108
+ inFlightDownloads.set(cachePath, task);
109
+ try {
110
+ await task;
111
+ } finally {
112
+ inFlightDownloads.delete(cachePath);
113
+ }
114
+ }
115
+
116
+ async function serveLazyAsset(name, asset, res) {
117
+ const cachePath = join(lazyCacheDir(), name);
118
+ try {
119
+ await fetchToCache(asset.url, cachePath);
120
+ } catch {
121
+ // Same failure mode as a missing asset always had here: the client (onnxruntime-web / Silero
122
+ // VAD) treats a 404/502 on this path as "voice model unavailable" and falls back gracefully
123
+ // (WebSpeech) rather than breaking the turn — offline or a CDN hiccup degrades, never crashes.
124
+ sendProblem(res, 502, 'The optional voice asset is unavailable.');
125
+ return;
126
+ }
127
+ res.writeHead(200, {
128
+ ...LOCAL_SECURITY_HEADERS,
129
+ 'Content-Type': asset.mime,
130
+ 'Cache-Control': 'no-cache',
131
+ });
132
+ const stream = createReadStream(cachePath);
133
+ stream.on('error', () => res.destroy());
134
+ stream.pipe(res);
135
+ }
136
+
137
+ export function parseArgs(argv, env = process.env) {
138
+ const envPort = env.PORT == null || env.PORT === '' ? 4173 : Number(env.PORT);
139
+ const out = { port: envPort, open: true, voice: true };
140
+ for (let i = 0; i < argv.length; i++) {
141
+ const a = argv[i];
142
+ if (a === '--port' || a === '-p') {
143
+ const value = argv[++i];
144
+ if (value == null) throw new Error(`${a} requires a port number`);
145
+ out.port = Number(value);
146
+ } else if (a === '--no-open') out.open = false;
147
+ else if (a === '--no-voice') out.voice = false;
148
+ else if (a === '--help' || a === '-h') out.help = true;
149
+ else throw new Error(`unknown option: ${a}`);
150
+ }
151
+ if (!Number.isInteger(out.port) || out.port < 0 || out.port > 65_535) {
152
+ throw new Error(`invalid port: ${String(env.PORT ?? out.port)}`);
153
+ }
154
+ return out;
155
+ }
156
+
157
+ // Same-origin forwarders — the production equivalent of the /llm, /search, /actions, /tts, /stt
158
+ // dev proxies in vite.config.ts. BYOK keys travel browser → this local server → provider, same
159
+ // as dev; nothing is stored or logged here.
160
+ const MiB = 1024 * 1024;
161
+
162
+ export const PROXIES = [
163
+ {
164
+ prefix: '/tts',
165
+ target: process.env.KOKORO_URL || 'http://localhost:8880',
166
+ methods: ['GET', 'POST'],
167
+ bodyLimit: 2 * MiB,
168
+ responseLimit: 32 * MiB,
169
+ requestsPerMinute: 120,
170
+ maxConcurrent: 4,
171
+ timeoutMs: 120_000,
172
+ },
173
+ {
174
+ prefix: '/stt',
175
+ target: process.env.WHISPER_URL || 'http://localhost:8100',
176
+ methods: ['GET', 'POST'],
177
+ bodyLimit: 32 * MiB,
178
+ responseLimit: 4 * MiB,
179
+ requestsPerMinute: 60,
180
+ maxConcurrent: 2,
181
+ timeoutMs: 120_000,
182
+ },
183
+ {
184
+ prefix: '/llm/anthropic',
185
+ target: 'https://api.anthropic.com',
186
+ // GET too: every provider adapter's readiness probe (the wizard/settings "Test" check) hits
187
+ // GET .../v1/models before ever sending a POST — restricting this to POST 405'd that probe,
188
+ // which surfaced as a permanent, key-independent "Not reachable." on this local server.
189
+ methods: ['GET', 'POST'],
190
+ bodyLimit: 96 * MiB,
191
+ responseLimit: 32 * MiB,
192
+ requestsPerMinute: 60,
193
+ maxConcurrent: 2,
194
+ timeoutMs: 120_000,
195
+ },
196
+ {
197
+ prefix: '/llm/openai',
198
+ target: 'https://api.openai.com',
199
+ methods: ['GET', 'POST'], // GET: the readiness probe (see the anthropic route's comment)
200
+ bodyLimit: 96 * MiB,
201
+ responseLimit: 32 * MiB,
202
+ requestsPerMinute: 60,
203
+ maxConcurrent: 2,
204
+ timeoutMs: 120_000,
205
+ },
206
+ {
207
+ prefix: '/llm/gemini',
208
+ target: 'https://generativelanguage.googleapis.com',
209
+ methods: ['GET', 'POST'], // GET: the readiness probe (see the anthropic route's comment)
210
+ bodyLimit: 96 * MiB,
211
+ responseLimit: 32 * MiB,
212
+ requestsPerMinute: 60,
213
+ maxConcurrent: 2,
214
+ timeoutMs: 120_000,
215
+ injectGeminiKey: true,
216
+ },
217
+ {
218
+ prefix: '/llm/grok',
219
+ target: 'https://api.x.ai',
220
+ methods: ['GET', 'POST'], // GET: the readiness probe (see the anthropic route's comment)
221
+ bodyLimit: 96 * MiB,
222
+ responseLimit: 32 * MiB,
223
+ requestsPerMinute: 60,
224
+ maxConcurrent: 2,
225
+ timeoutMs: 120_000,
226
+ },
227
+ {
228
+ prefix: '/llm/openrouter',
229
+ target: 'https://openrouter.ai',
230
+ methods: ['GET', 'POST'], // GET: the readiness probe (see the anthropic route's comment)
231
+ bodyLimit: 96 * MiB,
232
+ responseLimit: 32 * MiB,
233
+ requestsPerMinute: 60,
234
+ maxConcurrent: 2,
235
+ timeoutMs: 120_000,
236
+ },
237
+ {
238
+ prefix: '/search/brave',
239
+ target: 'https://api.search.brave.com',
240
+ methods: ['GET'],
241
+ bodyLimit: 0,
242
+ responseLimit: 4 * MiB,
243
+ requestsPerMinute: 120,
244
+ maxConcurrent: 6,
245
+ timeoutMs: 30_000,
246
+ },
247
+ {
248
+ prefix: '/search/tavily',
249
+ target: 'https://api.tavily.com',
250
+ methods: ['POST'],
251
+ bodyLimit: 2 * MiB,
252
+ responseLimit: 4 * MiB,
253
+ requestsPerMinute: 120,
254
+ maxConcurrent: 6,
255
+ timeoutMs: 30_000,
256
+ },
257
+ {
258
+ prefix: '/actions',
259
+ target: process.env.ACTIONS_URL || 'http://127.0.0.1:8910',
260
+ methods: ['GET', 'POST', 'DELETE'],
261
+ bodyLimit: 2 * MiB,
262
+ responseLimit: 8 * MiB,
263
+ requestsPerMinute: 120,
264
+ maxConcurrent: 4,
265
+ timeoutMs: 30_000,
266
+ forwardOrigin: true,
267
+ crossSiteNavigationPaths: ['/oauth/google/callback'],
268
+ injectGatewaySecret: true,
269
+ },
270
+ ];
271
+
272
+ function send(res, status, body = '', headers = {}) {
273
+ if (res.headersSent) return res.end();
274
+ const payload = body ? Buffer.from(body) : null;
275
+ res.writeHead(status, {
276
+ ...LOCAL_SECURITY_HEADERS,
277
+ 'Cache-Control': 'no-store',
278
+ ...(payload ? { 'Content-Length': String(payload.byteLength) } : {}),
279
+ ...headers,
280
+ });
281
+ res.end(payload);
282
+ }
283
+
284
+ function sendProblem(res, status, message, headers = {}) {
285
+ send(res, status, `${message}\n`, {
286
+ 'Content-Type': 'text/plain; charset=utf-8',
287
+ ...headers,
288
+ });
289
+ }
290
+
291
+ function parseRequestTarget(rawUrl) {
292
+ if (!rawUrl || Buffer.byteLength(rawUrl) > MAX_REQUEST_TARGET_BYTES) {
293
+ throw new Error('request target is empty or too long');
294
+ }
295
+ // Only origin-form request targets belong on this local origin. Rejecting absolute-form targets
296
+ // prevents the CLI from accidentally behaving like a general-purpose forward proxy.
297
+ if (!rawUrl.startsWith('/')) throw new Error('absolute request targets are not supported');
298
+ const parsed = new URL(rawUrl, 'http://127.0.0.1');
299
+ const decodedPathname = decodeURIComponent(parsed.pathname);
300
+ if (decodedPathname.includes('\0')) throw new Error('request target contains a null byte');
301
+ return {
302
+ pathname: parsed.pathname,
303
+ decodedPathname,
304
+ search: parsed.search,
305
+ };
306
+ }
307
+
308
+ function routeMatches(pathname, prefix) {
309
+ return pathname === prefix || pathname.startsWith(`${prefix}/`);
310
+ }
311
+
312
+ function localUrlFromHeader(value) {
313
+ if (!value || Array.isArray(value)) return null;
314
+ try {
315
+ const parsed = new URL(value.includes('://') ? value : `http://${value}`);
316
+ const hostname = parsed.hostname.toLowerCase();
317
+ if (!['127.0.0.1', 'localhost', '[::1]', '::1'].includes(hostname)) return null;
318
+ return parsed;
319
+ } catch {
320
+ return null;
321
+ }
322
+ }
323
+
324
+ function isTrustedBrowserRequest(req, requestTarget, route) {
325
+ const host = localUrlFromHeader(req.headers.host);
326
+ if (!host) return false;
327
+
328
+ const crossSitePath = requestTarget.pathname.slice(route.prefix.length) || '/';
329
+ if (
330
+ req.method === 'GET' &&
331
+ route.crossSiteNavigationPaths?.some((path) => crossSitePath === path)
332
+ ) {
333
+ return true;
334
+ }
335
+
336
+ const origin = localUrlFromHeader(req.headers.origin);
337
+ if (origin) return origin.host === host.host;
338
+
339
+ const referer = localUrlFromHeader(req.headers.referer);
340
+ if (referer) return referer.host === host.host;
341
+
342
+ // Fetch Metadata is supplied by current browsers even for same-origin GETs that omit Origin.
343
+ // A raw socket/curl request supplies none of these browser proofs and cannot consume the proxy.
344
+ return req.headers['sec-fetch-site'] === 'same-origin';
345
+ }
346
+
347
+ function createRateLimiter(now = Date.now) {
348
+ const windows = new Map();
349
+ let calls = 0;
350
+ return {
351
+ take(key, limit) {
352
+ const current = now();
353
+ const cutoff = current - 60_000;
354
+ const recent = (windows.get(key) || []).filter((time) => time > cutoff);
355
+ if (recent.length >= limit) {
356
+ return {
357
+ allowed: false,
358
+ retryAfter: Math.max(1, Math.ceil((recent[0] + 60_000 - current) / 1000)),
359
+ };
360
+ }
361
+ recent.push(current);
362
+ windows.set(key, recent);
363
+ // Keep a long-running CLI bounded even if many transient client keys appear.
364
+ if (++calls % 256 === 0) {
365
+ for (const [client, hits] of windows) {
366
+ if (!hits.some((time) => time > cutoff)) windows.delete(client);
367
+ }
368
+ }
369
+ return { allowed: true, retryAfter: 0 };
370
+ },
371
+ };
372
+ }
373
+
374
+ function readRequestBody(req, limit) {
375
+ const declared = Number(req.headers['content-length']);
376
+ if (Number.isFinite(declared) && declared > limit) {
377
+ req.resume();
378
+ return Promise.reject(Object.assign(new Error('request body too large'), { statusCode: 413 }));
379
+ }
380
+ return new Promise((resolveBody, rejectBody) => {
381
+ const chunks = [];
382
+ let received = 0;
383
+ let settled = false;
384
+ const reject = (error) => {
385
+ if (settled) return;
386
+ settled = true;
387
+ rejectBody(error);
388
+ };
389
+ req.on('data', (chunk) => {
390
+ received += chunk.length;
391
+ if (received > limit) {
392
+ reject(Object.assign(new Error('request body too large'), { statusCode: 413 }));
393
+ req.resume();
394
+ return;
395
+ }
396
+ chunks.push(chunk);
397
+ });
398
+ req.on('end', () => {
399
+ if (settled) return;
400
+ settled = true;
401
+ resolveBody(Buffer.concat(chunks, received));
402
+ });
403
+ req.on('aborted', () =>
404
+ reject(Object.assign(new Error('request aborted'), { statusCode: 400 })),
405
+ );
406
+ req.on('error', reject);
407
+ });
408
+ }
409
+
410
+ function requestHeadersForUpstream(req, route, targetUrl, body) {
411
+ const headers = {};
412
+ for (const [name, value] of Object.entries(req.headers)) {
413
+ const lower = name.toLowerCase();
414
+ if (
415
+ HOP_BY_HOP_HEADERS.has(lower) ||
416
+ lower === 'host' ||
417
+ lower === 'cookie' ||
418
+ lower === 'content-length' ||
419
+ (!route.forwardOrigin && (lower === 'origin' || lower === 'referer'))
420
+ ) {
421
+ continue;
422
+ }
423
+ if (value != null) headers[lower] = value;
424
+ }
425
+ headers.host = targetUrl.host;
426
+ if (body.byteLength > 0) headers['content-length'] = String(body.byteLength);
427
+ if (route.injectGeminiKey && process.env.GEMINI_API_KEY && !headers['x-goog-api-key']) {
428
+ headers['x-goog-api-key'] = process.env.GEMINI_API_KEY;
429
+ }
430
+ if (route.injectGatewaySecret && process.env.GATEWAY_SECRET) {
431
+ headers['x-gateway-secret'] = process.env.GATEWAY_SECRET;
432
+ }
433
+ return headers;
434
+ }
435
+
436
+ function responseHeadersForClient(proxyHeaders) {
437
+ const headers = { ...LOCAL_SECURITY_HEADERS, 'Cache-Control': 'no-store' };
438
+ for (const [name, value] of Object.entries(proxyHeaders)) {
439
+ const lower = name.toLowerCase();
440
+ if (
441
+ HOP_BY_HOP_HEADERS.has(lower) ||
442
+ lower === 'set-cookie' ||
443
+ lower.startsWith('access-control-') ||
444
+ lower === 'content-length'
445
+ ) {
446
+ continue;
447
+ }
448
+ if (value != null) headers[lower] = value;
449
+ }
450
+ return headers;
451
+ }
452
+
453
+ function streamBoundedResponse(proxyRes, res, limit, onDone) {
454
+ const declared = Number(proxyRes.headers['content-length']);
455
+ if (Number.isFinite(declared) && declared > limit) {
456
+ proxyRes.destroy();
457
+ sendProblem(res, 502, 'Upstream response exceeded the configured limit.');
458
+ onDone();
459
+ return;
460
+ }
461
+
462
+ res.writeHead(proxyRes.statusCode || 502, responseHeadersForClient(proxyRes.headers));
463
+ let received = 0;
464
+ proxyRes.on('data', (chunk) => {
465
+ received += chunk.length;
466
+ if (received > limit) {
467
+ proxyRes.destroy();
468
+ res.destroy(new Error('upstream response exceeded the configured limit'));
469
+ onDone();
470
+ return;
471
+ }
472
+ if (!res.write(chunk)) {
473
+ proxyRes.pause();
474
+ res.once('drain', () => proxyRes.resume());
475
+ }
476
+ });
477
+ proxyRes.on('end', () => {
478
+ res.end();
479
+ onDone();
480
+ });
481
+ proxyRes.on('aborted', () => {
482
+ res.destroy();
483
+ onDone();
484
+ });
485
+ proxyRes.on('error', () => {
486
+ if (!res.headersSent) sendProblem(res, 502, 'The upstream service failed.');
487
+ else res.destroy();
488
+ onDone();
489
+ });
490
+ }
491
+
492
+ async function proxyRequest(req, res, route, requestTarget) {
493
+ let body;
494
+ try {
495
+ body = await readRequestBody(req, route.bodyLimit);
496
+ } catch (error) {
497
+ sendProblem(res, error.statusCode || 400, error.message || 'Invalid request body.');
498
+ return;
499
+ }
500
+
501
+ const targetUrl = new URL(route.target);
502
+ if (targetUrl.protocol !== 'http:' && targetUrl.protocol !== 'https:') {
503
+ sendProblem(res, 502, 'The upstream service has an invalid protocol.');
504
+ return;
505
+ }
506
+ const restPath = requestTarget.pathname.slice(route.prefix.length) || '/';
507
+ const rest = restPath + requestTarget.search;
508
+ const isHttps = targetUrl.protocol === 'https:';
509
+ const mod = isHttps ? https : http;
510
+ const headers = requestHeadersForUpstream(req, route, targetUrl, body);
511
+
512
+ await new Promise((resolveProxy) => {
513
+ let settled = false;
514
+ const settle = () => {
515
+ if (settled) return;
516
+ settled = true;
517
+ res.off('close', onClientClose);
518
+ resolveProxy();
519
+ };
520
+ const proxyReq = mod.request(
521
+ {
522
+ protocol: targetUrl.protocol,
523
+ hostname: targetUrl.hostname,
524
+ port: targetUrl.port || (isHttps ? 443 : 80),
525
+ path: (targetUrl.pathname === '/' ? '' : targetUrl.pathname) + rest,
526
+ method: req.method,
527
+ headers,
528
+ },
529
+ (proxyRes) => {
530
+ proxyRes.setTimeout(route.timeoutMs, () => {
531
+ proxyRes.destroy(new Error('upstream response timeout'));
532
+ });
533
+ streamBoundedResponse(proxyRes, res, route.responseLimit, settle);
534
+ },
535
+ );
536
+ const onClientClose = () => {
537
+ if (!res.writableEnded) proxyReq.destroy(new Error('client disconnected'));
538
+ settle();
539
+ };
540
+ res.once('close', onClientClose);
541
+ proxyReq.setTimeout(route.timeoutMs, () => {
542
+ proxyReq.destroy(new Error('upstream request timeout'));
543
+ });
544
+ proxyReq.on('error', () => {
545
+ // Connection refused (voice/gateway not running) surfaces as an honest failure on the card.
546
+ if (!res.destroyed && !res.headersSent) {
547
+ sendProblem(res, 502, 'The upstream service is unavailable.');
548
+ } else if (!res.destroyed) {
549
+ res.destroy();
550
+ }
551
+ settle();
552
+ });
553
+ if (body.byteLength > 0) proxyReq.write(body);
554
+ proxyReq.end();
555
+ });
556
+ }
557
+
558
+ const MIME = {
559
+ '.html': 'text/html',
560
+ '.js': 'text/javascript',
561
+ '.mjs': 'text/javascript',
562
+ '.css': 'text/css',
563
+ '.json': 'application/json',
564
+ '.svg': 'image/svg+xml',
565
+ '.png': 'image/png',
566
+ '.jpg': 'image/jpeg',
567
+ '.jpeg': 'image/jpeg',
568
+ '.webp': 'image/webp',
569
+ '.pdf': 'application/pdf',
570
+ '.woff2': 'font/woff2',
571
+ '.wasm': 'application/wasm',
572
+ '.onnx': 'application/octet-stream',
573
+ '.ico': 'image/x-icon',
574
+ '.webmanifest': 'application/manifest+json',
575
+ };
576
+
577
+ // Text assets are stored uncompressed, so serving them verbatim spends roughly three bytes for
578
+ // every one a browser needs: the app shell alone is 396 kB raw against 129 kB gzipped. Anything
579
+ // already carrying its own compression (fonts, images, PDFs, media) is left alone — re-compressing
580
+ // it costs CPU and returns nothing. `.wasm` is here because it is plain bytecode and halves.
581
+ const COMPRESSIBLE = new Set([
582
+ '.css',
583
+ '.html',
584
+ '.js',
585
+ '.json',
586
+ '.map',
587
+ '.mjs',
588
+ '.svg',
589
+ '.txt',
590
+ '.wasm',
591
+ '.xml',
592
+ ]);
593
+
594
+ // Quality 5 rather than brotli's default 11: on a local server the file is compressed on every
595
+ // request, and 11 spends seconds of CPU to save a few percent over 5 — a trade that makes the
596
+ // first paint slower, not faster. Encoding the size hint lets brotli size its window to the file.
597
+ const BROTLI_OPTIONS = {
598
+ params: {
599
+ [constants.BROTLI_PARAM_QUALITY]: 5,
600
+ [constants.BROTLI_PARAM_MODE]: constants.BROTLI_MODE_TEXT,
601
+ },
602
+ };
603
+
604
+ /** The best encoding this client accepts for this file, or null to send it verbatim. Brotli beats
605
+ * gzip by ~15% on our bundles and every browser that can run the app supports it, so it wins when
606
+ * offered; gzip is the floor for anything older or a proxy that stripped br. */
607
+ export function negotiateEncoding(acceptEncoding, ext) {
608
+ if (!COMPRESSIBLE.has(ext.toLowerCase())) return null;
609
+ const accepted = String(acceptEncoding ?? '').toLowerCase();
610
+ if (/\bbr\b/.test(accepted)) return 'br';
611
+ if (/\bgzip\b/.test(accepted)) return 'gzip';
612
+ return null;
613
+ }
614
+
615
+ // Content-hashed assets are immutable by definition — the same URL can never return different
616
+ // bytes — so compressing one twice is pure waste, and on the small machines this exists to serve,
617
+ // brotli-ing a 400 kB bundle on every page load is CPU taken from the render. Compress once, hold
618
+ // the result, and every later request is a buffer write with a real Content-Length instead of a
619
+ // chunked stream. Only /assets/ is eligible: index.html is `no-cache` and may change under us.
620
+ const COMPRESSED_CACHE_BUDGET_BYTES = 32 * 1024 * 1024;
621
+ const compressedCache = new Map();
622
+ let compressedCacheBytes = 0;
623
+
624
+ function cacheCompressed(key, body) {
625
+ // A budget rather than an LRU: the set is the build's own asset list, which is bounded and small.
626
+ // The cap exists so a pathological dist/ cannot pin unbounded memory, not to manage churn.
627
+ if (compressedCacheBytes + body.length > COMPRESSED_CACHE_BUDGET_BYTES) return;
628
+ compressedCache.set(key, body);
629
+ compressedCacheBytes += body.length;
630
+ }
631
+
632
+ /** Compressed bytes for an immutable asset, or null if it is not worth holding one. */
633
+ function cachedCompression(filePath, encoding, isHashedAsset) {
634
+ if (!isHashedAsset) return null;
635
+ return compressedCache.get(`${encoding}${filePath}`) ?? null;
636
+ }
637
+
638
+ export function resetCompressionCacheForTest() {
639
+ compressedCache.clear();
640
+ compressedCacheBytes = 0;
641
+ }
642
+
643
+ function serveStatic(req, res, requestTarget, distDir = DIST) {
644
+ if (req.method !== 'GET' && req.method !== 'HEAD') {
645
+ sendProblem(res, 405, 'Method not allowed.', { Allow: 'GET, HEAD' });
646
+ return;
647
+ }
648
+
649
+ const urlPath = requestTarget.decodedPathname;
650
+ const safePath = normalize(urlPath === '/' ? '/index.html' : urlPath);
651
+ let filePath = resolve(distDir, `.${safePath}`);
652
+ // Hash-routed app (#/live, #/gallery never hit the server) — no traversal outside dist/.
653
+ const distPrefix = distDir.endsWith(sep) ? distDir : `${distDir}${sep}`;
654
+ const inDist =
655
+ filePath.startsWith(distPrefix) && existsSync(filePath) && !statSync(filePath).isDirectory();
656
+ if (!inDist) {
657
+ // A published package omits these two large assets from dist/ (see package.json `files`) —
658
+ // fetch-and-cache them on first request instead of falling through to the SPA's index.html,
659
+ // which would hand onnxruntime-web an HTML page where it expects a .wasm/.onnx binary.
660
+ // A source checkout's own `pnpm build` still writes them straight into dist/, so this path
661
+ // never triggers there — `inDist` above is already true and this whole branch is skipped.
662
+ const asset = LAZY_ASSETS[safePath.replace(/^\//, '')];
663
+ if (asset) return void serveLazyAsset(safePath.replace(/^\//, ''), asset, res);
664
+ filePath = join(distDir, 'index.html');
665
+ }
666
+ if (!existsSync(filePath)) {
667
+ sendProblem(res, 404, 'Build entry point not found.');
668
+ return;
669
+ }
670
+ const isHtml = extname(filePath) === '.html';
671
+ const isPdf = extname(filePath).toLowerCase() === '.pdf';
672
+ const isHashedAsset = safePath.startsWith('/assets/');
673
+ const encoding = negotiateEncoding(req.headers['accept-encoding'], extname(filePath));
674
+ const cached = encoding ? cachedCompression(filePath, encoding, isHashedAsset) : null;
675
+ res.writeHead(200, {
676
+ ...LOCAL_SECURITY_HEADERS,
677
+ ...(isPdf
678
+ ? {
679
+ // The app shell remains DENY/'none'. Only a PDF response may be framed, and then only
680
+ // by this same origin for Pdfreader's sandboxed iframe.
681
+ 'Content-Security-Policy': "frame-ancestors 'self'",
682
+ 'X-Frame-Options': 'SAMEORIGIN',
683
+ 'Cross-Origin-Resource-Policy': 'same-origin',
684
+ 'Content-Disposition': 'inline',
685
+ }
686
+ : {}),
687
+ 'Cache-Control': isHtml
688
+ ? 'no-cache'
689
+ : isHashedAsset
690
+ ? 'public, max-age=31536000, immutable'
691
+ : 'no-cache',
692
+ 'Content-Type': MIME[extname(filePath)] || 'application/octet-stream',
693
+ ...(encoding ? { 'Content-Encoding': encoding, Vary: 'Accept-Encoding' } : {}),
694
+ // Known only for a cached body: a streamed compression cannot state its length up front, and
695
+ // guessing it from the uncompressed file would be a lie the client counts bytes against.
696
+ ...(cached ? { 'Content-Length': String(cached.length) } : {}),
697
+ });
698
+ if (req.method === 'HEAD') return res.end();
699
+ if (cached) return void res.end(cached);
700
+
701
+ const stream = createReadStream(filePath);
702
+ stream.on('error', () => {
703
+ if (!res.headersSent) sendProblem(res, 500, 'Could not read the requested file.');
704
+ else res.destroy();
705
+ });
706
+ if (!encoding) return void stream.pipe(res);
707
+
708
+ const compressor = encoding === 'br' ? createBrotliCompress(BROTLI_OPTIONS) : createGzip();
709
+ compressor.on('error', () => res.destroy());
710
+ stream.pipe(compressor).pipe(res);
711
+ if (!isHashedAsset) return;
712
+
713
+ // Keep what the client is already being sent, so the next request for this immutable URL skips
714
+ // the compression entirely. Collecting alongside the pipe adds no latency — the response streams
715
+ // as it is produced either way — and 'end' lands once the last chunk has gone out.
716
+ const chunks = [];
717
+ compressor.on('data', (chunk) => chunks.push(chunk));
718
+ compressor.once('end', () => cacheCompressed(`${encoding} ${filePath}`, Buffer.concat(chunks)));
719
+ }
720
+
721
+ export function createMaveaServer({ distDir = DIST, proxies = PROXIES, now = Date.now } = {}) {
722
+ const limiter = createRateLimiter(now);
723
+ const activeByRoute = new Map();
724
+ const server = createServer((req, res) => {
725
+ let requestTarget;
726
+ try {
727
+ requestTarget = parseRequestTarget(req.url);
728
+ } catch {
729
+ sendProblem(res, 400, 'Malformed request target.');
730
+ return;
731
+ }
732
+
733
+ const route = proxies.find((candidate) =>
734
+ routeMatches(requestTarget.pathname, candidate.prefix),
735
+ );
736
+ if (!route) {
737
+ try {
738
+ serveStatic(req, res, requestTarget, distDir);
739
+ } catch {
740
+ sendProblem(res, 500, 'The local server could not complete the request.');
741
+ }
742
+ return;
743
+ }
744
+
745
+ const allowedMethods = route.methods || [];
746
+ if (!allowedMethods.includes(req.method)) {
747
+ sendProblem(res, 405, 'Method not allowed.', { Allow: allowedMethods.join(', ') });
748
+ return;
749
+ }
750
+ if (!isTrustedBrowserRequest(req, requestTarget, route)) {
751
+ sendProblem(res, 403, 'Proxy requests must come from this local Mavéa page.');
752
+ return;
753
+ }
754
+
755
+ const client = req.socket.remoteAddress || 'unknown';
756
+ const rate = limiter.take(`${route.prefix}:${client}`, route.requestsPerMinute);
757
+ if (!rate.allowed) {
758
+ sendProblem(res, 429, 'Too many proxy requests.', {
759
+ 'Retry-After': String(rate.retryAfter),
760
+ });
761
+ return;
762
+ }
763
+
764
+ const active = activeByRoute.get(route.prefix) || 0;
765
+ if (active >= (route.maxConcurrent || 4)) {
766
+ sendProblem(res, 503, 'This local service is busy. Try again shortly.', {
767
+ 'Retry-After': '1',
768
+ });
769
+ return;
770
+ }
771
+ activeByRoute.set(route.prefix, active + 1);
772
+
773
+ void proxyRequest(req, res, route, requestTarget)
774
+ .catch(() => {
775
+ if (!res.headersSent) sendProblem(res, 502, 'The upstream service failed.');
776
+ else res.destroy();
777
+ })
778
+ .finally(() => {
779
+ const remaining = (activeByRoute.get(route.prefix) || 1) - 1;
780
+ if (remaining <= 0) activeByRoute.delete(route.prefix);
781
+ else activeByRoute.set(route.prefix, remaining);
782
+ });
783
+ });
784
+ server.headersTimeout = 10_000;
785
+ server.requestTimeout = 45_000;
786
+ server.keepAliveTimeout = 5_000;
787
+ server.maxHeadersCount = 100;
788
+ server.maxRequestsPerSocket = 1_000;
789
+ return server;
790
+ }
791
+
792
+ export function listenOnLoopback(server, port) {
793
+ return new Promise((resolveListen, rejectListen) => {
794
+ const onError = (error) => {
795
+ server.off('listening', onListening);
796
+ rejectListen(error);
797
+ };
798
+ const onListening = () => {
799
+ server.off('error', onError);
800
+ resolveListen(server.address());
801
+ };
802
+ server.once('error', onError);
803
+ server.once('listening', onListening);
804
+ server.listen(port, LOOPBACK_HOST);
805
+ });
806
+ }
807
+
808
+ function openBrowser(url) {
809
+ const platform = process.platform;
810
+ try {
811
+ if (platform === 'darwin') spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
812
+ else if (platform === 'win32')
813
+ spawn('cmd', ['/c', 'start', '""', url], { stdio: 'ignore', detached: true }).unref();
814
+ else spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
815
+ } catch {
816
+ // best-effort only
817
+ }
818
+ }
819
+
820
+ function dockerInstalled() {
821
+ try {
822
+ execSync('docker --version', { stdio: 'ignore' });
823
+ return true;
824
+ } catch {
825
+ return false;
826
+ }
827
+ }
828
+
829
+ function dockerRunning() {
830
+ try {
831
+ execSync('docker info', { stdio: 'ignore' });
832
+ return true;
833
+ } catch {
834
+ return false;
835
+ }
836
+ }
837
+
838
+ function commandExists(cmd) {
839
+ try {
840
+ execSync(process.platform === 'win32' ? `where ${cmd}` : `command -v ${cmd}`, {
841
+ stdio: 'ignore',
842
+ shell: true,
843
+ });
844
+ return true;
845
+ } catch {
846
+ return false;
847
+ }
848
+ }
849
+
850
+ function waitForDocker(timeoutMs = 90_000) {
851
+ return new Promise((resolve) => {
852
+ const started = Date.now();
853
+ const tick = () => {
854
+ if (dockerRunning()) return resolve(true);
855
+ if (Date.now() - started > timeoutMs) return resolve(false);
856
+ setTimeout(tick, 2000);
857
+ };
858
+ tick();
859
+ });
860
+ }
861
+
862
+ async function kokoroReachable(url) {
863
+ try {
864
+ const ctrl = new AbortController();
865
+ const timer = setTimeout(() => ctrl.abort(), 800);
866
+ await fetch(url, { signal: ctrl.signal });
867
+ clearTimeout(timer);
868
+ return true;
869
+ } catch {
870
+ return false;
871
+ }
872
+ }
873
+
874
+ function askYesNo(question) {
875
+ return new Promise((resolve) => {
876
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
877
+ rl.question(question, (answer) => {
878
+ rl.close();
879
+ resolve(/^y/i.test(answer.trim()) || answer.trim() === '');
880
+ });
881
+ });
882
+ }
883
+
884
+ function startKokoro() {
885
+ console.log(' Starting Kokoro TTS (first run downloads the voice model, ~1 min)…');
886
+ spawn('docker', ['compose', '-f', COMPOSE_FILE, 'up', '-d'], { stdio: 'inherit' });
887
+ }
888
+
889
+ const VOICE_INTRO =
890
+ '\n◌ Voice — Mavéa speaks its replies aloud using a local TTS model (Kokoro).\n' +
891
+ ' It runs in a small Docker container, fully on your machine — no audio ever\n' +
892
+ ' leaves your computer.\n';
893
+
894
+ async function offerDockerInstall() {
895
+ console.log(VOICE_INTRO);
896
+ console.log(" Docker isn't installed, so for now Mavéa is text-only:");
897
+ console.log(' ✗ no spoken replies — lines appear as captions instead');
898
+ console.log(' ✓ everything else works: demo, live mode, gallery, actions\n');
899
+
900
+ const later =
901
+ ' To enable voice later: install Docker (https://docker.com/get-started),\n' +
902
+ ' then re-run `npx mavea` — it will detect Docker and set voice up for you.';
903
+
904
+ if (process.platform === 'darwin' && commandExists('brew')) {
905
+ const yes = await askYesNo(
906
+ ' Install Docker now via Homebrew? [Y/n] (~600 MB, then launch Docker\n' +
907
+ ' Desktop once and re-run `npx mavea` — voice will offer to start itself) ',
908
+ );
909
+ if (!yes) return console.log(' Skipping.\n' + later);
910
+ const r = spawn('brew', ['install', '--cask', 'docker'], { stdio: 'inherit' });
911
+ r.on('exit', (code) => {
912
+ console.log(
913
+ code === 0
914
+ ? ' ✓ Docker installed. Launch Docker Desktop once (to accept its service\n' +
915
+ ' agreement), then re-run `npx mavea` to enable voice.'
916
+ : ' Homebrew install failed.\n' + later,
917
+ );
918
+ });
919
+ return;
920
+ }
921
+
922
+ if (process.platform === 'win32' && commandExists('winget')) {
923
+ const yes = await askYesNo(
924
+ ' Install Docker Desktop now via winget? [Y/n] (then launch it once and\n' +
925
+ ' re-run `npx mavea` — voice will offer to start itself) ',
926
+ );
927
+ if (!yes) return console.log(' Skipping.\n' + later);
928
+ const r = spawn('winget', ['install', '-e', '--id', 'Docker.DockerDesktop'], {
929
+ stdio: 'inherit',
930
+ });
931
+ r.on('exit', (code) => {
932
+ console.log(
933
+ code === 0
934
+ ? ' ✓ Docker installed. Launch Docker Desktop once, then re-run `npx mavea`.'
935
+ : ' winget install failed.\n' + later,
936
+ );
937
+ });
938
+ return;
939
+ }
940
+
941
+ if (process.platform === 'linux') {
942
+ // Docker's install script needs sudo — print it, never run it on the user's behalf.
943
+ console.log(' To install Docker, run:\n');
944
+ console.log(' curl -fsSL https://get.docker.com | sh\n');
945
+ console.log(' then re-run `npx mavea` — it will detect Docker and set voice up for you.');
946
+ return;
947
+ }
948
+
949
+ const yes = await askYesNo(' Open the Docker download page in your browser? [Y/n] ');
950
+ if (yes) openBrowser('https://www.docker.com/get-started/');
951
+ console.log(later);
952
+ }
953
+
954
+ async function offerDockerStart() {
955
+ console.log(VOICE_INTRO);
956
+ console.log(' Docker is installed but not running.\n');
957
+ if (process.platform === 'darwin' || process.platform === 'win32') {
958
+ const yes = await askYesNo(' Start Docker Desktop now? [Y/n] ');
959
+ if (!yes) {
960
+ console.log(' Skipping — captions only. Start Docker Desktop and re-run `npx mavea`.');
961
+ return;
962
+ }
963
+ if (process.platform === 'darwin') {
964
+ spawn('open', ['-a', 'Docker'], { stdio: 'ignore', detached: true }).unref();
965
+ } else {
966
+ spawn('cmd', ['/c', 'start', '""', 'docker desktop'], {
967
+ stdio: 'ignore',
968
+ detached: true,
969
+ }).unref();
970
+ }
971
+ process.stdout.write(' … waiting for Docker to start ');
972
+ const up = await waitForDocker();
973
+ if (!up) {
974
+ console.log('\n Docker did not come up in time — captions only for now.');
975
+ console.log(' Once Docker Desktop is running, re-run `npx mavea` to enable voice.');
976
+ return;
977
+ }
978
+ console.log('✓');
979
+ startKokoro();
980
+ return;
981
+ }
982
+ console.log(' Start it (e.g. `sudo systemctl start docker`), then re-run `npx mavea`.');
983
+ }
984
+
985
+ async function maybeOfferVoice() {
986
+ const kokoroUrl = process.env.KOKORO_URL || 'http://localhost:8880';
987
+ if (await kokoroReachable(kokoroUrl)) return;
988
+ if (!existsSync(COMPOSE_FILE) || !process.stdin.isTTY) {
989
+ console.log(
990
+ "Voice (Kokoro TTS) isn't running — lines will show as captions only. " +
991
+ 'Re-run `npx mavea` in a terminal to set it up, or start Kokoro yourself on :8880.',
992
+ );
993
+ return;
994
+ }
995
+ if (!dockerInstalled()) return offerDockerInstall();
996
+ if (!dockerRunning()) return offerDockerStart();
997
+ console.log(VOICE_INTRO);
998
+ const yes = await askYesNo(' Start it now? [Y/n] ');
999
+ if (!yes) {
1000
+ console.log(' Skipping — captions only. Re-run `npx mavea` any time to enable voice.');
1001
+ return;
1002
+ }
1003
+ startKokoro();
1004
+ }
1005
+
1006
+ async function main() {
1007
+ let args;
1008
+ try {
1009
+ args = parseArgs(process.argv.slice(2));
1010
+ } catch (error) {
1011
+ console.error(`mavea: ${error.message}`);
1012
+ process.exitCode = 1;
1013
+ return;
1014
+ }
1015
+
1016
+ if (args.help) {
1017
+ console.log(`mavea — run the Mavéa app locally
1018
+
1019
+ Usage: npx mavea [options]
1020
+
1021
+ Options:
1022
+ --port, -p <n> Port to serve on (default 4173, or $PORT)
1023
+ --no-open Don't open a browser automatically
1024
+ --no-voice Skip the Kokoro (local TTS) Docker prompt
1025
+ --help, -h Show this help
1026
+
1027
+ The CLI listens only on 127.0.0.1. Live mode needs your own model API key (BYOK) — add it in
1028
+ Settings inside the app. Requests pass through this local proxy to your selected providers.
1029
+ AI output may be inaccurate and is not professional advice. Provider terms and charges apply.
1030
+
1031
+ License: PolyForm Noncommercial 1.0.0 (noncommercial use only)
1032
+ Terms, privacy, disclaimer, and third-party notices ship with this package.
1033
+ `);
1034
+ return;
1035
+ }
1036
+
1037
+ if (!existsSync(DIST)) {
1038
+ console.error(
1039
+ 'mavea: no build found in this package (missing dist/). If you are running from source, ' +
1040
+ 'run `pnpm build` first; a published package should always ship a prebuilt dist/.',
1041
+ );
1042
+ process.exitCode = 1;
1043
+ return;
1044
+ }
1045
+
1046
+ const server = createMaveaServer();
1047
+ let address;
1048
+ try {
1049
+ address = await listenOnLoopback(server, args.port);
1050
+ } catch (error) {
1051
+ console.error(`mavea: could not listen on ${LOOPBACK_HOST}:${args.port} (${error.message})`);
1052
+ process.exitCode = 1;
1053
+ return;
1054
+ }
1055
+ const actualPort = typeof address === 'object' && address ? address.port : args.port;
1056
+ const base = `http://${LOOPBACK_HOST}:${actualPort}`;
1057
+ console.log(`\nMavéa is running at ${base}`);
1058
+ console.log(` Demo: ${base}/`);
1059
+ console.log(` Live: ${base}/#/live`);
1060
+ console.log(` Gallery: ${base}/#/gallery`);
1061
+ console.log('\nLive mode needs your own model API key (BYOK) — add it in Settings in the app.');
1062
+ console.log('Requests pass through this local proxy. Provider terms and charges apply.');
1063
+ console.log(
1064
+ 'AI output may be inaccurate and is not professional advice; verify important information.',
1065
+ );
1066
+ console.log(`Terms: ${base}/legal/TERMS.md`);
1067
+ console.log(`Privacy: ${base}/legal/PRIVACY.md`);
1068
+ console.log(`License: ${base}/legal/LICENSE.txt (PolyForm Noncommercial 1.0.0)`);
1069
+ if (args.open) openBrowser(base);
1070
+ if (args.voice) await maybeOfferVoice();
1071
+ }
1072
+
1073
+ const isMain = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
1074
+ if (isMain) {
1075
+ void main().catch((error) => {
1076
+ console.error(`mavea: ${error instanceof Error ? error.message : String(error)}`);
1077
+ process.exitCode = 1;
1078
+ });
1079
+ }