@cuongtran001/kanna 0.39.2

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 (473) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +355 -0
  3. package/bin/kanna +9 -0
  4. package/dist/client/apple-touch-icon.png +0 -0
  5. package/dist/client/assets/abap-BdImnpbu.js +1 -0
  6. package/dist/client/assets/actionscript-3-CoDkCxhg.js +1 -0
  7. package/dist/client/assets/ada-bCR0ucgS.js +1 -0
  8. package/dist/client/assets/andromeeda-C4gqWexZ.js +1 -0
  9. package/dist/client/assets/angular-html-CU67Zn6k.js +1 -0
  10. package/dist/client/assets/angular-ts-BwZT4LLn.js +1 -0
  11. package/dist/client/assets/apache-Pmp26Uib.js +1 -0
  12. package/dist/client/assets/apex-D8_7TLub.js +1 -0
  13. package/dist/client/assets/apl-dKokRX4l.js +1 -0
  14. package/dist/client/assets/applescript-Co6uUVPk.js +1 -0
  15. package/dist/client/assets/ara-BRHolxvo.js +1 -0
  16. package/dist/client/assets/asciidoc-Ve4PFQV2.js +1 -0
  17. package/dist/client/assets/asm-D_Q5rh1f.js +1 -0
  18. package/dist/client/assets/astro-CbQHKStN.js +1 -0
  19. package/dist/client/assets/aurora-x-D-2ljcwZ.js +1 -0
  20. package/dist/client/assets/awk-DMzUqQB5.js +1 -0
  21. package/dist/client/assets/ayu-dark-DYE7WIF3.js +1 -0
  22. package/dist/client/assets/ayu-light-BA47KaF1.js +1 -0
  23. package/dist/client/assets/ayu-mirage-32ctXXKs.js +1 -0
  24. package/dist/client/assets/ballerina-BFfxhgS-.js +1 -0
  25. package/dist/client/assets/bat-BkioyH1T.js +1 -0
  26. package/dist/client/assets/beancount-k_qm7-4y.js +1 -0
  27. package/dist/client/assets/berry-uYugtg8r.js +1 -0
  28. package/dist/client/assets/bibtex-CHM0blh-.js +1 -0
  29. package/dist/client/assets/bicep-Bmn6On1c.js +1 -0
  30. package/dist/client/assets/bird2-DPOp833l.js +1 -0
  31. package/dist/client/assets/blade-D4QpJJKB.js +1 -0
  32. package/dist/client/assets/bricolage-grotesque-latin-ext-wght-normal-CcLUaPy7.woff2 +0 -0
  33. package/dist/client/assets/bricolage-grotesque-latin-wght-normal-DLoelf7F.woff2 +0 -0
  34. package/dist/client/assets/bricolage-grotesque-vietnamese-wght-normal-BUzh504Q.woff2 +0 -0
  35. package/dist/client/assets/bsl-BO_Y6i37.js +1 -0
  36. package/dist/client/assets/c-BIGW1oBm.js +1 -0
  37. package/dist/client/assets/c3-eo99z4R2.js +1 -0
  38. package/dist/client/assets/cadence-Bv_4Rxtq.js +1 -0
  39. package/dist/client/assets/cairo-KRGpt6FW.js +1 -0
  40. package/dist/client/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  41. package/dist/client/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  42. package/dist/client/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  43. package/dist/client/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  44. package/dist/client/assets/clarity-D53aC0YG.js +1 -0
  45. package/dist/client/assets/clojure-P80f7IUj.js +1 -0
  46. package/dist/client/assets/cmake-D1j8_8rp.js +1 -0
  47. package/dist/client/assets/cobol-nwyudZeR.js +1 -0
  48. package/dist/client/assets/codeowners-Bp6g37R7.js +1 -0
  49. package/dist/client/assets/codeql-DsOJ9woJ.js +1 -0
  50. package/dist/client/assets/coffee-Ch7k5sss.js +1 -0
  51. package/dist/client/assets/common-lisp-Cg-RD9OK.js +1 -0
  52. package/dist/client/assets/coq-DkFqJrB1.js +1 -0
  53. package/dist/client/assets/cpp-CofmeUqb.js +1 -0
  54. package/dist/client/assets/crystal-tKQVLTB8.js +1 -0
  55. package/dist/client/assets/csharp-COcwbKMJ.js +1 -0
  56. package/dist/client/assets/css-DPfMkruS.js +1 -0
  57. package/dist/client/assets/csv-fuZLfV_i.js +1 -0
  58. package/dist/client/assets/cue-D82EKSYY.js +1 -0
  59. package/dist/client/assets/cypher-COkxafJQ.js +1 -0
  60. package/dist/client/assets/d-85-TOEBH.js +1 -0
  61. package/dist/client/assets/dark-plus-C3mMm8J8.js +1 -0
  62. package/dist/client/assets/dart-CF10PKvl.js +1 -0
  63. package/dist/client/assets/dax-CEL-wOlO.js +1 -0
  64. package/dist/client/assets/desktop-BmXAJ9_W.js +1 -0
  65. package/dist/client/assets/diff-D97Zzqfu.js +1 -0
  66. package/dist/client/assets/docker-BcOcwvcX.js +1 -0
  67. package/dist/client/assets/dotenv-Da5cRb03.js +1 -0
  68. package/dist/client/assets/dracula-BzJJZx-M.js +1 -0
  69. package/dist/client/assets/dracula-soft-BXkSAIEj.js +1 -0
  70. package/dist/client/assets/dream-maker-BtqSS_iP.js +1 -0
  71. package/dist/client/assets/edge-BkV0erSs.js +1 -0
  72. package/dist/client/assets/elixir-CDX3lj18.js +1 -0
  73. package/dist/client/assets/elm-DbKCFpqz.js +1 -0
  74. package/dist/client/assets/emacs-lisp-C9XAeP06.js +1 -0
  75. package/dist/client/assets/erb-B12qg9BL.js +1 -0
  76. package/dist/client/assets/erlang-DsQrWhSR.js +1 -0
  77. package/dist/client/assets/everforest-dark-BgDCqdQA.js +1 -0
  78. package/dist/client/assets/everforest-light-C8M2exoo.js +1 -0
  79. package/dist/client/assets/fennel-BYunw83y.js +1 -0
  80. package/dist/client/assets/fish-BvzEVeQv.js +1 -0
  81. package/dist/client/assets/fluent-C4IJs8-o.js +1 -0
  82. package/dist/client/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  83. package/dist/client/assets/fortran-free-form-BxgE0vQu.js +1 -0
  84. package/dist/client/assets/fsharp-CXgrBDvD.js +1 -0
  85. package/dist/client/assets/gdresource-BOOCDP_w.js +1 -0
  86. package/dist/client/assets/gdscript-C5YyOfLZ.js +1 -0
  87. package/dist/client/assets/gdshader-DkwncUOv.js +1 -0
  88. package/dist/client/assets/genie-D0YGMca9.js +1 -0
  89. package/dist/client/assets/gherkin-DyxjwDmM.js +1 -0
  90. package/dist/client/assets/git-commit-F4YmCXRG.js +1 -0
  91. package/dist/client/assets/git-rebase-r7XF79zn.js +1 -0
  92. package/dist/client/assets/github-dark-DHJKELXO.js +1 -0
  93. package/dist/client/assets/github-dark-default-Cuk6v7N8.js +1 -0
  94. package/dist/client/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  95. package/dist/client/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  96. package/dist/client/assets/github-light-DAi9KRSo.js +1 -0
  97. package/dist/client/assets/github-light-default-D7oLnXFd.js +1 -0
  98. package/dist/client/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  99. package/dist/client/assets/gleam-BspZqrRM.js +1 -0
  100. package/dist/client/assets/glimmer-js-Rg0-pVw9.js +1 -0
  101. package/dist/client/assets/glimmer-ts-U6CK756n.js +1 -0
  102. package/dist/client/assets/glsl-DplSGwfg.js +1 -0
  103. package/dist/client/assets/gn-n2N0HUVH.js +1 -0
  104. package/dist/client/assets/gnuplot-DdkO51Og.js +1 -0
  105. package/dist/client/assets/go-CxLEBnE3.js +1 -0
  106. package/dist/client/assets/graphql-ChdNCCLP.js +1 -0
  107. package/dist/client/assets/groovy-gcz8RCvz.js +1 -0
  108. package/dist/client/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  109. package/dist/client/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  110. package/dist/client/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  111. package/dist/client/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  112. package/dist/client/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  113. package/dist/client/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  114. package/dist/client/assets/hack-CaT9iCJl.js +1 -0
  115. package/dist/client/assets/haml-B8DHNrY2.js +1 -0
  116. package/dist/client/assets/handlebars-BL8al0AC.js +1 -0
  117. package/dist/client/assets/haskell-Df6bDoY_.js +1 -0
  118. package/dist/client/assets/haxe-CzTSHFRz.js +1 -0
  119. package/dist/client/assets/hcl-BWvSN4gD.js +1 -0
  120. package/dist/client/assets/hjson-D5-asLiD.js +1 -0
  121. package/dist/client/assets/hlsl-D3lLCCz7.js +1 -0
  122. package/dist/client/assets/horizon-BUw7H-hv.js +1 -0
  123. package/dist/client/assets/horizon-bright-Cn-bp-IR.js +1 -0
  124. package/dist/client/assets/houston-DnULxvSX.js +1 -0
  125. package/dist/client/assets/html-GMplVEZG.js +1 -0
  126. package/dist/client/assets/html-derivative-BFtXZ54Q.js +1 -0
  127. package/dist/client/assets/http-jrhK8wxY.js +1 -0
  128. package/dist/client/assets/hurl-irOxFIW8.js +1 -0
  129. package/dist/client/assets/hxml-Bvhsp5Yf.js +1 -0
  130. package/dist/client/assets/hy-DFXneXwc.js +1 -0
  131. package/dist/client/assets/imba-DGztddWO.js +1 -0
  132. package/dist/client/assets/index-Do7324M0.css +32 -0
  133. package/dist/client/assets/index-ktE9DLCD.js +2620 -0
  134. package/dist/client/assets/ini-BEwlwnbL.js +1 -0
  135. package/dist/client/assets/java-CylS5w8V.js +1 -0
  136. package/dist/client/assets/javascript-wDzz0qaB.js +1 -0
  137. package/dist/client/assets/jinja-4LBKfQ-Z.js +1 -0
  138. package/dist/client/assets/jison-wvAkD_A8.js +1 -0
  139. package/dist/client/assets/json-Cp-IABpG.js +1 -0
  140. package/dist/client/assets/json5-C9tS-k6U.js +1 -0
  141. package/dist/client/assets/jsonc-Des-eS-w.js +1 -0
  142. package/dist/client/assets/jsonl-DcaNXYhu.js +1 -0
  143. package/dist/client/assets/jsonnet-DFQXde-d.js +1 -0
  144. package/dist/client/assets/jssm-C2t-YnRu.js +1 -0
  145. package/dist/client/assets/jsx-g9-lgVsj.js +1 -0
  146. package/dist/client/assets/julia-CxzCAyBv.js +1 -0
  147. package/dist/client/assets/just-Cw27pwNe.js +1 -0
  148. package/dist/client/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  149. package/dist/client/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  150. package/dist/client/assets/kanagawa-wave-DWedfzmr.js +1 -0
  151. package/dist/client/assets/kdl-DV7GczEv.js +1 -0
  152. package/dist/client/assets/kotlin-BdnUsdx6.js +1 -0
  153. package/dist/client/assets/kusto-DZf3V79B.js +1 -0
  154. package/dist/client/assets/laserwave-DUszq2jm.js +1 -0
  155. package/dist/client/assets/latex-CWtU0Tv5.js +1 -0
  156. package/dist/client/assets/lean-BZvkOJ9d.js +1 -0
  157. package/dist/client/assets/less-B1dDrJ26.js +1 -0
  158. package/dist/client/assets/light-plus-B7mTdjB0.js +1 -0
  159. package/dist/client/assets/liquid-DYVedYrR.js +1 -0
  160. package/dist/client/assets/llvm-DjAJT7YJ.js +1 -0
  161. package/dist/client/assets/log-2UxHyX5q.js +1 -0
  162. package/dist/client/assets/logo-BtOb2qkB.js +1 -0
  163. package/dist/client/assets/lua-BaeVxFsk.js +1 -0
  164. package/dist/client/assets/luau-C-HG3fhB.js +1 -0
  165. package/dist/client/assets/make-CHLpvVh8.js +1 -0
  166. package/dist/client/assets/markdown-Cvjx9yec.js +1 -0
  167. package/dist/client/assets/marko-CnJfTvn9.js +1 -0
  168. package/dist/client/assets/material-theme-D5KoaKCx.js +1 -0
  169. package/dist/client/assets/material-theme-darker-BfHTSMKl.js +1 -0
  170. package/dist/client/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  171. package/dist/client/assets/material-theme-ocean-CyktbL80.js +1 -0
  172. package/dist/client/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  173. package/dist/client/assets/matlab-D7o27uSR.js +1 -0
  174. package/dist/client/assets/mdc-BMNejdWA.js +1 -0
  175. package/dist/client/assets/mdx-Cmh6b_Ma.js +1 -0
  176. package/dist/client/assets/mermaid-mWjccvbQ.js +1 -0
  177. package/dist/client/assets/min-dark-CafNBF8u.js +1 -0
  178. package/dist/client/assets/min-light-CTRr51gU.js +1 -0
  179. package/dist/client/assets/mipsasm-CKIfxQSi.js +1 -0
  180. package/dist/client/assets/mojo-rZm6bMo-.js +1 -0
  181. package/dist/client/assets/monokai-D4h5O-jR.js +1 -0
  182. package/dist/client/assets/moonbit-_H4v1dQx.js +1 -0
  183. package/dist/client/assets/move-IF9eRakj.js +1 -0
  184. package/dist/client/assets/narrat-DRg8JJMk.js +1 -0
  185. package/dist/client/assets/nextflow-Zz6hmt5N.js +1 -0
  186. package/dist/client/assets/nextflow-groovy-BeH2EWoN.js +1 -0
  187. package/dist/client/assets/nginx-BpAMiNFr.js +1 -0
  188. package/dist/client/assets/night-owl-C39BiMTA.js +1 -0
  189. package/dist/client/assets/night-owl-light-CMTm3GFP.js +1 -0
  190. package/dist/client/assets/nim-CVrawwO9.js +1 -0
  191. package/dist/client/assets/nix-CwoSXNpI.js +1 -0
  192. package/dist/client/assets/nord-Ddv68eIx.js +1 -0
  193. package/dist/client/assets/nushell-Cz2AlsmD.js +1 -0
  194. package/dist/client/assets/objective-c-DXmwc3jG.js +1 -0
  195. package/dist/client/assets/objective-cpp-CLxacb5B.js +1 -0
  196. package/dist/client/assets/ocaml-C0hk2d4L.js +1 -0
  197. package/dist/client/assets/odin-BBf5iR-q.js +1 -0
  198. package/dist/client/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  199. package/dist/client/assets/one-light-C3Wv6jpd.js +1 -0
  200. package/dist/client/assets/openscad-C4EeE6gA.js +1 -0
  201. package/dist/client/assets/pascal-D93ZcfNL.js +1 -0
  202. package/dist/client/assets/perl-C0TMdlhV.js +1 -0
  203. package/dist/client/assets/php-Dhbhpdrm.js +1 -0
  204. package/dist/client/assets/pierre-dark-DF2SEV7i.js +1 -0
  205. package/dist/client/assets/pierre-light-DOlZxES8.js +1 -0
  206. package/dist/client/assets/pkl-u5AG7uiY.js +1 -0
  207. package/dist/client/assets/plastic-3e1v2bzS.js +1 -0
  208. package/dist/client/assets/plsql-ChMvpjG-.js +1 -0
  209. package/dist/client/assets/po-BTJTHyun.js +1 -0
  210. package/dist/client/assets/poimandres-CS3Unz2-.js +1 -0
  211. package/dist/client/assets/polar-C0HS_06l.js +1 -0
  212. package/dist/client/assets/postcss-CXtECtnM.js +1 -0
  213. package/dist/client/assets/powerquery-CEu0bR-o.js +1 -0
  214. package/dist/client/assets/powershell-Dpen1YoG.js +1 -0
  215. package/dist/client/assets/prisma-Dd19v3D-.js +1 -0
  216. package/dist/client/assets/prolog-CbFg5uaA.js +1 -0
  217. package/dist/client/assets/proto-C7zT0LnQ.js +1 -0
  218. package/dist/client/assets/pug-CGlum2m_.js +1 -0
  219. package/dist/client/assets/puppet-BMWR74SV.js +1 -0
  220. package/dist/client/assets/purescript-CklMAg4u.js +1 -0
  221. package/dist/client/assets/python-B6aJPvgy.js +1 -0
  222. package/dist/client/assets/qml-3beO22l8.js +1 -0
  223. package/dist/client/assets/qmldir-C8lEn-DE.js +1 -0
  224. package/dist/client/assets/qss-IeuSbFQv.js +1 -0
  225. package/dist/client/assets/r-Dspwwk_N.js +1 -0
  226. package/dist/client/assets/racket-BqYA7rlc.js +1 -0
  227. package/dist/client/assets/raku-DXvB9xmW.js +1 -0
  228. package/dist/client/assets/razor-Uh8Bk_45.js +1 -0
  229. package/dist/client/assets/red-bN70gL4F.js +1 -0
  230. package/dist/client/assets/reg-C-SQnVFl.js +1 -0
  231. package/dist/client/assets/regexp-CDVJQ6XC.js +1 -0
  232. package/dist/client/assets/rel-C3B-1QV4.js +1 -0
  233. package/dist/client/assets/riscv-BM1_JUlF.js +1 -0
  234. package/dist/client/assets/ron-D8l8udqQ.js +1 -0
  235. package/dist/client/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  236. package/dist/client/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  237. package/dist/client/assets/rose-pine-qdsjHGoJ.js +1 -0
  238. package/dist/client/assets/rosmsg-BJDFO7_C.js +1 -0
  239. package/dist/client/assets/rst-BrH8l1NY.js +1 -0
  240. package/dist/client/assets/ruby-Dw2BHqvy.js +1 -0
  241. package/dist/client/assets/rust-B1yitclQ.js +1 -0
  242. package/dist/client/assets/sas-cz2c8ADy.js +1 -0
  243. package/dist/client/assets/sass-Cj5Yp3dK.js +1 -0
  244. package/dist/client/assets/scala-C151Ov-r.js +1 -0
  245. package/dist/client/assets/scheme-C98Dy4si.js +1 -0
  246. package/dist/client/assets/scss-OYdSNvt2.js +1 -0
  247. package/dist/client/assets/sdbl-DVxCFoDh.js +1 -0
  248. package/dist/client/assets/shaderlab-Dg9Lc6iA.js +1 -0
  249. package/dist/client/assets/shellscript-Yzrsuije.js +1 -0
  250. package/dist/client/assets/shellsession-BADoaaVG.js +1 -0
  251. package/dist/client/assets/slack-dark-BthQWCQV.js +1 -0
  252. package/dist/client/assets/slack-ochin-DqwNpetd.js +1 -0
  253. package/dist/client/assets/smalltalk-BERRCDM3.js +1 -0
  254. package/dist/client/assets/snazzy-light-Bw305WKR.js +1 -0
  255. package/dist/client/assets/solarized-dark-DXbdFlpD.js +1 -0
  256. package/dist/client/assets/solarized-light-L9t79GZl.js +1 -0
  257. package/dist/client/assets/solidity-rGO070M0.js +1 -0
  258. package/dist/client/assets/soy-Brmx7dQM.js +1 -0
  259. package/dist/client/assets/sparql-rVzFXLq3.js +1 -0
  260. package/dist/client/assets/splunk-BtCnVYZw.js +1 -0
  261. package/dist/client/assets/sql-BLtJtn59.js +1 -0
  262. package/dist/client/assets/ssh-config-_ykCGR6B.js +1 -0
  263. package/dist/client/assets/stata-BH5u7GGu.js +1 -0
  264. package/dist/client/assets/stylus-BEDo0Tqx.js +1 -0
  265. package/dist/client/assets/surrealql-Bq5Q-fJD.js +1 -0
  266. package/dist/client/assets/svelte-C_ipcX3V.js +1 -0
  267. package/dist/client/assets/swift-D82vCrfD.js +1 -0
  268. package/dist/client/assets/synthwave-84-CbfX1IO0.js +1 -0
  269. package/dist/client/assets/system-verilog-CnnmHF94.js +1 -0
  270. package/dist/client/assets/systemd-4A_iFExJ.js +1 -0
  271. package/dist/client/assets/talonscript-CkByrt1z.js +1 -0
  272. package/dist/client/assets/tasl-QIJgUcNo.js +1 -0
  273. package/dist/client/assets/tcl-dwOrl1Do.js +1 -0
  274. package/dist/client/assets/templ-P3uqSqPl.js +1 -0
  275. package/dist/client/assets/terraform-BETggiCN.js +1 -0
  276. package/dist/client/assets/tex-idrVyKtj.js +1 -0
  277. package/dist/client/assets/tokyo-night-hegEt444.js +1 -0
  278. package/dist/client/assets/toml-vGWfd6FD.js +1 -0
  279. package/dist/client/assets/ts-tags-zn1MmPIZ.js +1 -0
  280. package/dist/client/assets/tsv-B_m7g4N7.js +1 -0
  281. package/dist/client/assets/tsx-COt5Ahok.js +1 -0
  282. package/dist/client/assets/turtle-BsS91CYL.js +1 -0
  283. package/dist/client/assets/twig-DNn4PbVi.js +1 -0
  284. package/dist/client/assets/typescript-BPQ3VLAy.js +1 -0
  285. package/dist/client/assets/typespec-BGHnOYBU.js +1 -0
  286. package/dist/client/assets/typst-DHCkPAjA.js +1 -0
  287. package/dist/client/assets/v-BcVCzyr7.js +1 -0
  288. package/dist/client/assets/vala-CsfeWuGM.js +1 -0
  289. package/dist/client/assets/vb-D17OF-Vu.js +1 -0
  290. package/dist/client/assets/verilog-BQ8w6xss.js +1 -0
  291. package/dist/client/assets/vesper-DU1UobuO.js +1 -0
  292. package/dist/client/assets/vhdl-CeAyd5Ju.js +1 -0
  293. package/dist/client/assets/viml-CJc9bBzg.js +1 -0
  294. package/dist/client/assets/vitesse-black-Bkuqu6BP.js +1 -0
  295. package/dist/client/assets/vitesse-dark-D0r3Knsf.js +1 -0
  296. package/dist/client/assets/vitesse-light-CVO1_9PV.js +1 -0
  297. package/dist/client/assets/vue-DN_0RTcg.js +1 -0
  298. package/dist/client/assets/vue-html-AaS7Mt5G.js +1 -0
  299. package/dist/client/assets/vue-vine-CQOfvN7w.js +1 -0
  300. package/dist/client/assets/vyper-CDx5xZoG.js +1 -0
  301. package/dist/client/assets/wasm-CG6Dc4jp.js +1 -0
  302. package/dist/client/assets/wasm-MzD3tlZU.js +1 -0
  303. package/dist/client/assets/wenyan-BV7otONQ.js +1 -0
  304. package/dist/client/assets/wgsl-Dx-B1_4e.js +1 -0
  305. package/dist/client/assets/wikitext-BhOHFoWU.js +1 -0
  306. package/dist/client/assets/wit-5i3qLPDT.js +1 -0
  307. package/dist/client/assets/wolfram-lXgVvXCa.js +1 -0
  308. package/dist/client/assets/xml-sdJ4AIDG.js +1 -0
  309. package/dist/client/assets/xsl-CtQFsRM5.js +1 -0
  310. package/dist/client/assets/yaml-Buea-lGh.js +1 -0
  311. package/dist/client/assets/zenscript-DVFEvuxE.js +1 -0
  312. package/dist/client/assets/zig-VOosw3JB.js +1 -0
  313. package/dist/client/chat-sounds/Blow.mp3 +0 -0
  314. package/dist/client/chat-sounds/Bottle.mp3 +0 -0
  315. package/dist/client/chat-sounds/Frog.mp3 +0 -0
  316. package/dist/client/chat-sounds/Funk.mp3 +0 -0
  317. package/dist/client/chat-sounds/Glass.mp3 +0 -0
  318. package/dist/client/chat-sounds/Ping.mp3 +0 -0
  319. package/dist/client/chat-sounds/Pop.mp3 +0 -0
  320. package/dist/client/chat-sounds/Purr.mp3 +0 -0
  321. package/dist/client/chat-sounds/Tink.mp3 +0 -0
  322. package/dist/client/editor-icons/cursor.png +0 -0
  323. package/dist/client/editor-icons/custom.png +0 -0
  324. package/dist/client/editor-icons/default-app.png +0 -0
  325. package/dist/client/editor-icons/finder.png +0 -0
  326. package/dist/client/editor-icons/preview.png +0 -0
  327. package/dist/client/editor-icons/terminal.png +0 -0
  328. package/dist/client/editor-icons/windsurf.png +0 -0
  329. package/dist/client/editor-icons/xcode.png +0 -0
  330. package/dist/client/favicon.png +0 -0
  331. package/dist/client/fonts/body-medium.woff2 +0 -0
  332. package/dist/client/fonts/body-regular-italic.woff2 +0 -0
  333. package/dist/client/fonts/body-regular.woff2 +0 -0
  334. package/dist/client/fonts/body-semibold.woff2 +0 -0
  335. package/dist/client/icon-192.png +0 -0
  336. package/dist/client/icon-512.png +0 -0
  337. package/dist/client/icon-maskable-512.png +0 -0
  338. package/dist/client/icon.svg +4 -0
  339. package/dist/client/index.html +34 -0
  340. package/dist/client/manifest.webmanifest +46 -0
  341. package/dist/client/screenshot-light.png +0 -0
  342. package/dist/client/screenshot.png +0 -0
  343. package/dist/export-viewer/assets/bricolage-grotesque-latin-ext-wght-normal-CcLUaPy7.woff2 +0 -0
  344. package/dist/export-viewer/assets/bricolage-grotesque-latin-wght-normal-DLoelf7F.woff2 +0 -0
  345. package/dist/export-viewer/assets/bricolage-grotesque-vietnamese-wght-normal-BUzh504Q.woff2 +0 -0
  346. package/dist/export-viewer/assets/index-D1qUumZR.js +410 -0
  347. package/dist/export-viewer/assets/index-gG2nMW51.css +1 -0
  348. package/dist/export-viewer/editor-icons/cursor.png +0 -0
  349. package/dist/export-viewer/editor-icons/custom.png +0 -0
  350. package/dist/export-viewer/editor-icons/default-app.png +0 -0
  351. package/dist/export-viewer/editor-icons/finder.png +0 -0
  352. package/dist/export-viewer/editor-icons/preview.png +0 -0
  353. package/dist/export-viewer/editor-icons/terminal.png +0 -0
  354. package/dist/export-viewer/editor-icons/windsurf.png +0 -0
  355. package/dist/export-viewer/editor-icons/xcode.png +0 -0
  356. package/dist/export-viewer/fonts/body-medium.woff2 +0 -0
  357. package/dist/export-viewer/fonts/body-regular-italic.woff2 +0 -0
  358. package/dist/export-viewer/fonts/body-regular.woff2 +0 -0
  359. package/dist/export-viewer/fonts/body-semibold.woff2 +0 -0
  360. package/dist/export-viewer/index.html +14 -0
  361. package/package.json +99 -0
  362. package/src/server/__fixtures__/claude-session-empty.jsonl +0 -0
  363. package/src/server/__fixtures__/claude-session-malformed.jsonl +3 -0
  364. package/src/server/__fixtures__/claude-session-valid.jsonl +6 -0
  365. package/src/server/agent.test.ts +2369 -0
  366. package/src/server/agent.ts +1927 -0
  367. package/src/server/analytics.test.ts +313 -0
  368. package/src/server/analytics.ts +131 -0
  369. package/src/server/app-settings.test.ts +233 -0
  370. package/src/server/app-settings.ts +548 -0
  371. package/src/server/auth.test.ts +329 -0
  372. package/src/server/auth.ts +204 -0
  373. package/src/server/auto-continue/e2e.test.ts +215 -0
  374. package/src/server/auto-continue/events.test.ts +30 -0
  375. package/src/server/auto-continue/events.ts +35 -0
  376. package/src/server/auto-continue/limit-detector.test.ts +153 -0
  377. package/src/server/auto-continue/limit-detector.ts +159 -0
  378. package/src/server/auto-continue/read-model.test.ts +109 -0
  379. package/src/server/auto-continue/read-model.ts +83 -0
  380. package/src/server/auto-continue/schedule-manager.test.ts +155 -0
  381. package/src/server/auto-continue/schedule-manager.ts +116 -0
  382. package/src/server/claude-session-importer.test.ts +214 -0
  383. package/src/server/claude-session-importer.ts +187 -0
  384. package/src/server/claude-session-mapper.test.ts +88 -0
  385. package/src/server/claude-session-mapper.ts +106 -0
  386. package/src/server/claude-session-parser.test.ts +38 -0
  387. package/src/server/claude-session-parser.ts +67 -0
  388. package/src/server/claude-session-scanner.test.ts +49 -0
  389. package/src/server/claude-session-scanner.ts +24 -0
  390. package/src/server/claude-session-types.ts +61 -0
  391. package/src/server/cli-runtime.test.ts +523 -0
  392. package/src/server/cli-runtime.ts +405 -0
  393. package/src/server/cli-supervisor.ts +102 -0
  394. package/src/server/cli.ts +64 -0
  395. package/src/server/cloudflare-tunnel/agent-integration.test.ts +76 -0
  396. package/src/server/cloudflare-tunnel/agent-integration.ts +55 -0
  397. package/src/server/cloudflare-tunnel/detector.test.ts +72 -0
  398. package/src/server/cloudflare-tunnel/detector.ts +44 -0
  399. package/src/server/cloudflare-tunnel/e2e.test.ts +194 -0
  400. package/src/server/cloudflare-tunnel/events.test.ts +43 -0
  401. package/src/server/cloudflare-tunnel/events.ts +31 -0
  402. package/src/server/cloudflare-tunnel/gateway.ts +143 -0
  403. package/src/server/cloudflare-tunnel/lifecycle.test.ts +48 -0
  404. package/src/server/cloudflare-tunnel/lifecycle.ts +62 -0
  405. package/src/server/cloudflare-tunnel/read-model.test.ts +69 -0
  406. package/src/server/cloudflare-tunnel/read-model.ts +80 -0
  407. package/src/server/cloudflare-tunnel/tunnel-manager.test.ts +116 -0
  408. package/src/server/cloudflare-tunnel/tunnel-manager.ts +165 -0
  409. package/src/server/codex-app-server-protocol.ts +487 -0
  410. package/src/server/codex-app-server.test.ts +1816 -0
  411. package/src/server/codex-app-server.ts +1475 -0
  412. package/src/server/diff-store.test.ts +737 -0
  413. package/src/server/diff-store.ts +2199 -0
  414. package/src/server/discovery.test.ts +211 -0
  415. package/src/server/discovery.ts +301 -0
  416. package/src/server/event-store.test.ts +797 -0
  417. package/src/server/event-store.ts +1421 -0
  418. package/src/server/events.ts +217 -0
  419. package/src/server/external-open.test.ts +112 -0
  420. package/src/server/external-open.ts +345 -0
  421. package/src/server/generate-commit-message.test.ts +79 -0
  422. package/src/server/generate-commit-message.ts +126 -0
  423. package/src/server/generate-title.ts +76 -0
  424. package/src/server/harness-types.ts +19 -0
  425. package/src/server/keybindings.test.ts +144 -0
  426. package/src/server/keybindings.ts +178 -0
  427. package/src/server/llm-provider.test.ts +134 -0
  428. package/src/server/llm-provider.ts +207 -0
  429. package/src/server/machine-name.ts +22 -0
  430. package/src/server/paths-route.test.ts +64 -0
  431. package/src/server/paths.ts +35 -0
  432. package/src/server/process-utils.test.ts +12 -0
  433. package/src/server/process-utils.ts +47 -0
  434. package/src/server/project-paths.test.ts +95 -0
  435. package/src/server/project-paths.ts +191 -0
  436. package/src/server/provider-catalog.test.ts +69 -0
  437. package/src/server/provider-catalog.ts +87 -0
  438. package/src/server/quick-response.test.ts +440 -0
  439. package/src/server/quick-response.ts +300 -0
  440. package/src/server/read-models.test.ts +509 -0
  441. package/src/server/read-models.ts +230 -0
  442. package/src/server/restart.test.ts +27 -0
  443. package/src/server/restart.ts +30 -0
  444. package/src/server/server.ts +616 -0
  445. package/src/server/share.test.ts +180 -0
  446. package/src/server/share.ts +150 -0
  447. package/src/server/standalone-export.test.ts +224 -0
  448. package/src/server/standalone-export.ts +419 -0
  449. package/src/server/terminal-manager.test.ts +315 -0
  450. package/src/server/terminal-manager.ts +350 -0
  451. package/src/server/test-helpers/async-event-queue.ts +52 -0
  452. package/src/server/test-helpers/wait-for.ts +14 -0
  453. package/src/server/title-generation.live.test.ts +44 -0
  454. package/src/server/update-manager.test.ts +158 -0
  455. package/src/server/update-manager.ts +222 -0
  456. package/src/server/update-strategy.test.ts +237 -0
  457. package/src/server/update-strategy.ts +241 -0
  458. package/src/server/uploads.test.ts +292 -0
  459. package/src/server/uploads.ts +131 -0
  460. package/src/server/ws-router.test.ts +2292 -0
  461. package/src/server/ws-router.ts +1465 -0
  462. package/src/shared/analytics.ts +30 -0
  463. package/src/shared/branding.test.ts +31 -0
  464. package/src/shared/branding.ts +77 -0
  465. package/src/shared/dev-ports.test.ts +113 -0
  466. package/src/shared/dev-ports.ts +134 -0
  467. package/src/shared/ports.ts +2 -0
  468. package/src/shared/protocol.ts +257 -0
  469. package/src/shared/share.ts +27 -0
  470. package/src/shared/tools.test.ts +164 -0
  471. package/src/shared/tools.ts +327 -0
  472. package/src/shared/types.test.ts +25 -0
  473. package/src/shared/types.ts +1088 -0
@@ -0,0 +1,616 @@
1
+ import path from "node:path"
2
+ import { stat } from "node:fs/promises"
3
+ import { APP_NAME, getRuntimeProfile } from "../shared/branding"
4
+ import type { ChatAttachment } from "../shared/types"
5
+ import type { ShareMode } from "../shared/share"
6
+ import { createAuthManager } from "./auth"
7
+ import { EventStore } from "./event-store"
8
+ import { AgentCoordinator } from "./agent"
9
+ import type { LimitDetector } from "./auto-continue/limit-detector"
10
+ import { KannaAnalyticsReporter } from "./analytics"
11
+ import { AppSettingsManager } from "./app-settings"
12
+ import { DiffStore } from "./diff-store"
13
+ import { discoverProjects, type DiscoveredProject } from "./discovery"
14
+ import { KeybindingsManager } from "./keybindings"
15
+ import { readLlmProviderSnapshot, validateLlmProviderCredentials, writeLlmProviderSnapshot } from "./llm-provider"
16
+ import { getMachineDisplayName } from "./machine-name"
17
+ import { TerminalManager } from "./terminal-manager"
18
+ import { UpdateManager } from "./update-manager"
19
+ import type { UpdateInstallAttemptResult } from "./cli-runtime"
20
+ import { compareVersions } from "./cli-runtime"
21
+ import { createUpdateStrategy } from "./update-strategy"
22
+ import { createWsRouter, type ClientState } from "./ws-router"
23
+ import { deleteProjectUpload, inferAttachmentContentType, inferProjectFileContentType, persistProjectUpload } from "./uploads"
24
+ import { getProjectUploadDir } from "./paths"
25
+ import { listProjectPaths } from "./project-paths"
26
+ import { ScheduleManager } from "./auto-continue/schedule-manager"
27
+ import { TunnelGateway } from "./cloudflare-tunnel/gateway"
28
+ import { TunnelManager } from "./cloudflare-tunnel/tunnel-manager"
29
+ import { TunnelLifecycle } from "./cloudflare-tunnel/lifecycle"
30
+
31
+ const MAX_UPLOAD_FILES = 50
32
+ const MAX_UPLOAD_SIZE_BYTES = 100 * 1024 * 1024
33
+ const STALE_EMPTY_CHAT_PRUNE_INTERVAL_MS = 60 * 1000
34
+
35
+ export async function persistUploadedFiles(args: {
36
+ projectId: string
37
+ localPath: string
38
+ files: File[]
39
+ persistUpload?: typeof persistProjectUpload
40
+ }): Promise<ChatAttachment[]> {
41
+ const persistUpload = args.persistUpload ?? persistProjectUpload
42
+ const attachments: ChatAttachment[] = []
43
+
44
+ try {
45
+ for (const file of args.files) {
46
+ const bytes = new Uint8Array(await file.arrayBuffer())
47
+ const attachment = await persistUpload({
48
+ projectId: args.projectId,
49
+ localPath: args.localPath,
50
+ fileName: file.name,
51
+ bytes,
52
+ fallbackMimeType: file.type || undefined,
53
+ })
54
+ attachments.push(attachment)
55
+ }
56
+ } catch (error) {
57
+ await Promise.allSettled(
58
+ attachments.map((attachment) => deleteProjectUpload({
59
+ localPath: args.localPath,
60
+ storedName: path.basename(attachment.absolutePath),
61
+ }))
62
+ )
63
+ throw error
64
+ }
65
+
66
+ return attachments
67
+ }
68
+
69
+ export interface StartKannaServerOptions {
70
+ port?: number
71
+ host?: string
72
+ openBrowser?: boolean
73
+ share?: ShareMode
74
+ dataDir?: string
75
+ password?: string | null
76
+ strictPort?: boolean
77
+ /**
78
+ * When true, the auth layer trusts X-Forwarded-Proto for CSRF origin
79
+ * checks, redirect URLs, and the Secure cookie flag. The hostname still
80
+ * comes from the request URL / Host header. Only enable when the server is
81
+ * reachable solely through a trusted reverse proxy such as cloudflared.
82
+ */
83
+ trustProxy?: boolean
84
+ onMigrationProgress?: (message: string) => void
85
+ update?: {
86
+ version: string
87
+ fetchLatestVersion: (packageName: string) => Promise<string>
88
+ installVersion: (packageName: string, version: string) => UpdateInstallAttemptResult
89
+ }
90
+ agentOverrides?: {
91
+ claudeLimitDetector?: LimitDetector
92
+ codexLimitDetector?: LimitDetector
93
+ throwOnClaudeSessionStart?: boolean
94
+ }
95
+ }
96
+
97
+ export async function startKannaServer(options: StartKannaServerOptions = {}) {
98
+ const port = options.port ?? 3210
99
+ const hostname = options.host ?? "127.0.0.1"
100
+ const strictPort = options.strictPort ?? false
101
+ const runtimeProfile = getRuntimeProfile()
102
+ const auth = options.password ? createAuthManager(options.password, { trustProxy: options.trustProxy ?? false }) : null
103
+ const store = new EventStore(options.dataDir)
104
+ const diffStore = new DiffStore(store.dataDir)
105
+ const machineDisplayName = getMachineDisplayName()
106
+ await store.initialize()
107
+ await diffStore.initialize()
108
+ await store.migrateLegacyTranscripts(options.onMigrationProgress)
109
+ let discoveredProjects: DiscoveredProject[] = []
110
+
111
+ async function refreshDiscovery() {
112
+ discoveredProjects = discoverProjects()
113
+ return discoveredProjects
114
+ }
115
+
116
+ await refreshDiscovery()
117
+
118
+ let server: ReturnType<typeof Bun.serve<ClientState>>
119
+ let router: ReturnType<typeof createWsRouter>
120
+ const terminals = new TerminalManager()
121
+ const keybindings = new KeybindingsManager()
122
+ const appSettings = new AppSettingsManager(path.join(store.dataDir, "settings.json"))
123
+ await appSettings.initialize()
124
+ await keybindings.initialize()
125
+ const analytics = new KannaAnalyticsReporter({
126
+ settings: appSettings,
127
+ currentVersion: options.update?.version ?? "unknown",
128
+ environment: runtimeProfile === "dev" ? "dev" : "prod",
129
+ })
130
+ const updateManager: UpdateManager | null = (() => {
131
+ if (!options.update) return null
132
+ let manager: UpdateManager | null = null
133
+ const strategy = createUpdateStrategy({
134
+ reloaderEnv: process.env.KANNA_RELOADER,
135
+ currentVersion: options.update.version,
136
+ fetchLatestVersion: options.update.fetchLatestVersion,
137
+ installVersion: options.update.installVersion,
138
+ latestVersionHint: () => {
139
+ const snapshot = manager?.getSnapshot()
140
+ if (!snapshot) return null
141
+ const latest = snapshot.latestVersion
142
+ const current = snapshot.currentVersion
143
+ if (!latest) return current
144
+ return compareVersions(latest, current) > 0 ? latest : current
145
+ },
146
+ repoDir: process.env.KANNA_REPO_DIR,
147
+ pm2ProcessName: process.env.KANNA_PM2_PROCESS_NAME,
148
+ })
149
+ manager = new UpdateManager({
150
+ currentVersion: options.update.version,
151
+ checker: strategy.checker,
152
+ reloader: strategy.reloader,
153
+ devMode: runtimeProfile === "dev",
154
+ trackEvent: analytics.track.bind(analytics),
155
+ })
156
+ return manager
157
+ })()
158
+ const broadcastTunnel = (chatId: string) => {
159
+ router.scheduleChatStateBroadcast(chatId)
160
+ }
161
+ const tunnelManager = new TunnelManager({
162
+ cloudflaredPath: appSettings.getSnapshot().cloudflareTunnel.cloudflaredPath,
163
+ onEvent: async (event) => {
164
+ await store.appendTunnelEvent(event)
165
+ broadcastTunnel(event.chatId)
166
+ },
167
+ })
168
+ const tunnelLifecycle = new TunnelLifecycle({
169
+ onSourceExit: (tunnelId) => { void tunnelManager.stop(tunnelId, "source_exited") },
170
+ })
171
+ const tunnelGateway = new TunnelGateway({
172
+ manager: tunnelManager,
173
+ lifecycle: tunnelLifecycle,
174
+ settings: appSettings,
175
+ store,
176
+ broadcast: broadcastTunnel,
177
+ })
178
+
179
+ let agent!: AgentCoordinator
180
+ const scheduleManager = new ScheduleManager({
181
+ fire: async (chatId, scheduleId) => {
182
+ await agent.fireAutoContinue(chatId, scheduleId)
183
+ },
184
+ })
185
+ agent = new AgentCoordinator({
186
+ store,
187
+ scheduleManager,
188
+ claudeLimitDetector: options.agentOverrides?.claudeLimitDetector,
189
+ codexLimitDetector: options.agentOverrides?.codexLimitDetector,
190
+ throwOnClaudeSessionStart: options.agentOverrides?.throwOnClaudeSessionStart,
191
+ analytics,
192
+ tunnelGateway,
193
+ onStateChange: (chatId?: string, options?: { immediate?: boolean }) => {
194
+ if (chatId) {
195
+ if (options?.immediate) {
196
+ void router.broadcastChatStateImmediately(chatId)
197
+ return
198
+ }
199
+ router.scheduleChatStateBroadcast(chatId)
200
+ return
201
+ }
202
+ router.scheduleBroadcast()
203
+ },
204
+ })
205
+ router = createWsRouter({
206
+ store,
207
+ diffStore,
208
+ agent,
209
+ terminals,
210
+ keybindings,
211
+ appSettings,
212
+ analytics,
213
+ tunnelGateway,
214
+ llmProvider: {
215
+ read: readLlmProviderSnapshot,
216
+ write: writeLlmProviderSnapshot,
217
+ validate: validateLlmProviderCredentials,
218
+ },
219
+ refreshDiscovery,
220
+ getDiscoveredProjects: () => discoveredProjects,
221
+ machineDisplayName,
222
+ updateManager,
223
+ })
224
+ scheduleManager.rehydrate(
225
+ store.listAutoContinueChats().flatMap((chatId) => store.getAutoContinueEvents(chatId))
226
+ )
227
+ await tunnelGateway.reapOrphanedTunnels()
228
+ const staleEmptyChatPruneInterval = setInterval(() => {
229
+ void router.pruneStaleEmptyChats()
230
+ .then(() => router.broadcastSnapshots())
231
+ }, STALE_EMPTY_CHAT_PRUNE_INTERVAL_MS)
232
+
233
+ const distDir = path.join(import.meta.dir, "..", "..", "dist", "client")
234
+
235
+ const MAX_PORT_ATTEMPTS = 20
236
+ let actualPort = port
237
+
238
+ for (let attempt = 0; attempt < MAX_PORT_ATTEMPTS; attempt++) {
239
+ try {
240
+ server = Bun.serve<ClientState>({
241
+ port: actualPort,
242
+ hostname,
243
+ async fetch(req, serverInstance) {
244
+ const url = new URL(req.url)
245
+
246
+ if (url.pathname === "/auth/status") {
247
+ return auth
248
+ ? auth.handleStatus(req)
249
+ : Response.json({ enabled: false, authenticated: true })
250
+ }
251
+
252
+ if (url.pathname === "/auth/logout") {
253
+ if (req.method !== "POST") {
254
+ return new Response(null, { status: 405, headers: { Allow: "POST" } })
255
+ }
256
+
257
+ return auth
258
+ ? auth.handleLogout(req)
259
+ : Response.json({ ok: true })
260
+ }
261
+
262
+ if (auth) {
263
+ if (url.pathname === "/auth/login") {
264
+ if (req.method === "GET") {
265
+ return auth.redirectToApp(req)
266
+ }
267
+ if (req.method === "POST") {
268
+ return auth.handleLogin(req, "/")
269
+ }
270
+ return new Response(null, { status: 405, headers: { Allow: "GET, POST" } })
271
+ }
272
+
273
+ if (url.pathname === "/ws") {
274
+ if (!auth.validateOrigin(req)) {
275
+ return new Response("Forbidden", { status: 403 })
276
+ }
277
+ if (!auth.isAuthenticated(req)) {
278
+ return new Response("Unauthorized", { status: 401 })
279
+ }
280
+ } else if (url.pathname.startsWith("/api/") && !auth.isAuthenticated(req)) {
281
+ return Response.json({ error: "Unauthorized" }, { status: 401 })
282
+ }
283
+ }
284
+
285
+ if (url.pathname === "/ws") {
286
+ const upgraded = serverInstance.upgrade(req, {
287
+ data: {
288
+ subscriptions: new Map(),
289
+ snapshotSignatures: new Map(),
290
+ },
291
+ })
292
+ return upgraded ? undefined : new Response("WebSocket upgrade failed", { status: 400 })
293
+ }
294
+
295
+ if (url.pathname === "/health") {
296
+ return Response.json({ ok: true, port: actualPort })
297
+ }
298
+
299
+ const uploadResponse = await handleProjectUpload(req, url, store)
300
+ if (uploadResponse) {
301
+ return uploadResponse
302
+ }
303
+
304
+ const deleteUploadResponse = await handleProjectUploadDelete(req, url, store)
305
+ if (deleteUploadResponse) {
306
+ return deleteUploadResponse
307
+ }
308
+
309
+ const attachmentContentResponse = await handleAttachmentContent(req, url, store)
310
+ if (attachmentContentResponse) {
311
+ return attachmentContentResponse
312
+ }
313
+
314
+ const projectFileContentResponse = await handleProjectFileContent(req, url, store)
315
+ if (projectFileContentResponse) {
316
+ return projectFileContentResponse
317
+ }
318
+
319
+ const projectPathsResponse = await handleProjectPaths(req, url, store)
320
+ if (projectPathsResponse) {
321
+ return projectPathsResponse
322
+ }
323
+
324
+ return serveStatic(distDir, url.pathname)
325
+ },
326
+ websocket: {
327
+ open(ws) {
328
+ router.handleOpen(ws)
329
+ },
330
+ message(ws, raw) {
331
+ router.handleMessage(ws, raw)
332
+ },
333
+ close(ws) {
334
+ router.handleClose(ws)
335
+ },
336
+ },
337
+ })
338
+ break
339
+ } catch (err: unknown) {
340
+ const isAddrInUse =
341
+ err instanceof Error && "code" in err && (err as NodeJS.ErrnoException).code === "EADDRINUSE"
342
+ if (!isAddrInUse || strictPort || attempt === MAX_PORT_ATTEMPTS - 1) {
343
+ throw err
344
+ }
345
+ console.log(`Port ${actualPort} is in use, trying ${actualPort + 1}...`)
346
+ actualPort++
347
+ }
348
+ }
349
+
350
+ analytics.trackLaunch({
351
+ port: actualPort,
352
+ host: hostname,
353
+ openBrowser: options.openBrowser ?? true,
354
+ share: options.share ?? false,
355
+ password: options.password ?? null,
356
+ strictPort,
357
+ })
358
+
359
+ const shutdown = async () => {
360
+ scheduleManager.shutdown()
361
+ tunnelGateway.shutdown()
362
+ clearInterval(staleEmptyChatPruneInterval)
363
+ for (const chatId of [...agent.activeTurns.keys()]) {
364
+ await agent.cancel(chatId)
365
+ }
366
+ router.dispose()
367
+ appSettings.dispose()
368
+ keybindings.dispose()
369
+ terminals.closeAll()
370
+ await store.compact()
371
+ server.stop(true)
372
+ }
373
+
374
+ return {
375
+ port: actualPort,
376
+ store,
377
+ diffStore,
378
+ updateManager,
379
+ stop: shutdown,
380
+ }
381
+ }
382
+
383
+ async function handleProjectUpload(req: Request, url: URL, store: EventStore) {
384
+ if (req.method !== "POST") {
385
+ return null
386
+ }
387
+
388
+ const match = url.pathname.match(/^\/api\/projects\/([^/]+)\/uploads$/)
389
+ if (!match) {
390
+ return null
391
+ }
392
+
393
+ const project = store.getProject(match[1])
394
+ if (!project) {
395
+ return Response.json({ error: "Project not found" }, { status: 404 })
396
+ }
397
+
398
+ const formData = await req.formData()
399
+ const files = formData
400
+ .getAll("files")
401
+ .filter((value): value is File => value instanceof File)
402
+
403
+ if (files.length === 0) {
404
+ return Response.json({ error: "No files uploaded" }, { status: 400 })
405
+ }
406
+
407
+ if (files.length > MAX_UPLOAD_FILES) {
408
+ return Response.json({ error: `You can upload up to ${MAX_UPLOAD_FILES} files at a time.` }, { status: 400 })
409
+ }
410
+
411
+ for (const file of files) {
412
+ if (file.size > MAX_UPLOAD_SIZE_BYTES) {
413
+ return Response.json(
414
+ { error: `File "${file.name}" exceeds the ${Math.floor(MAX_UPLOAD_SIZE_BYTES / (1024 * 1024))} MB limit.` },
415
+ { status: 413 }
416
+ )
417
+ }
418
+ }
419
+
420
+ try {
421
+ const attachments = await persistUploadedFiles({
422
+ projectId: project.id,
423
+ localPath: project.localPath,
424
+ files,
425
+ })
426
+ return Response.json({ attachments })
427
+ } catch (error) {
428
+ console.error("[uploads] Upload failed:", error)
429
+ return Response.json({ error: "Upload failed" }, { status: 500 })
430
+ }
431
+ }
432
+
433
+ async function handleAttachmentContent(req: Request, url: URL, store: EventStore) {
434
+ const match = url.pathname.match(/^\/api\/projects\/([^/]+)\/uploads\/([^/]+)\/content$/)
435
+ if (!match) {
436
+ return null
437
+ }
438
+
439
+ if (req.method !== "GET") {
440
+ return new Response(null, {
441
+ status: 405,
442
+ headers: {
443
+ Allow: "GET",
444
+ },
445
+ })
446
+ }
447
+
448
+ const project = store.getProject(match[1])
449
+ if (!project) {
450
+ return Response.json({ error: "Project not found" }, { status: 404 })
451
+ }
452
+
453
+ const storedName = decodeURIComponent(match[2])
454
+ if (!storedName || storedName.includes("/") || storedName.includes("\\") || storedName === "." || storedName === "..") {
455
+ return Response.json({ error: "Invalid attachment path" }, { status: 400 })
456
+ }
457
+
458
+ const filePath = path.join(getProjectUploadDir(project.localPath), storedName)
459
+ const file = Bun.file(filePath)
460
+ try {
461
+ const info = await stat(filePath)
462
+ if (!info.isFile()) {
463
+ return Response.json({ error: "Attachment not found" }, { status: 404 })
464
+ }
465
+ } catch {
466
+ return Response.json({ error: "Attachment not found" }, { status: 404 })
467
+ }
468
+
469
+ return new Response(file, {
470
+ headers: {
471
+ "Content-Type": inferAttachmentContentType(storedName, file.type),
472
+ },
473
+ })
474
+ }
475
+
476
+ async function handleProjectFileContent(req: Request, url: URL, store: EventStore) {
477
+ const match = url.pathname.match(/^\/api\/projects\/([^/]+)\/files\/([^/]+)\/content$/)
478
+ if (!match) {
479
+ return null
480
+ }
481
+
482
+ if (req.method !== "GET") {
483
+ return new Response(null, {
484
+ status: 405,
485
+ headers: {
486
+ Allow: "GET",
487
+ },
488
+ })
489
+ }
490
+
491
+ const project = store.getProject(match[1])
492
+ if (!project) {
493
+ return Response.json({ error: "Project not found" }, { status: 404 })
494
+ }
495
+
496
+ const relativePath = path.posix.normalize(decodeURIComponent(match[2]).replaceAll("\\", "/"))
497
+ if (!relativePath || relativePath === "." || relativePath.startsWith("../") || relativePath.includes("/../") || path.posix.isAbsolute(relativePath)) {
498
+ return Response.json({ error: "Invalid project file path" }, { status: 400 })
499
+ }
500
+
501
+ const filePath = path.resolve(project.localPath, relativePath)
502
+ const projectRoot = path.resolve(project.localPath)
503
+ if (filePath !== projectRoot && !filePath.startsWith(`${projectRoot}${path.sep}`)) {
504
+ return Response.json({ error: "Invalid project file path" }, { status: 400 })
505
+ }
506
+
507
+ const file = Bun.file(filePath)
508
+ try {
509
+ const info = await stat(filePath)
510
+ if (!info.isFile()) {
511
+ return Response.json({ error: "File not found" }, { status: 404 })
512
+ }
513
+ } catch {
514
+ return Response.json({ error: "File not found" }, { status: 404 })
515
+ }
516
+
517
+ return new Response(file, {
518
+ headers: {
519
+ "Content-Type": inferProjectFileContentType(relativePath, file.type),
520
+ },
521
+ })
522
+ }
523
+
524
+ async function handleProjectUploadDelete(req: Request, url: URL, store: EventStore) {
525
+ if (req.method !== "DELETE") {
526
+ return null
527
+ }
528
+
529
+ const match = url.pathname.match(/^\/api\/projects\/([^/]+)\/uploads\/([^/]+)$/)
530
+ if (!match) {
531
+ return null
532
+ }
533
+
534
+ const project = store.getProject(match[1])
535
+ if (!project) {
536
+ return Response.json({ error: "Project not found" }, { status: 404 })
537
+ }
538
+
539
+ const storedName = decodeURIComponent(match[2])
540
+ if (!storedName || storedName.includes("/") || storedName.includes("\\") || storedName === "." || storedName === "..") {
541
+ return Response.json({ error: "Invalid attachment path" }, { status: 400 })
542
+ }
543
+
544
+ const deleted = await deleteProjectUpload({
545
+ localPath: project.localPath,
546
+ storedName,
547
+ })
548
+
549
+ return Response.json({ ok: deleted })
550
+ }
551
+
552
+ async function handleProjectPaths(req: Request, url: URL, store: EventStore) {
553
+ if (req.method !== "GET") return null
554
+ const match = url.pathname.match(/^\/api\/projects\/([^/]+)\/paths$/)
555
+ if (!match) return null
556
+
557
+ const project = store.getProject(match[1])
558
+ if (!project) {
559
+ return Response.json({ error: "Project not found" }, { status: 404 })
560
+ }
561
+
562
+ const query = url.searchParams.get("query") ?? ""
563
+ const limitRaw = url.searchParams.get("limit")
564
+ const limit = limitRaw !== null ? Number.parseInt(limitRaw, 10) : undefined
565
+
566
+ try {
567
+ const paths = await listProjectPaths({
568
+ projectId: project.id,
569
+ localPath: project.localPath,
570
+ query,
571
+ limit: Number.isFinite(limit) ? limit : undefined,
572
+ })
573
+ return Response.json({ paths })
574
+ } catch (error) {
575
+ console.error("[paths] list failed:", error)
576
+ return Response.json({ error: "Failed to list paths" }, { status: 500 })
577
+ }
578
+ }
579
+
580
+ async function serveStatic(distDir: string, pathname: string) {
581
+ const requestedPath = pathname === "/" ? "/index.html" : pathname
582
+ const filePath = path.join(distDir, requestedPath)
583
+ const indexPath = path.join(distDir, "index.html")
584
+
585
+ const file = Bun.file(filePath)
586
+ if (await file.exists()) {
587
+ return new Response(file, {
588
+ headers: getStaticHeaders(requestedPath),
589
+ })
590
+ }
591
+
592
+ const indexFile = Bun.file(indexPath)
593
+ if (await indexFile.exists()) {
594
+ return new Response(indexFile, {
595
+ headers: {
596
+ "Content-Type": "text/html; charset=utf-8",
597
+ "Cache-Control": "no-store",
598
+ },
599
+ })
600
+ }
601
+
602
+ return new Response(
603
+ `${APP_NAME} client bundle not found. Run \`bun run build\` inside workbench/ first.`,
604
+ { status: 503 }
605
+ )
606
+ }
607
+
608
+ function getStaticHeaders(requestedPath: string) {
609
+ if (requestedPath.endsWith(".html")) {
610
+ return {
611
+ "Cache-Control": "no-store",
612
+ }
613
+ }
614
+
615
+ return undefined
616
+ }