kanna-code 0.19.0 → 0.21.0

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 (327) hide show
  1. package/dist/client/assets/abap-BdImnpbu.js +1 -0
  2. package/dist/client/assets/actionscript-3-CoDkCxhg.js +1 -0
  3. package/dist/client/assets/ada-bCR0ucgS.js +1 -0
  4. package/dist/client/assets/andromeeda-C4gqWexZ.js +1 -0
  5. package/dist/client/assets/angular-html-CU67Zn6k.js +1 -0
  6. package/dist/client/assets/angular-ts-BwZT4LLn.js +1 -0
  7. package/dist/client/assets/apache-Pmp26Uib.js +1 -0
  8. package/dist/client/assets/apex-D8_7TLub.js +1 -0
  9. package/dist/client/assets/apl-dKokRX4l.js +1 -0
  10. package/dist/client/assets/applescript-Co6uUVPk.js +1 -0
  11. package/dist/client/assets/ara-BRHolxvo.js +1 -0
  12. package/dist/client/assets/asciidoc-Ve4PFQV2.js +1 -0
  13. package/dist/client/assets/asm-D_Q5rh1f.js +1 -0
  14. package/dist/client/assets/astro-CbQHKStN.js +1 -0
  15. package/dist/client/assets/aurora-x-D-2ljcwZ.js +1 -0
  16. package/dist/client/assets/awk-DMzUqQB5.js +1 -0
  17. package/dist/client/assets/ayu-dark-DYE7WIF3.js +1 -0
  18. package/dist/client/assets/ayu-light-BA47KaF1.js +1 -0
  19. package/dist/client/assets/ayu-mirage-32ctXXKs.js +1 -0
  20. package/dist/client/assets/ballerina-BFfxhgS-.js +1 -0
  21. package/dist/client/assets/bat-BkioyH1T.js +1 -0
  22. package/dist/client/assets/beancount-k_qm7-4y.js +1 -0
  23. package/dist/client/assets/berry-uYugtg8r.js +1 -0
  24. package/dist/client/assets/bibtex-CHM0blh-.js +1 -0
  25. package/dist/client/assets/bicep-Bmn6On1c.js +1 -0
  26. package/dist/client/assets/bird2-DPOp833l.js +1 -0
  27. package/dist/client/assets/blade-D4QpJJKB.js +1 -0
  28. package/dist/client/assets/bsl-BO_Y6i37.js +1 -0
  29. package/dist/client/assets/c-BIGW1oBm.js +1 -0
  30. package/dist/client/assets/c3-eo99z4R2.js +1 -0
  31. package/dist/client/assets/cadence-Bv_4Rxtq.js +1 -0
  32. package/dist/client/assets/cairo-KRGpt6FW.js +1 -0
  33. package/dist/client/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  34. package/dist/client/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  35. package/dist/client/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  36. package/dist/client/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  37. package/dist/client/assets/clarity-D53aC0YG.js +1 -0
  38. package/dist/client/assets/clojure-P80f7IUj.js +1 -0
  39. package/dist/client/assets/cmake-D1j8_8rp.js +1 -0
  40. package/dist/client/assets/cobol-nwyudZeR.js +1 -0
  41. package/dist/client/assets/codeowners-Bp6g37R7.js +1 -0
  42. package/dist/client/assets/codeql-DsOJ9woJ.js +1 -0
  43. package/dist/client/assets/coffee-Ch7k5sss.js +1 -0
  44. package/dist/client/assets/common-lisp-Cg-RD9OK.js +1 -0
  45. package/dist/client/assets/coq-DkFqJrB1.js +1 -0
  46. package/dist/client/assets/cpp-CofmeUqb.js +1 -0
  47. package/dist/client/assets/crystal-tKQVLTB8.js +1 -0
  48. package/dist/client/assets/csharp-COcwbKMJ.js +1 -0
  49. package/dist/client/assets/css-DPfMkruS.js +1 -0
  50. package/dist/client/assets/csv-fuZLfV_i.js +1 -0
  51. package/dist/client/assets/cue-D82EKSYY.js +1 -0
  52. package/dist/client/assets/cypher-COkxafJQ.js +1 -0
  53. package/dist/client/assets/d-85-TOEBH.js +1 -0
  54. package/dist/client/assets/dark-plus-C3mMm8J8.js +1 -0
  55. package/dist/client/assets/dart-CF10PKvl.js +1 -0
  56. package/dist/client/assets/dax-CEL-wOlO.js +1 -0
  57. package/dist/client/assets/desktop-BmXAJ9_W.js +1 -0
  58. package/dist/client/assets/diff-D97Zzqfu.js +1 -0
  59. package/dist/client/assets/docker-BcOcwvcX.js +1 -0
  60. package/dist/client/assets/dotenv-Da5cRb03.js +1 -0
  61. package/dist/client/assets/dracula-BzJJZx-M.js +1 -0
  62. package/dist/client/assets/dracula-soft-BXkSAIEj.js +1 -0
  63. package/dist/client/assets/dream-maker-BtqSS_iP.js +1 -0
  64. package/dist/client/assets/edge-BkV0erSs.js +1 -0
  65. package/dist/client/assets/elixir-CDX3lj18.js +1 -0
  66. package/dist/client/assets/elm-DbKCFpqz.js +1 -0
  67. package/dist/client/assets/emacs-lisp-C9XAeP06.js +1 -0
  68. package/dist/client/assets/erb-B12qg9BL.js +1 -0
  69. package/dist/client/assets/erlang-DsQrWhSR.js +1 -0
  70. package/dist/client/assets/everforest-dark-BgDCqdQA.js +1 -0
  71. package/dist/client/assets/everforest-light-C8M2exoo.js +1 -0
  72. package/dist/client/assets/fennel-BYunw83y.js +1 -0
  73. package/dist/client/assets/fish-BvzEVeQv.js +1 -0
  74. package/dist/client/assets/fluent-C4IJs8-o.js +1 -0
  75. package/dist/client/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  76. package/dist/client/assets/fortran-free-form-BxgE0vQu.js +1 -0
  77. package/dist/client/assets/fsharp-CXgrBDvD.js +1 -0
  78. package/dist/client/assets/gdresource-BOOCDP_w.js +1 -0
  79. package/dist/client/assets/gdscript-C5YyOfLZ.js +1 -0
  80. package/dist/client/assets/gdshader-DkwncUOv.js +1 -0
  81. package/dist/client/assets/genie-D0YGMca9.js +1 -0
  82. package/dist/client/assets/gherkin-DyxjwDmM.js +1 -0
  83. package/dist/client/assets/git-commit-F4YmCXRG.js +1 -0
  84. package/dist/client/assets/git-rebase-r7XF79zn.js +1 -0
  85. package/dist/client/assets/github-dark-DHJKELXO.js +1 -0
  86. package/dist/client/assets/github-dark-default-Cuk6v7N8.js +1 -0
  87. package/dist/client/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  88. package/dist/client/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  89. package/dist/client/assets/github-light-DAi9KRSo.js +1 -0
  90. package/dist/client/assets/github-light-default-D7oLnXFd.js +1 -0
  91. package/dist/client/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  92. package/dist/client/assets/gleam-BspZqrRM.js +1 -0
  93. package/dist/client/assets/glimmer-js-Rg0-pVw9.js +1 -0
  94. package/dist/client/assets/glimmer-ts-U6CK756n.js +1 -0
  95. package/dist/client/assets/glsl-DplSGwfg.js +1 -0
  96. package/dist/client/assets/gn-n2N0HUVH.js +1 -0
  97. package/dist/client/assets/gnuplot-DdkO51Og.js +1 -0
  98. package/dist/client/assets/go-CxLEBnE3.js +1 -0
  99. package/dist/client/assets/graphql-ChdNCCLP.js +1 -0
  100. package/dist/client/assets/groovy-gcz8RCvz.js +1 -0
  101. package/dist/client/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  102. package/dist/client/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  103. package/dist/client/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  104. package/dist/client/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  105. package/dist/client/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  106. package/dist/client/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  107. package/dist/client/assets/hack-CaT9iCJl.js +1 -0
  108. package/dist/client/assets/haml-B8DHNrY2.js +1 -0
  109. package/dist/client/assets/handlebars-BL8al0AC.js +1 -0
  110. package/dist/client/assets/haskell-Df6bDoY_.js +1 -0
  111. package/dist/client/assets/haxe-CzTSHFRz.js +1 -0
  112. package/dist/client/assets/hcl-BWvSN4gD.js +1 -0
  113. package/dist/client/assets/hjson-D5-asLiD.js +1 -0
  114. package/dist/client/assets/hlsl-D3lLCCz7.js +1 -0
  115. package/dist/client/assets/horizon-BUw7H-hv.js +1 -0
  116. package/dist/client/assets/horizon-bright-Cn-bp-IR.js +1 -0
  117. package/dist/client/assets/houston-DnULxvSX.js +1 -0
  118. package/dist/client/assets/html-GMplVEZG.js +1 -0
  119. package/dist/client/assets/html-derivative-BFtXZ54Q.js +1 -0
  120. package/dist/client/assets/http-jrhK8wxY.js +1 -0
  121. package/dist/client/assets/hurl-irOxFIW8.js +1 -0
  122. package/dist/client/assets/hxml-Bvhsp5Yf.js +1 -0
  123. package/dist/client/assets/hy-DFXneXwc.js +1 -0
  124. package/dist/client/assets/imba-DGztddWO.js +1 -0
  125. package/dist/client/assets/index-B-8fHyaU.css +32 -0
  126. package/dist/client/assets/index-DrUC3AGz.js +2502 -0
  127. package/dist/client/assets/ini-BEwlwnbL.js +1 -0
  128. package/dist/client/assets/java-CylS5w8V.js +1 -0
  129. package/dist/client/assets/javascript-wDzz0qaB.js +1 -0
  130. package/dist/client/assets/jinja-4LBKfQ-Z.js +1 -0
  131. package/dist/client/assets/jison-wvAkD_A8.js +1 -0
  132. package/dist/client/assets/json-Cp-IABpG.js +1 -0
  133. package/dist/client/assets/json5-C9tS-k6U.js +1 -0
  134. package/dist/client/assets/jsonc-Des-eS-w.js +1 -0
  135. package/dist/client/assets/jsonl-DcaNXYhu.js +1 -0
  136. package/dist/client/assets/jsonnet-DFQXde-d.js +1 -0
  137. package/dist/client/assets/jssm-C2t-YnRu.js +1 -0
  138. package/dist/client/assets/jsx-g9-lgVsj.js +1 -0
  139. package/dist/client/assets/julia-CxzCAyBv.js +1 -0
  140. package/dist/client/assets/just-Cw27pwNe.js +1 -0
  141. package/dist/client/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  142. package/dist/client/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  143. package/dist/client/assets/kanagawa-wave-DWedfzmr.js +1 -0
  144. package/dist/client/assets/kdl-DV7GczEv.js +1 -0
  145. package/dist/client/assets/kotlin-BdnUsdx6.js +1 -0
  146. package/dist/client/assets/kusto-DZf3V79B.js +1 -0
  147. package/dist/client/assets/laserwave-DUszq2jm.js +1 -0
  148. package/dist/client/assets/latex-CWtU0Tv5.js +1 -0
  149. package/dist/client/assets/lean-BZvkOJ9d.js +1 -0
  150. package/dist/client/assets/less-B1dDrJ26.js +1 -0
  151. package/dist/client/assets/light-plus-B7mTdjB0.js +1 -0
  152. package/dist/client/assets/liquid-DYVedYrR.js +1 -0
  153. package/dist/client/assets/llvm-DjAJT7YJ.js +1 -0
  154. package/dist/client/assets/log-2UxHyX5q.js +1 -0
  155. package/dist/client/assets/logo-BtOb2qkB.js +1 -0
  156. package/dist/client/assets/lua-BaeVxFsk.js +1 -0
  157. package/dist/client/assets/luau-C-HG3fhB.js +1 -0
  158. package/dist/client/assets/make-CHLpvVh8.js +1 -0
  159. package/dist/client/assets/markdown-Cvjx9yec.js +1 -0
  160. package/dist/client/assets/marko-CnJfTvn9.js +1 -0
  161. package/dist/client/assets/material-theme-D5KoaKCx.js +1 -0
  162. package/dist/client/assets/material-theme-darker-BfHTSMKl.js +1 -0
  163. package/dist/client/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  164. package/dist/client/assets/material-theme-ocean-CyktbL80.js +1 -0
  165. package/dist/client/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  166. package/dist/client/assets/matlab-D7o27uSR.js +1 -0
  167. package/dist/client/assets/mdc-BMNejdWA.js +1 -0
  168. package/dist/client/assets/mdx-Cmh6b_Ma.js +1 -0
  169. package/dist/client/assets/mermaid-mWjccvbQ.js +1 -0
  170. package/dist/client/assets/min-dark-CafNBF8u.js +1 -0
  171. package/dist/client/assets/min-light-CTRr51gU.js +1 -0
  172. package/dist/client/assets/mipsasm-CKIfxQSi.js +1 -0
  173. package/dist/client/assets/mojo-rZm6bMo-.js +1 -0
  174. package/dist/client/assets/monokai-D4h5O-jR.js +1 -0
  175. package/dist/client/assets/moonbit-_H4v1dQx.js +1 -0
  176. package/dist/client/assets/move-IF9eRakj.js +1 -0
  177. package/dist/client/assets/narrat-DRg8JJMk.js +1 -0
  178. package/dist/client/assets/nextflow-Zz6hmt5N.js +1 -0
  179. package/dist/client/assets/nextflow-groovy-BeH2EWoN.js +1 -0
  180. package/dist/client/assets/nginx-BpAMiNFr.js +1 -0
  181. package/dist/client/assets/night-owl-C39BiMTA.js +1 -0
  182. package/dist/client/assets/night-owl-light-CMTm3GFP.js +1 -0
  183. package/dist/client/assets/nim-CVrawwO9.js +1 -0
  184. package/dist/client/assets/nix-CwoSXNpI.js +1 -0
  185. package/dist/client/assets/nord-Ddv68eIx.js +1 -0
  186. package/dist/client/assets/nushell-Cz2AlsmD.js +1 -0
  187. package/dist/client/assets/objective-c-DXmwc3jG.js +1 -0
  188. package/dist/client/assets/objective-cpp-CLxacb5B.js +1 -0
  189. package/dist/client/assets/ocaml-C0hk2d4L.js +1 -0
  190. package/dist/client/assets/odin-BBf5iR-q.js +1 -0
  191. package/dist/client/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  192. package/dist/client/assets/one-light-C3Wv6jpd.js +1 -0
  193. package/dist/client/assets/openscad-C4EeE6gA.js +1 -0
  194. package/dist/client/assets/pascal-D93ZcfNL.js +1 -0
  195. package/dist/client/assets/perl-C0TMdlhV.js +1 -0
  196. package/dist/client/assets/php-Dhbhpdrm.js +1 -0
  197. package/dist/client/assets/pierre-dark-DF2SEV7i.js +1 -0
  198. package/dist/client/assets/pierre-light-DOlZxES8.js +1 -0
  199. package/dist/client/assets/pkl-u5AG7uiY.js +1 -0
  200. package/dist/client/assets/plastic-3e1v2bzS.js +1 -0
  201. package/dist/client/assets/plsql-ChMvpjG-.js +1 -0
  202. package/dist/client/assets/po-BTJTHyun.js +1 -0
  203. package/dist/client/assets/poimandres-CS3Unz2-.js +1 -0
  204. package/dist/client/assets/polar-C0HS_06l.js +1 -0
  205. package/dist/client/assets/postcss-CXtECtnM.js +1 -0
  206. package/dist/client/assets/powerquery-CEu0bR-o.js +1 -0
  207. package/dist/client/assets/powershell-Dpen1YoG.js +1 -0
  208. package/dist/client/assets/prisma-Dd19v3D-.js +1 -0
  209. package/dist/client/assets/prolog-CbFg5uaA.js +1 -0
  210. package/dist/client/assets/proto-C7zT0LnQ.js +1 -0
  211. package/dist/client/assets/pug-CGlum2m_.js +1 -0
  212. package/dist/client/assets/puppet-BMWR74SV.js +1 -0
  213. package/dist/client/assets/purescript-CklMAg4u.js +1 -0
  214. package/dist/client/assets/python-B6aJPvgy.js +1 -0
  215. package/dist/client/assets/qml-3beO22l8.js +1 -0
  216. package/dist/client/assets/qmldir-C8lEn-DE.js +1 -0
  217. package/dist/client/assets/qss-IeuSbFQv.js +1 -0
  218. package/dist/client/assets/r-Dspwwk_N.js +1 -0
  219. package/dist/client/assets/racket-BqYA7rlc.js +1 -0
  220. package/dist/client/assets/raku-DXvB9xmW.js +1 -0
  221. package/dist/client/assets/razor-Uh8Bk_45.js +1 -0
  222. package/dist/client/assets/red-bN70gL4F.js +1 -0
  223. package/dist/client/assets/reg-C-SQnVFl.js +1 -0
  224. package/dist/client/assets/regexp-CDVJQ6XC.js +1 -0
  225. package/dist/client/assets/rel-C3B-1QV4.js +1 -0
  226. package/dist/client/assets/riscv-BM1_JUlF.js +1 -0
  227. package/dist/client/assets/ron-D8l8udqQ.js +1 -0
  228. package/dist/client/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  229. package/dist/client/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  230. package/dist/client/assets/rose-pine-qdsjHGoJ.js +1 -0
  231. package/dist/client/assets/rosmsg-BJDFO7_C.js +1 -0
  232. package/dist/client/assets/rst-BrH8l1NY.js +1 -0
  233. package/dist/client/assets/ruby-Dw2BHqvy.js +1 -0
  234. package/dist/client/assets/rust-B1yitclQ.js +1 -0
  235. package/dist/client/assets/sas-cz2c8ADy.js +1 -0
  236. package/dist/client/assets/sass-Cj5Yp3dK.js +1 -0
  237. package/dist/client/assets/scala-C151Ov-r.js +1 -0
  238. package/dist/client/assets/scheme-C98Dy4si.js +1 -0
  239. package/dist/client/assets/scss-OYdSNvt2.js +1 -0
  240. package/dist/client/assets/sdbl-DVxCFoDh.js +1 -0
  241. package/dist/client/assets/shaderlab-Dg9Lc6iA.js +1 -0
  242. package/dist/client/assets/shellscript-Yzrsuije.js +1 -0
  243. package/dist/client/assets/shellsession-BADoaaVG.js +1 -0
  244. package/dist/client/assets/slack-dark-BthQWCQV.js +1 -0
  245. package/dist/client/assets/slack-ochin-DqwNpetd.js +1 -0
  246. package/dist/client/assets/smalltalk-BERRCDM3.js +1 -0
  247. package/dist/client/assets/snazzy-light-Bw305WKR.js +1 -0
  248. package/dist/client/assets/solarized-dark-DXbdFlpD.js +1 -0
  249. package/dist/client/assets/solarized-light-L9t79GZl.js +1 -0
  250. package/dist/client/assets/solidity-rGO070M0.js +1 -0
  251. package/dist/client/assets/soy-Brmx7dQM.js +1 -0
  252. package/dist/client/assets/sparql-rVzFXLq3.js +1 -0
  253. package/dist/client/assets/splunk-BtCnVYZw.js +1 -0
  254. package/dist/client/assets/sql-BLtJtn59.js +1 -0
  255. package/dist/client/assets/ssh-config-_ykCGR6B.js +1 -0
  256. package/dist/client/assets/stata-BH5u7GGu.js +1 -0
  257. package/dist/client/assets/stylus-BEDo0Tqx.js +1 -0
  258. package/dist/client/assets/surrealql-Bq5Q-fJD.js +1 -0
  259. package/dist/client/assets/svelte-C_ipcX3V.js +1 -0
  260. package/dist/client/assets/swift-D82vCrfD.js +1 -0
  261. package/dist/client/assets/synthwave-84-CbfX1IO0.js +1 -0
  262. package/dist/client/assets/system-verilog-CnnmHF94.js +1 -0
  263. package/dist/client/assets/systemd-4A_iFExJ.js +1 -0
  264. package/dist/client/assets/talonscript-CkByrt1z.js +1 -0
  265. package/dist/client/assets/tasl-QIJgUcNo.js +1 -0
  266. package/dist/client/assets/tcl-dwOrl1Do.js +1 -0
  267. package/dist/client/assets/templ-P3uqSqPl.js +1 -0
  268. package/dist/client/assets/terraform-BETggiCN.js +1 -0
  269. package/dist/client/assets/tex-idrVyKtj.js +1 -0
  270. package/dist/client/assets/tokyo-night-hegEt444.js +1 -0
  271. package/dist/client/assets/toml-vGWfd6FD.js +1 -0
  272. package/dist/client/assets/ts-tags-zn1MmPIZ.js +1 -0
  273. package/dist/client/assets/tsv-B_m7g4N7.js +1 -0
  274. package/dist/client/assets/tsx-COt5Ahok.js +1 -0
  275. package/dist/client/assets/turtle-BsS91CYL.js +1 -0
  276. package/dist/client/assets/twig-DNn4PbVi.js +1 -0
  277. package/dist/client/assets/typescript-BPQ3VLAy.js +1 -0
  278. package/dist/client/assets/typespec-BGHnOYBU.js +1 -0
  279. package/dist/client/assets/typst-DHCkPAjA.js +1 -0
  280. package/dist/client/assets/v-BcVCzyr7.js +1 -0
  281. package/dist/client/assets/vala-CsfeWuGM.js +1 -0
  282. package/dist/client/assets/vb-D17OF-Vu.js +1 -0
  283. package/dist/client/assets/verilog-BQ8w6xss.js +1 -0
  284. package/dist/client/assets/vesper-DU1UobuO.js +1 -0
  285. package/dist/client/assets/vhdl-CeAyd5Ju.js +1 -0
  286. package/dist/client/assets/viml-CJc9bBzg.js +1 -0
  287. package/dist/client/assets/vitesse-black-Bkuqu6BP.js +1 -0
  288. package/dist/client/assets/vitesse-dark-D0r3Knsf.js +1 -0
  289. package/dist/client/assets/vitesse-light-CVO1_9PV.js +1 -0
  290. package/dist/client/assets/vue-DN_0RTcg.js +1 -0
  291. package/dist/client/assets/vue-html-AaS7Mt5G.js +1 -0
  292. package/dist/client/assets/vue-vine-CQOfvN7w.js +1 -0
  293. package/dist/client/assets/vyper-CDx5xZoG.js +1 -0
  294. package/dist/client/assets/wasm-CG6Dc4jp.js +1 -0
  295. package/dist/client/assets/wasm-MzD3tlZU.js +1 -0
  296. package/dist/client/assets/wenyan-BV7otONQ.js +1 -0
  297. package/dist/client/assets/wgsl-Dx-B1_4e.js +1 -0
  298. package/dist/client/assets/wikitext-BhOHFoWU.js +1 -0
  299. package/dist/client/assets/wit-5i3qLPDT.js +1 -0
  300. package/dist/client/assets/wolfram-lXgVvXCa.js +1 -0
  301. package/dist/client/assets/xml-sdJ4AIDG.js +1 -0
  302. package/dist/client/assets/xsl-CtQFsRM5.js +1 -0
  303. package/dist/client/assets/yaml-Buea-lGh.js +1 -0
  304. package/dist/client/assets/zenscript-DVFEvuxE.js +1 -0
  305. package/dist/client/assets/zig-VOosw3JB.js +1 -0
  306. package/dist/client/index.html +2 -2
  307. package/package.json +2 -1
  308. package/src/server/agent.test.ts +589 -1
  309. package/src/server/agent.ts +459 -22
  310. package/src/server/codex-app-server-protocol.ts +28 -0
  311. package/src/server/codex-app-server.test.ts +88 -0
  312. package/src/server/codex-app-server.ts +71 -1
  313. package/src/server/diff-store.test.ts +126 -0
  314. package/src/server/diff-store.ts +365 -0
  315. package/src/server/generate-commit-message.test.ts +59 -0
  316. package/src/server/generate-commit-message.ts +121 -0
  317. package/src/server/read-models.test.ts +1 -1
  318. package/src/server/read-models.ts +6 -1
  319. package/src/server/server.ts +60 -1
  320. package/src/server/terminal-manager.test.ts +37 -4
  321. package/src/server/uploads.ts +4 -0
  322. package/src/server/ws-router.test.ts +5 -5
  323. package/src/server/ws-router.ts +125 -7
  324. package/src/shared/protocol.ts +4 -0
  325. package/src/shared/types.ts +51 -0
  326. package/dist/client/assets/index-C952_xJD.css +0 -32
  327. package/dist/client/assets/index-lgfU0zw2.js +0 -606
@@ -0,0 +1,365 @@
1
+ import { mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises"
2
+ import { tmpdir } from "node:os"
3
+ import path from "node:path"
4
+ import type { ChatDiffFile, ChatDiffSnapshot } from "../shared/types"
5
+ import { generateCommitMessageDetailed } from "./generate-commit-message"
6
+ import { inferProjectFileContentType } from "./uploads"
7
+
8
+ interface StoredChatDiffState {
9
+ status: ChatDiffSnapshot["status"]
10
+ branchName?: string
11
+ files: ChatDiffFile[]
12
+ }
13
+
14
+ function createEmptyState(): StoredChatDiffState {
15
+ return {
16
+ status: "unknown",
17
+ branchName: undefined,
18
+ files: [],
19
+ }
20
+ }
21
+
22
+ function snapshotsEqual(left: StoredChatDiffState | undefined, right: StoredChatDiffState) {
23
+ if (!left) {
24
+ return right.status === "unknown" && right.files.length === 0
25
+ }
26
+ if (left.status !== right.status) return false
27
+ if (left.branchName !== right.branchName) return false
28
+ if (left.files.length !== right.files.length) return false
29
+ return left.files.every((file, index) => {
30
+ const other = right.files[index]
31
+ return Boolean(other)
32
+ && file.path === other.path
33
+ && file.changeType === other.changeType
34
+ && file.patch === other.patch
35
+ })
36
+ }
37
+
38
+ interface DirtyPathEntry {
39
+ path: string
40
+ previousPath?: string
41
+ changeType: ChatDiffFile["changeType"]
42
+ }
43
+
44
+ async function fileExists(filePath: string) {
45
+ try {
46
+ await stat(filePath)
47
+ return true
48
+ } catch {
49
+ return false
50
+ }
51
+ }
52
+
53
+ async function runGit(args: string[], cwd: string) {
54
+ const process = Bun.spawn(["git", "-C", cwd, ...args], {
55
+ stdout: "pipe",
56
+ stderr: "pipe",
57
+ })
58
+ const [stdout, stderr, exitCode] = await Promise.all([
59
+ new Response(process.stdout).text(),
60
+ new Response(process.stderr).text(),
61
+ process.exited,
62
+ ])
63
+
64
+ return {
65
+ stdout,
66
+ stderr,
67
+ exitCode,
68
+ }
69
+ }
70
+
71
+ async function resolveRepo(projectPath: string): Promise<{ repoRoot: string; baseCommit: string | null } | null> {
72
+ const topLevel = await runGit(["rev-parse", "--show-toplevel"], projectPath)
73
+ if (topLevel.exitCode !== 0) {
74
+ return null
75
+ }
76
+
77
+ const repoRoot = topLevel.stdout.trim()
78
+ const head = await runGit(["rev-parse", "--verify", "HEAD"], repoRoot)
79
+ return {
80
+ repoRoot,
81
+ baseCommit: head.exitCode === 0 ? head.stdout.trim() : null,
82
+ }
83
+ }
84
+
85
+ async function getBranchName(repoRoot: string) {
86
+ const symbolicRef = await runGit(["symbolic-ref", "--quiet", "--short", "HEAD"], repoRoot)
87
+ if (symbolicRef.exitCode === 0) {
88
+ return symbolicRef.stdout.trim()
89
+ }
90
+
91
+ const revParse = await runGit(["rev-parse", "--abbrev-ref", "HEAD"], repoRoot)
92
+ if (revParse.exitCode === 0) {
93
+ return revParse.stdout.trim()
94
+ }
95
+
96
+ return undefined
97
+ }
98
+
99
+ function parseStatusPaths(output: string): DirtyPathEntry[] {
100
+ const entries: DirtyPathEntry[] = []
101
+ for (const rawLine of output.split(/\r?\n/u)) {
102
+ const line = rawLine.trimEnd()
103
+ if (line.length < 4) continue
104
+ const statusCode = line.slice(0, 2)
105
+ const value = line.slice(3)
106
+ if (!value) continue
107
+ const isRename = statusCode.includes("R")
108
+ const isDelete = statusCode.includes("D")
109
+ const isAdd = statusCode.includes("A") || statusCode === "??"
110
+ const changeType: ChatDiffFile["changeType"] = isRename
111
+ ? "renamed"
112
+ : isDelete
113
+ ? "deleted"
114
+ : isAdd
115
+ ? "added"
116
+ : "modified"
117
+
118
+ if (isRename && value.includes(" -> ")) {
119
+ const [previousPath, nextPath] = value.split(" -> ")
120
+ if (nextPath) {
121
+ entries.push({
122
+ path: nextPath,
123
+ previousPath: previousPath || undefined,
124
+ changeType,
125
+ })
126
+ }
127
+ continue
128
+ }
129
+
130
+ entries.push({
131
+ path: value,
132
+ changeType,
133
+ })
134
+ }
135
+ return entries.sort((left, right) => left.path.localeCompare(right.path))
136
+ }
137
+
138
+ async function listDirtyPaths(repoRoot: string) {
139
+ const status = await runGit(["status", "--short", "--untracked-files=all"], repoRoot)
140
+ if (status.exitCode !== 0) {
141
+ throw new Error(status.stderr.trim() || "Failed to read git status")
142
+ }
143
+
144
+ const paths = parseStatusPaths(status.stdout)
145
+ return paths
146
+ }
147
+
148
+ async function readWorktreeFile(repoRoot: string, relativePath: string): Promise<string | null> {
149
+ const absolutePath = path.join(repoRoot, relativePath)
150
+ if (!(await fileExists(absolutePath))) {
151
+ return null
152
+ }
153
+
154
+ return await readFile(absolutePath, "utf8")
155
+ }
156
+
157
+ async function readBaseFile(repoRoot: string, baseCommit: string | null, relativePath: string): Promise<string | null> {
158
+ if (!baseCommit) {
159
+ return null
160
+ }
161
+
162
+ const result = await runGit(["show", `${baseCommit}:${relativePath}`], repoRoot)
163
+ if (result.exitCode !== 0) {
164
+ return null
165
+ }
166
+ return result.stdout
167
+ }
168
+
169
+ async function createPatch(beforePathLabel: string, afterPathLabel: string, beforeText: string | null, afterText: string | null) {
170
+ const tempDir = await mkdtemp(path.join(tmpdir(), "kanna-diff-"))
171
+ const beforePath = path.join(tempDir, "before")
172
+ const afterPath = path.join(tempDir, "after")
173
+
174
+ try {
175
+ await writeFile(beforePath, beforeText ?? "", "utf8")
176
+ await writeFile(afterPath, afterText ?? "", "utf8")
177
+
178
+ const result = await runGit(
179
+ [
180
+ "diff",
181
+ "--no-index",
182
+ "--no-ext-diff",
183
+ "--text",
184
+ "--unified=3",
185
+ "--src-prefix=a/",
186
+ "--dst-prefix=b/",
187
+ "before",
188
+ "after",
189
+ ],
190
+ tempDir
191
+ )
192
+
193
+ if (result.exitCode !== 0 && result.exitCode !== 1) {
194
+ throw new Error(result.stderr.trim() || `Failed to build patch for ${afterPathLabel}`)
195
+ }
196
+
197
+ return result.stdout
198
+ .replace("diff --git a/before b/after", `diff --git a/${beforePathLabel} b/${afterPathLabel}`)
199
+ .replace("--- a/before", `--- a/${beforePathLabel}`)
200
+ .replace("+++ b/after", `+++ b/${afterPathLabel}`)
201
+ } finally {
202
+ await rm(tempDir, { recursive: true, force: true })
203
+ }
204
+ }
205
+
206
+ async function computeCurrentFiles(repoRoot: string, baseCommit: string | null): Promise<ChatDiffFile[]> {
207
+ const currentDirtyPaths = await listDirtyPaths(repoRoot)
208
+ const files: ChatDiffFile[] = []
209
+
210
+ for (const entry of currentDirtyPaths) {
211
+ const relativePath = entry.path
212
+ const beforePath = entry.previousPath ?? relativePath
213
+ const beforeText = await readBaseFile(repoRoot, baseCommit, beforePath)
214
+ const afterText = await readWorktreeFile(repoRoot, relativePath)
215
+ const absolutePath = path.join(repoRoot, relativePath)
216
+ const fileInfo = await stat(absolutePath).catch(() => null)
217
+ const file = fileInfo?.isFile() ? Bun.file(absolutePath) : null
218
+ const mimeType = file ? inferProjectFileContentType(relativePath, file.type) : undefined
219
+ const size = fileInfo?.isFile() ? fileInfo.size : undefined
220
+
221
+ if (beforeText === afterText && entry.changeType !== "renamed") {
222
+ continue
223
+ }
224
+
225
+ const patch = await createPatch(beforePath, relativePath, beforeText, afterText)
226
+ files.push({
227
+ path: relativePath,
228
+ changeType: entry.changeType,
229
+ patch,
230
+ mimeType,
231
+ size,
232
+ })
233
+ }
234
+
235
+ return files
236
+ }
237
+
238
+ function normalizeRepoRelativePath(inputPath: string) {
239
+ const normalized = path.posix.normalize(inputPath.replaceAll("\\", "/")).replace(/^\.\/+/u, "")
240
+ if (!normalized || normalized === "." || normalized.startsWith("../") || normalized.includes("/../") || path.posix.isAbsolute(normalized)) {
241
+ throw new Error(`Invalid diff path: ${inputPath}`)
242
+ }
243
+ return normalized
244
+ }
245
+
246
+ export class DiffStore {
247
+ private readonly states = new Map<string, StoredChatDiffState>()
248
+
249
+ constructor(_: string) {}
250
+
251
+ async initialize() {}
252
+
253
+ getSnapshot(chatId: string): ChatDiffSnapshot {
254
+ const state = this.states.get(chatId) ?? createEmptyState()
255
+ return {
256
+ status: state.status,
257
+ branchName: state.branchName,
258
+ files: [...state.files],
259
+ }
260
+ }
261
+
262
+ async refreshSnapshot(chatId: string, projectPath: string) {
263
+ const repo = await resolveRepo(projectPath)
264
+ if (!repo) {
265
+ const nextState = {
266
+ status: "no_repo",
267
+ branchName: undefined,
268
+ files: [],
269
+ } satisfies StoredChatDiffState
270
+ const changed = !snapshotsEqual(this.states.get(chatId), nextState)
271
+ this.states.set(chatId, nextState)
272
+ return changed
273
+ }
274
+
275
+ const files = await computeCurrentFiles(repo.repoRoot, repo.baseCommit)
276
+ const branchName = await getBranchName(repo.repoRoot)
277
+ const nextState = {
278
+ status: "ready",
279
+ branchName,
280
+ files,
281
+ } satisfies StoredChatDiffState
282
+ const changed = !snapshotsEqual(this.states.get(chatId), nextState)
283
+ this.states.set(chatId, nextState)
284
+ return changed
285
+ }
286
+
287
+ async generateCommitMessage(args: {
288
+ projectPath: string
289
+ paths: string[]
290
+ }) {
291
+ const normalizedPaths = [...new Set(args.paths.map(normalizeRepoRelativePath))]
292
+ if (normalizedPaths.length === 0) {
293
+ throw new Error("Select at least one file")
294
+ }
295
+
296
+ const repo = await resolveRepo(args.projectPath)
297
+ if (!repo) {
298
+ throw new Error("Project is not in a git repository")
299
+ }
300
+
301
+ const files = await computeCurrentFiles(repo.repoRoot, repo.baseCommit)
302
+ const selectedFiles = normalizedPaths.map((selectedPath) => {
303
+ const file = files.find((candidate) => candidate.path === selectedPath)
304
+ if (!file) {
305
+ throw new Error(`File is no longer changed: ${selectedPath}`)
306
+ }
307
+ return file
308
+ })
309
+
310
+ const branchName = await getBranchName(repo.repoRoot)
311
+ return await generateCommitMessageDetailed({
312
+ cwd: repo.repoRoot,
313
+ branchName,
314
+ files: selectedFiles,
315
+ })
316
+ }
317
+
318
+ async commitFiles(args: {
319
+ chatId: string
320
+ projectPath: string
321
+ paths: string[]
322
+ summary: string
323
+ description?: string
324
+ }) {
325
+ const summary = args.summary.trim()
326
+ const description = args.description?.trim()
327
+ if (!summary) {
328
+ throw new Error("Commit summary is required")
329
+ }
330
+
331
+ const normalizedPaths = [...new Set(args.paths.map(normalizeRepoRelativePath))]
332
+ if (normalizedPaths.length === 0) {
333
+ throw new Error("Select at least one file to commit")
334
+ }
335
+
336
+ const repo = await resolveRepo(args.projectPath)
337
+ if (!repo) {
338
+ throw new Error("Project is not in a git repository")
339
+ }
340
+
341
+ const currentDirtyPaths = new Set((await listDirtyPaths(repo.repoRoot)).map((entry) => entry.path))
342
+ const missingPaths = normalizedPaths.filter((relativePath) => !currentDirtyPaths.has(relativePath))
343
+ if (missingPaths.length > 0) {
344
+ throw new Error(`File is no longer changed: ${missingPaths[0]}`)
345
+ }
346
+
347
+ const addResult = await runGit(["add", "--", ...normalizedPaths], repo.repoRoot)
348
+ if (addResult.exitCode !== 0) {
349
+ throw new Error(addResult.stderr.trim() || "Failed to stage selected files")
350
+ }
351
+
352
+ const commitArgs = ["commit", "--only", "-m", summary]
353
+ if (description) {
354
+ commitArgs.push("-m", description)
355
+ }
356
+ commitArgs.push("--", ...normalizedPaths)
357
+
358
+ const commitResult = await runGit(commitArgs, repo.repoRoot)
359
+ if (commitResult.exitCode !== 0) {
360
+ throw new Error(commitResult.stderr.trim() || commitResult.stdout.trim() || "Failed to commit selected files")
361
+ }
362
+
363
+ return await this.refreshSnapshot(args.chatId, args.projectPath)
364
+ }
365
+ }
@@ -0,0 +1,59 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { QuickResponseAdapter } from "./quick-response"
3
+ import { generateCommitMessageDetailed } from "./generate-commit-message"
4
+
5
+ describe("generateCommitMessageDetailed", () => {
6
+ test("returns sanitized generated subject and body", async () => {
7
+ const result = await generateCommitMessageDetailed(
8
+ {
9
+ cwd: "/tmp/project",
10
+ branchName: "feature/test",
11
+ files: [{
12
+ path: "app.ts",
13
+ changeType: "modified",
14
+ patch: "diff --git a/app.ts b/app.ts\n--- a/app.ts\n+++ b/app.ts\n@@\n-old\n+new\n",
15
+ }],
16
+ },
17
+ new QuickResponseAdapter({
18
+ runClaudeStructured: async () => ({
19
+ subject: " Add login UI.\nextra line",
20
+ body: " - wire form\n- add validation ",
21
+ }),
22
+ })
23
+ )
24
+
25
+ expect(result).toEqual({
26
+ subject: "Add login UI",
27
+ body: "- wire form\n- add validation",
28
+ usedFallback: false,
29
+ failureMessage: null,
30
+ })
31
+ })
32
+
33
+ test("falls back when providers fail", async () => {
34
+ const result = await generateCommitMessageDetailed(
35
+ {
36
+ cwd: "/tmp/project",
37
+ branchName: "feature/test",
38
+ files: [{
39
+ path: "src/feature.ts",
40
+ changeType: "modified",
41
+ patch: "diff --git a/src/feature.ts b/src/feature.ts\n",
42
+ }],
43
+ },
44
+ new QuickResponseAdapter({
45
+ runClaudeStructured: async () => {
46
+ throw new Error("Not authenticated")
47
+ },
48
+ runCodexStructured: async () => null,
49
+ })
50
+ )
51
+
52
+ expect(result).toEqual({
53
+ subject: "Update feature.ts",
54
+ body: "",
55
+ usedFallback: true,
56
+ failureMessage: "claude failed commit message generation: Not authenticated; codex returned no result for commit message generation",
57
+ })
58
+ })
59
+ })
@@ -0,0 +1,121 @@
1
+ import path from "node:path"
2
+ import type { ChatDiffFile } from "../shared/types"
3
+ import { QuickResponseAdapter } from "./quick-response"
4
+
5
+ const COMMIT_MESSAGE_SCHEMA = {
6
+ type: "object",
7
+ properties: {
8
+ subject: { type: "string" },
9
+ body: { type: "string" },
10
+ },
11
+ required: ["subject", "body"],
12
+ additionalProperties: false,
13
+ } as const
14
+
15
+ export interface GenerateCommitMessageResult {
16
+ subject: string
17
+ body: string
18
+ usedFallback: boolean
19
+ failureMessage: string | null
20
+ }
21
+
22
+ function summarizeFailures(failures: Array<{ provider: "claude" | "codex"; reason: string }>) {
23
+ if (failures.length === 0) return null
24
+ return failures.map((failure) => failure.reason).join("; ")
25
+ }
26
+
27
+ function limitText(value: string, maxLength: number) {
28
+ return value.length <= maxLength ? value : `${value.slice(0, maxLength).trimEnd()}\n...[truncated]`
29
+ }
30
+
31
+ function sanitizeSubject(value: unknown): string | null {
32
+ if (typeof value !== "string") return null
33
+ const normalized = (value.split(/\r?\n/u)[0] ?? "")
34
+ .replace(/\s+/g, " ")
35
+ .trim()
36
+ .replace(/[.]+$/u, "")
37
+ .slice(0, 72)
38
+ .trim()
39
+ return normalized.length > 0 ? normalized : null
40
+ }
41
+
42
+ function sanitizeBody(value: unknown): string {
43
+ if (typeof value !== "string") return ""
44
+ return value.trim()
45
+ }
46
+
47
+ function fallbackSubject(files: ChatDiffFile[]) {
48
+ if (files.length === 1) {
49
+ const fileName = path.posix.basename(files[0]?.path ?? "file")
50
+ const normalized = `Update ${fileName}`.replace(/\s+/g, " ").trim()
51
+ return normalized.slice(0, 72).trim()
52
+ }
53
+
54
+ return `Update ${files.length} files`
55
+ }
56
+
57
+ function buildCommitMessagePrompt(args: {
58
+ branchName?: string
59
+ files: ChatDiffFile[]
60
+ }) {
61
+ const fileList = args.files.map((file) => `${file.changeType}: ${file.path}`).join("\n")
62
+ const combinedPatch = args.files.map((file) => file.patch).join("\n\n")
63
+
64
+ return [
65
+ "Generate a concise git commit message for the selected changes.",
66
+ "Return JSON with keys: subject, body.",
67
+ "Rules:",
68
+ "- subject must be imperative, under 72 chars, and have no trailing period",
69
+ "- body may be an empty string or short bullet points",
70
+ "- capture the primary user-visible or developer-visible change",
71
+ "",
72
+ `Branch: ${args.branchName ?? "current branch"}`,
73
+ "",
74
+ "Selected files:",
75
+ limitText(fileList, 6_000),
76
+ "",
77
+ "Selected patch:",
78
+ limitText(combinedPatch, 40_000),
79
+ ].join("\n")
80
+ }
81
+
82
+ export async function generateCommitMessageDetailed(
83
+ args: {
84
+ cwd: string
85
+ branchName?: string
86
+ files: ChatDiffFile[]
87
+ },
88
+ adapter = new QuickResponseAdapter()
89
+ ): Promise<GenerateCommitMessageResult> {
90
+ const result = await adapter.generateStructuredWithDiagnostics<{ subject: string; body: string }>({
91
+ cwd: args.cwd,
92
+ task: "commit message generation",
93
+ prompt: buildCommitMessagePrompt(args),
94
+ schema: COMMIT_MESSAGE_SCHEMA,
95
+ parse: (value) => {
96
+ const output = value && typeof value === "object" ? value as { subject?: unknown; body?: unknown } : {}
97
+ const subject = sanitizeSubject(output.subject)
98
+ if (!subject) return null
99
+ return {
100
+ subject,
101
+ body: sanitizeBody(output.body),
102
+ }
103
+ },
104
+ })
105
+
106
+ if (result.value) {
107
+ return {
108
+ subject: result.value.subject,
109
+ body: result.value.body,
110
+ usedFallback: false,
111
+ failureMessage: null,
112
+ }
113
+ }
114
+
115
+ return {
116
+ subject: fallbackSubject(args.files),
117
+ body: "",
118
+ usedFallback: true,
119
+ failureMessage: summarizeFailures(result.failures),
120
+ }
121
+ }
@@ -54,7 +54,7 @@ describe("read models", () => {
54
54
  lastTurnOutcome: null,
55
55
  })
56
56
 
57
- const chat = deriveChatSnapshot(state, new Map(), "chat-1", () => [])
57
+ const chat = deriveChatSnapshot(state, new Map(), new Set(), "chat-1", () => [], () => ({ status: "unknown", files: [] }))
58
58
  expect(chat?.runtime.provider).toBe("claude")
59
59
  expect(chat?.availableProviders.length).toBeGreaterThan(1)
60
60
  expect(chat?.availableProviders.find((provider) => provider.id === "codex")?.models.map((model) => model.id)).toEqual([
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ ChatDiffSnapshot,
2
3
  ChatRuntime,
3
4
  ChatSnapshot,
4
5
  KannaStatus,
@@ -98,8 +99,10 @@ export function deriveLocalProjectsSnapshot(
98
99
  export function deriveChatSnapshot(
99
100
  state: StoreState,
100
101
  activeStatuses: Map<string, KannaStatus>,
102
+ drainingChatIds: Set<string>,
101
103
  chatId: string,
102
- getMessages: (chatId: string) => ChatSnapshot["messages"]
104
+ getMessages: (chatId: string) => ChatSnapshot["messages"],
105
+ getDiffs: (chatId: string) => ChatDiffSnapshot
103
106
  ): ChatSnapshot | null {
104
107
  const chat = state.chatsById.get(chatId)
105
108
  if (!chat || chat.deletedAt) return null
@@ -112,6 +115,7 @@ export function deriveChatSnapshot(
112
115
  localPath: project.localPath,
113
116
  title: chat.title,
114
117
  status: deriveStatus(chat, activeStatuses.get(chat.id)),
118
+ isDraining: drainingChatIds.has(chat.id),
115
119
  provider: chat.provider,
116
120
  planMode: chat.planMode,
117
121
  sessionToken: chat.sessionToken,
@@ -120,6 +124,7 @@ export function deriveChatSnapshot(
120
124
  return {
121
125
  runtime,
122
126
  messages: getMessages(chat.id),
127
+ diffs: getDiffs(chat.id),
123
128
  availableProviders: [...SERVER_PROVIDERS],
124
129
  }
125
130
  }
@@ -4,6 +4,7 @@ import { APP_NAME, getRuntimeProfile } from "../shared/branding"
4
4
  import type { ChatAttachment } from "../shared/types"
5
5
  import { EventStore } from "./event-store"
6
6
  import { AgentCoordinator } from "./agent"
7
+ import { DiffStore } from "./diff-store"
7
8
  import { discoverProjects, type DiscoveredProject } from "./discovery"
8
9
  import { KeybindingsManager } from "./keybindings"
9
10
  import { getMachineDisplayName } from "./machine-name"
@@ -11,7 +12,7 @@ import { TerminalManager } from "./terminal-manager"
11
12
  import { UpdateManager } from "./update-manager"
12
13
  import type { UpdateInstallAttemptResult } from "./cli-runtime"
13
14
  import { createWsRouter, type ClientState } from "./ws-router"
14
- import { deleteProjectUpload, inferAttachmentContentType, persistProjectUpload } from "./uploads"
15
+ import { deleteProjectUpload, inferAttachmentContentType, inferProjectFileContentType, persistProjectUpload } from "./uploads"
15
16
  import { getProjectUploadDir } from "./paths"
16
17
 
17
18
  const MAX_UPLOAD_FILES = 10
@@ -68,8 +69,10 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
68
69
  const hostname = options.host ?? "127.0.0.1"
69
70
  const strictPort = options.strictPort ?? false
70
71
  const store = new EventStore()
72
+ const diffStore = new DiffStore(store.dataDir)
71
73
  const machineDisplayName = getMachineDisplayName()
72
74
  await store.initialize()
75
+ await diffStore.initialize()
73
76
  await store.migrateLegacyTranscripts(options.onMigrationProgress)
74
77
  let discoveredProjects: DiscoveredProject[] = []
75
78
 
@@ -101,6 +104,7 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
101
104
  })
102
105
  router = createWsRouter({
103
106
  store,
107
+ diffStore,
104
108
  agent,
105
109
  terminals,
106
110
  keybindings,
@@ -127,6 +131,7 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
127
131
  const upgraded = serverInstance.upgrade(req, {
128
132
  data: {
129
133
  subscriptions: new Map(),
134
+ snapshotSignatures: new Map(),
130
135
  },
131
136
  })
132
137
  return upgraded ? undefined : new Response("WebSocket upgrade failed", { status: 400 })
@@ -151,6 +156,11 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
151
156
  return attachmentContentResponse
152
157
  }
153
158
 
159
+ const projectFileContentResponse = await handleProjectFileContent(req, url, store)
160
+ if (projectFileContentResponse) {
161
+ return projectFileContentResponse
162
+ }
163
+
154
164
  return serveStatic(distDir, url.pathname)
155
165
  },
156
166
  websocket: {
@@ -191,6 +201,7 @@ export async function startKannaServer(options: StartKannaServerOptions = {}) {
191
201
  return {
192
202
  port: actualPort,
193
203
  store,
204
+ diffStore,
194
205
  updateManager,
195
206
  stop: shutdown,
196
207
  }
@@ -289,6 +300,54 @@ async function handleAttachmentContent(req: Request, url: URL, store: EventStore
289
300
  })
290
301
  }
291
302
 
303
+ async function handleProjectFileContent(req: Request, url: URL, store: EventStore) {
304
+ const match = url.pathname.match(/^\/api\/projects\/([^/]+)\/files\/([^/]+)\/content$/)
305
+ if (!match) {
306
+ return null
307
+ }
308
+
309
+ if (req.method !== "GET") {
310
+ return new Response(null, {
311
+ status: 405,
312
+ headers: {
313
+ Allow: "GET",
314
+ },
315
+ })
316
+ }
317
+
318
+ const project = store.getProject(match[1])
319
+ if (!project) {
320
+ return Response.json({ error: "Project not found" }, { status: 404 })
321
+ }
322
+
323
+ const relativePath = path.posix.normalize(decodeURIComponent(match[2]).replaceAll("\\", "/"))
324
+ if (!relativePath || relativePath === "." || relativePath.startsWith("../") || relativePath.includes("/../") || path.posix.isAbsolute(relativePath)) {
325
+ return Response.json({ error: "Invalid project file path" }, { status: 400 })
326
+ }
327
+
328
+ const filePath = path.resolve(project.localPath, relativePath)
329
+ const projectRoot = path.resolve(project.localPath)
330
+ if (filePath !== projectRoot && !filePath.startsWith(`${projectRoot}${path.sep}`)) {
331
+ return Response.json({ error: "Invalid project file path" }, { status: 400 })
332
+ }
333
+
334
+ const file = Bun.file(filePath)
335
+ try {
336
+ const info = await stat(filePath)
337
+ if (!info.isFile()) {
338
+ return Response.json({ error: "File not found" }, { status: 404 })
339
+ }
340
+ } catch {
341
+ return Response.json({ error: "File not found" }, { status: 404 })
342
+ }
343
+
344
+ return new Response(file, {
345
+ headers: {
346
+ "Content-Type": inferProjectFileContentType(relativePath, file.type),
347
+ },
348
+ })
349
+ }
350
+
292
351
  async function handleProjectUploadDelete(req: Request, url: URL, store: EventStore) {
293
352
  if (req.method !== "DELETE") {
294
353
  return null