@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,187 @@
1
+ import { statSync } from "node:fs"
2
+ import { homedir } from "node:os"
3
+ import type { EventStore } from "./event-store"
4
+ import type { ChatRecord } from "./events"
5
+ import { mapClaudeRecordsToEntries } from "./claude-session-mapper"
6
+ import { scanClaudeSessions } from "./claude-session-scanner"
7
+ import type { ParsedClaudeSession } from "./claude-session-types"
8
+
9
+ export interface ImportClaudeSessionsResult {
10
+ imported: number // brand new sessions
11
+ updated: number // existing sessions whose hash changed; new messages appended
12
+ skipped: number // unchanged (hash match) or empty-entry sessions
13
+ failed: number // cwd missing or store error
14
+ newProjects: number
15
+ }
16
+
17
+ export interface ImportClaudeSessionsArgs {
18
+ store: EventStore
19
+ homeDir?: string
20
+ onProgress?: (update: { scanned: number; imported: number }) => void
21
+ }
22
+
23
+ function cwdExists(cwd: string): boolean {
24
+ if (!cwd) return false
25
+ try {
26
+ return statSync(cwd).isDirectory()
27
+ } catch {
28
+ return false
29
+ }
30
+ }
31
+
32
+ function extractUserText(content: unknown): string | null {
33
+ if (typeof content === "string") {
34
+ const trimmed = content.trim()
35
+ return trimmed ? trimmed : null
36
+ }
37
+ if (!Array.isArray(content)) return null
38
+ for (const block of content) {
39
+ if (!block || typeof block !== "object") continue
40
+ const blockRec = block as { type?: unknown; text?: unknown }
41
+ if (blockRec.type === "text" && typeof blockRec.text === "string") {
42
+ const trimmed = blockRec.text.trim()
43
+ if (trimmed) return trimmed
44
+ }
45
+ }
46
+ return null
47
+ }
48
+
49
+ function deriveTitle(session: ParsedClaudeSession): string {
50
+ for (const record of session.records) {
51
+ if (record.type !== "user") continue
52
+ const content = (record as { message?: { content?: unknown } }).message?.content
53
+ const text = extractUserText(content)
54
+ if (text) return text.slice(0, 60)
55
+ }
56
+ return "Imported session"
57
+ }
58
+
59
+ /**
60
+ * Extract the source record uuid from an entry _id.
61
+ * Mapper format: `${uuid}-user`, `${uuid}-text-<n>`, `${uuid}-tool_call-<n>`,
62
+ * `${uuid}-tool_result-<n>`. We match known trailing suffixes so that UUID v4
63
+ * values (which contain dashes) are not split incorrectly.
64
+ */
65
+ function extractUuidFromEntryId(entryId: string): string | null {
66
+ const match = entryId.match(/^(.+)-(?:user|text-\d+|tool_call-\d+|tool_result-\d+)$/)
67
+ return match ? match[1] : null
68
+ }
69
+
70
+ /**
71
+ * Collect the set of record uuids already stored for a chat.
72
+ * Entries with a random uuid prefix (records that had no uuid) will always
73
+ * be absent from any record.uuid lookup — assumed acceptable since real Claude
74
+ * sessions always include uuid.
75
+ */
76
+ function collectExistingUuids(store: EventStore, chatId: string): Set<string> {
77
+ const seen = new Set<string>()
78
+ for (const entry of store.getMessages(chatId)) {
79
+ const uuid = extractUuidFromEntryId(entry._id)
80
+ if (uuid) seen.add(uuid)
81
+ }
82
+ return seen
83
+ }
84
+
85
+ async function applyDelta(
86
+ store: EventStore,
87
+ chatId: string,
88
+ session: ParsedClaudeSession,
89
+ ): Promise<number> {
90
+ const seen = collectExistingUuids(store, chatId)
91
+ const newRecords = session.records.filter(
92
+ (record) => !record.uuid || !seen.has(record.uuid),
93
+ )
94
+ if (newRecords.length === 0) return 0
95
+
96
+ const entries = mapClaudeRecordsToEntries(newRecords)
97
+ for (const entry of entries) {
98
+ await store.appendMessage(chatId, entry)
99
+ }
100
+ return entries.length
101
+ }
102
+
103
+ export async function importClaudeSessions(
104
+ args: ImportClaudeSessionsArgs,
105
+ ): Promise<ImportClaudeSessionsResult> {
106
+ const { store, homeDir = homedir(), onProgress } = args
107
+ const sessions = scanClaudeSessions(homeDir)
108
+
109
+ let imported = 0
110
+ let updated = 0
111
+ let skipped = 0
112
+ let failed = 0
113
+ let newProjects = 0
114
+
115
+ let scanned = 0
116
+ for (const session of sessions) {
117
+ scanned += 1
118
+ if (onProgress) onProgress({ scanned, imported })
119
+
120
+ // Check if a chat already exists for this sessionId
121
+ let existingChat: ChatRecord | undefined
122
+ for (const chat of store.state.chatsById.values()) {
123
+ if (!chat.deletedAt && chat.sessionToken === session.sessionId) {
124
+ existingChat = chat
125
+ break
126
+ }
127
+ }
128
+
129
+ if (existingChat) {
130
+ // Hash match → nothing new to do
131
+ if (existingChat.sourceHash === session.sourceHash) {
132
+ skipped += 1
133
+ continue
134
+ }
135
+ // Hash changed → append only new records
136
+ try {
137
+ const appended = await applyDelta(store, existingChat.id, session)
138
+ if (appended > 0) {
139
+ updated += 1
140
+ } else {
141
+ skipped += 1
142
+ }
143
+ await store.setSourceHash(existingChat.id, session.sourceHash)
144
+ } catch (error) {
145
+ console.error("[kanna/import] failed to update session", session.filePath, error)
146
+ failed += 1
147
+ }
148
+ continue
149
+ }
150
+
151
+ // No existing chat — new import path
152
+ if (!cwdExists(session.cwd)) {
153
+ failed += 1
154
+ continue
155
+ }
156
+
157
+ const entries = mapClaudeRecordsToEntries(session.records)
158
+ if (entries.length === 0) {
159
+ skipped += 1
160
+ continue
161
+ }
162
+
163
+ try {
164
+ const projectBefore = store.state.projectIdsByPath.get(session.cwd)
165
+ const project = await store.openProject(session.cwd)
166
+ if (!projectBefore) newProjects += 1
167
+
168
+ const chat = await store.createChat(project.id)
169
+ await store.setChatProvider(chat.id, "claude")
170
+ await store.renameChat(chat.id, deriveTitle(session))
171
+
172
+ for (const entry of entries) {
173
+ await store.appendMessage(chat.id, entry)
174
+ }
175
+
176
+ await store.setSessionToken(chat.id, session.sessionId)
177
+ await store.setSourceHash(chat.id, session.sourceHash)
178
+ imported += 1
179
+ if (onProgress) onProgress({ scanned, imported })
180
+ } catch (error) {
181
+ console.error("[kanna/import] failed to import session", session.filePath, error)
182
+ failed += 1
183
+ }
184
+ }
185
+
186
+ return { imported, updated, skipped, failed, newProjects }
187
+ }
@@ -0,0 +1,88 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { mapClaudeRecordsToEntries } from "./claude-session-mapper"
3
+ import type { ClaudeSessionRecord } from "./claude-session-types"
4
+
5
+ describe("mapClaudeRecordsToEntries", () => {
6
+ const baseTs = "2026-04-20T10:00:00.000Z"
7
+
8
+ test("user message → user_prompt entry", () => {
9
+ const records: ClaudeSessionRecord[] = [
10
+ { type: "user", uuid: "u1", timestamp: baseTs, message: { role: "user", content: "hello" } },
11
+ ]
12
+ const entries = mapClaudeRecordsToEntries(records)
13
+ expect(entries.length).toBe(1)
14
+ expect(entries[0].kind).toBe("user_prompt")
15
+ if (entries[0].kind === "user_prompt") {
16
+ expect(entries[0].content).toBe("hello")
17
+ }
18
+ })
19
+
20
+ test("assistant text → assistant_text entry", () => {
21
+ const records: ClaudeSessionRecord[] = [
22
+ {
23
+ type: "assistant",
24
+ uuid: "a1",
25
+ timestamp: baseTs,
26
+ message: { role: "assistant", id: "m1", content: [{ type: "text", text: "hi" }] },
27
+ },
28
+ ]
29
+ const entries = mapClaudeRecordsToEntries(records)
30
+ expect(entries.length).toBe(1)
31
+ expect(entries[0].kind).toBe("assistant_text")
32
+ if (entries[0].kind === "assistant_text") {
33
+ expect(entries[0].text).toBe("hi")
34
+ }
35
+ })
36
+
37
+ test("assistant tool_use → tool_call entry with normalized Bash tool", () => {
38
+ const records: ClaudeSessionRecord[] = [
39
+ {
40
+ type: "assistant",
41
+ uuid: "a2",
42
+ timestamp: baseTs,
43
+ message: {
44
+ role: "assistant",
45
+ content: [{ type: "tool_use", id: "tu-1", name: "Bash", input: { command: "ls" } }],
46
+ },
47
+ },
48
+ ]
49
+ const entries = mapClaudeRecordsToEntries(records)
50
+ expect(entries.length).toBe(1)
51
+ expect(entries[0].kind).toBe("tool_call")
52
+ if (entries[0].kind === "tool_call") {
53
+ expect(entries[0].tool.toolKind).toBe("bash")
54
+ expect(entries[0].tool.toolId).toBe("tu-1")
55
+ }
56
+ })
57
+
58
+ test("user tool_result → tool_result entry", () => {
59
+ const records: ClaudeSessionRecord[] = [
60
+ {
61
+ type: "user",
62
+ uuid: "u1",
63
+ timestamp: baseTs,
64
+ message: {
65
+ role: "user",
66
+ content: [{ type: "tool_result", tool_use_id: "tu-1", content: "file1\nfile2" }],
67
+ },
68
+ },
69
+ ]
70
+ const entries = mapClaudeRecordsToEntries(records)
71
+ expect(entries.length).toBe(1)
72
+ expect(entries[0].kind).toBe("tool_result")
73
+ if (entries[0].kind === "tool_result") {
74
+ expect(entries[0].toolId).toBe("tu-1")
75
+ expect(entries[0].content).toBe("file1\nfile2")
76
+ }
77
+ })
78
+
79
+ test("skips summary and system records", () => {
80
+ const records: ClaudeSessionRecord[] = [
81
+ { type: "summary", summary: "x" },
82
+ { type: "system", content: "y" },
83
+ { type: "user", uuid: "u1", timestamp: baseTs, message: { role: "user", content: "hi" } },
84
+ ]
85
+ const entries = mapClaudeRecordsToEntries(records)
86
+ expect(entries.length).toBe(1)
87
+ })
88
+ })
@@ -0,0 +1,106 @@
1
+ import { normalizeToolCall } from "../shared/tools"
2
+ import type {
3
+ AssistantTextEntry,
4
+ ToolCallEntry,
5
+ ToolResultEntry,
6
+ TranscriptEntry,
7
+ UserPromptEntry,
8
+ } from "../shared/types"
9
+ import type {
10
+ ClaudeSessionAssistantRecord,
11
+ ClaudeSessionRecord,
12
+ ClaudeSessionUserRecord,
13
+ } from "./claude-session-types"
14
+
15
+ function toMillis(value: string | undefined): number {
16
+ if (!value) return Date.now()
17
+ const parsed = Date.parse(value)
18
+ return Number.isFinite(parsed) ? parsed : Date.now()
19
+ }
20
+
21
+ function makeId(uuid: string | undefined, suffix: string): string {
22
+ if (uuid) return `${uuid}-${suffix}`
23
+ return `${crypto.randomUUID()}-${suffix}`
24
+ }
25
+
26
+ function mapUserRecord(record: ClaudeSessionUserRecord): TranscriptEntry[] {
27
+ const createdAt = toMillis(record.timestamp)
28
+ const content = record.message.content
29
+
30
+ if (typeof content === "string") {
31
+ const entry: UserPromptEntry = {
32
+ _id: makeId(record.uuid, "user"),
33
+ kind: "user_prompt",
34
+ createdAt,
35
+ content,
36
+ }
37
+ return [entry]
38
+ }
39
+
40
+ const entries: TranscriptEntry[] = []
41
+ for (let i = 0; i < content.length; i += 1) {
42
+ const block = content[i]
43
+ if (block.type === "tool_result") {
44
+ const resultEntry: ToolResultEntry = {
45
+ _id: makeId(record.uuid, `tool_result-${i}`),
46
+ kind: "tool_result",
47
+ createdAt,
48
+ toolId: block.tool_use_id,
49
+ content: typeof block.content === "string" ? block.content : block.content ?? null,
50
+ isError: block.is_error === true,
51
+ }
52
+ entries.push(resultEntry)
53
+ }
54
+ }
55
+ return entries
56
+ }
57
+
58
+ function mapAssistantRecord(record: ClaudeSessionAssistantRecord): TranscriptEntry[] {
59
+ const createdAt = toMillis(record.timestamp)
60
+ const messageId = record.message.id
61
+
62
+ const entries: TranscriptEntry[] = []
63
+ for (let i = 0; i < record.message.content.length; i += 1) {
64
+ const block = record.message.content[i]
65
+ if (block.type === "text") {
66
+ const entry: AssistantTextEntry = {
67
+ _id: makeId(record.uuid, `text-${i}`),
68
+ messageId,
69
+ kind: "assistant_text",
70
+ createdAt,
71
+ text: block.text,
72
+ }
73
+ entries.push(entry)
74
+ continue
75
+ }
76
+ if (block.type === "tool_use") {
77
+ const tool = normalizeToolCall({
78
+ toolName: block.name,
79
+ toolId: block.id,
80
+ input: block.input ?? {},
81
+ })
82
+ const entry: ToolCallEntry = {
83
+ _id: makeId(record.uuid, `tool_call-${i}`),
84
+ messageId,
85
+ kind: "tool_call",
86
+ createdAt,
87
+ tool,
88
+ }
89
+ entries.push(entry)
90
+ }
91
+ }
92
+ return entries
93
+ }
94
+
95
+ export function mapClaudeRecordsToEntries(records: ClaudeSessionRecord[]): TranscriptEntry[] {
96
+ const entries: TranscriptEntry[] = []
97
+ for (const record of records) {
98
+ if (record.type === "user") {
99
+ entries.push(...mapUserRecord(record as ClaudeSessionUserRecord))
100
+ } else if (record.type === "assistant") {
101
+ entries.push(...mapAssistantRecord(record as ClaudeSessionAssistantRecord))
102
+ }
103
+ // summary / system / other: skipped
104
+ }
105
+ return entries
106
+ }
@@ -0,0 +1,38 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import path from "node:path"
3
+ import { parseClaudeSessionFile } from "./claude-session-parser"
4
+
5
+ const FIXTURE_DIR = path.join(__dirname, "__fixtures__")
6
+
7
+ describe("parseClaudeSessionFile", () => {
8
+ test("parses valid session with user, assistant, tool_use, tool_result", () => {
9
+ const parsed = parseClaudeSessionFile(path.join(FIXTURE_DIR, "claude-session-valid.jsonl"))
10
+ expect(parsed).not.toBeNull()
11
+ if (!parsed) return
12
+ expect(parsed.sessionId).toBe("sess-abc")
13
+ expect(parsed.cwd).toBe("/tmp/kanna-test-proj")
14
+ expect(parsed.records.length).toBe(6)
15
+ expect(parsed.firstTimestamp).toBeGreaterThan(0)
16
+ expect(parsed.lastTimestamp).toBeGreaterThanOrEqual(parsed.firstTimestamp)
17
+ expect(typeof parsed.sourceHash).toBe("string")
18
+ expect(parsed.sourceHash.length).toBe(32) // md5 hex = 32 chars
19
+ })
20
+
21
+ test("skips malformed lines, keeps valid ones", () => {
22
+ const parsed = parseClaudeSessionFile(path.join(FIXTURE_DIR, "claude-session-malformed.jsonl"))
23
+ expect(parsed).not.toBeNull()
24
+ if (!parsed) return
25
+ expect(parsed.records.length).toBe(2)
26
+ expect(parsed.sessionId).toBe("sess-bad")
27
+ })
28
+
29
+ test("returns null for empty file", () => {
30
+ const parsed = parseClaudeSessionFile(path.join(FIXTURE_DIR, "claude-session-empty.jsonl"))
31
+ expect(parsed).toBeNull()
32
+ })
33
+
34
+ test("returns null for missing file", () => {
35
+ const parsed = parseClaudeSessionFile(path.join(FIXTURE_DIR, "does-not-exist.jsonl"))
36
+ expect(parsed).toBeNull()
37
+ })
38
+ })
@@ -0,0 +1,67 @@
1
+ import { createHash } from "node:crypto"
2
+ import { readFileSync, statSync } from "node:fs"
3
+ import type { ClaudeSessionRecord, ParsedClaudeSession } from "./claude-session-types"
4
+
5
+ function tryParse(line: string): ClaudeSessionRecord | null {
6
+ try {
7
+ const parsed = JSON.parse(line)
8
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null
9
+ if (typeof (parsed as ClaudeSessionRecord).type !== "string") return null
10
+ return parsed as ClaudeSessionRecord
11
+ } catch {
12
+ return null
13
+ }
14
+ }
15
+
16
+ export function parseClaudeSessionFile(filePath: string): ParsedClaudeSession | null {
17
+ let raw: string
18
+ try {
19
+ raw = readFileSync(filePath, "utf8")
20
+ } catch {
21
+ return null
22
+ }
23
+ const sourceHash = createHash("md5").update(raw).digest("hex")
24
+
25
+ const records: ClaudeSessionRecord[] = []
26
+ let sessionId: string | null = null
27
+ let cwd: string | null = null
28
+ let first = Number.POSITIVE_INFINITY
29
+ let last = Number.NEGATIVE_INFINITY
30
+
31
+ for (const line of raw.split("\n")) {
32
+ const trimmed = line.trim()
33
+ if (!trimmed) continue
34
+ const record = tryParse(trimmed)
35
+ if (!record) continue
36
+
37
+ if (!sessionId && typeof record.sessionId === "string") sessionId = record.sessionId
38
+ if (!cwd && typeof record.cwd === "string") cwd = record.cwd
39
+
40
+ const ts = typeof record.timestamp === "string" ? Date.parse(record.timestamp) : Number.NaN
41
+ if (!Number.isNaN(ts)) {
42
+ if (ts < first) first = ts
43
+ if (ts > last) last = ts
44
+ }
45
+
46
+ records.push(record)
47
+ }
48
+
49
+ if (!sessionId) return null
50
+ if (records.length === 0) return null
51
+
52
+ let mtime: number
53
+ try {
54
+ mtime = statSync(filePath).mtimeMs
55
+ } catch {
56
+ mtime = Date.now()
57
+ }
58
+ return {
59
+ sessionId,
60
+ filePath,
61
+ cwd: cwd ?? "",
62
+ firstTimestamp: Number.isFinite(first) ? first : mtime,
63
+ lastTimestamp: Number.isFinite(last) ? last : mtime,
64
+ records,
65
+ sourceHash,
66
+ }
67
+ }
@@ -0,0 +1,49 @@
1
+ import { describe, expect, test } from "bun:test"
2
+ import { mkdirSync, mkdtempSync, writeFileSync, rmSync } from "node:fs"
3
+ import { tmpdir } from "node:os"
4
+ import path from "node:path"
5
+ import { scanClaudeSessions } from "./claude-session-scanner"
6
+
7
+ function makeTempClaudeHome(): { home: string; cleanup: () => void } {
8
+ const home = mkdtempSync(path.join(tmpdir(), "kanna-claude-home-"))
9
+ return { home, cleanup: () => rmSync(home, { recursive: true, force: true }) }
10
+ }
11
+
12
+ describe("scanClaudeSessions", () => {
13
+ test("returns empty list when ~/.claude/projects missing", () => {
14
+ const { home, cleanup } = makeTempClaudeHome()
15
+ try {
16
+ expect(scanClaudeSessions(home)).toEqual([])
17
+ } finally {
18
+ cleanup()
19
+ }
20
+ })
21
+
22
+ test("discovers session files inside project folders", () => {
23
+ const { home, cleanup } = makeTempClaudeHome()
24
+ try {
25
+ const realProj = mkdtempSync(path.join(tmpdir(), "kanna-proj-"))
26
+ const folderName = realProj.replace(/\//g, "-")
27
+ const projDir = path.join(home, ".claude", "projects", folderName)
28
+ mkdirSync(projDir, { recursive: true })
29
+ const sessionPath = path.join(projDir, "sess-abc.jsonl")
30
+ const line = JSON.stringify({
31
+ type: "user",
32
+ uuid: "u1",
33
+ sessionId: "sess-abc",
34
+ cwd: realProj,
35
+ timestamp: "2026-04-20T10:00:00.000Z",
36
+ message: { role: "user", content: "hi" },
37
+ })
38
+ writeFileSync(sessionPath, `${line}\n`, "utf8")
39
+
40
+ const sessions = scanClaudeSessions(home)
41
+ expect(sessions.length).toBe(1)
42
+ expect(sessions[0].sessionId).toBe("sess-abc")
43
+ expect(sessions[0].filePath).toBe(sessionPath)
44
+ rmSync(realProj, { recursive: true, force: true })
45
+ } finally {
46
+ cleanup()
47
+ }
48
+ })
49
+ })
@@ -0,0 +1,24 @@
1
+ import { existsSync, readdirSync } from "node:fs"
2
+ import { homedir } from "node:os"
3
+ import path from "node:path"
4
+ import type { ParsedClaudeSession } from "./claude-session-types"
5
+ import { parseClaudeSessionFile } from "./claude-session-parser"
6
+
7
+ export function scanClaudeSessions(homeDir: string = homedir()): ParsedClaudeSession[] {
8
+ const projectsDir = path.join(homeDir, ".claude", "projects")
9
+ if (!existsSync(projectsDir)) return []
10
+
11
+ const sessions: ParsedClaudeSession[] = []
12
+ for (const entry of readdirSync(projectsDir, { withFileTypes: true })) {
13
+ if (!entry.isDirectory()) continue
14
+ const projDir = path.join(projectsDir, entry.name)
15
+
16
+ for (const file of readdirSync(projDir, { withFileTypes: true })) {
17
+ if (!file.isFile() || !file.name.endsWith(".jsonl")) continue
18
+ const parsed = parseClaudeSessionFile(path.join(projDir, file.name))
19
+ if (parsed) sessions.push(parsed)
20
+ }
21
+ }
22
+
23
+ return sessions
24
+ }
@@ -0,0 +1,61 @@
1
+ // src/server/claude-session-types.ts
2
+
3
+ export interface ClaudeSessionRecordBase {
4
+ type: string
5
+ uuid?: string
6
+ parentUuid?: string | null
7
+ sessionId?: string
8
+ timestamp?: string
9
+ cwd?: string
10
+ version?: string
11
+ }
12
+
13
+ export interface ClaudeSessionUserRecord extends ClaudeSessionRecordBase {
14
+ type: "user"
15
+ message: {
16
+ role: "user"
17
+ content: string | Array<
18
+ | { type: "text"; text: string }
19
+ | { type: "tool_result"; tool_use_id: string; content?: unknown; is_error?: boolean }
20
+ >
21
+ }
22
+ }
23
+
24
+ export interface ClaudeSessionAssistantRecord extends ClaudeSessionRecordBase {
25
+ type: "assistant"
26
+ message: {
27
+ role: "assistant"
28
+ id?: string
29
+ content: Array<
30
+ | { type: "text"; text: string }
31
+ | { type: "tool_use"; id: string; name: string; input: Record<string, unknown> }
32
+ >
33
+ }
34
+ }
35
+
36
+ export interface ClaudeSessionSummaryRecord extends ClaudeSessionRecordBase {
37
+ type: "summary"
38
+ summary?: string
39
+ }
40
+
41
+ export interface ClaudeSessionSystemRecord extends ClaudeSessionRecordBase {
42
+ type: "system"
43
+ content?: string
44
+ }
45
+
46
+ export type ClaudeSessionRecord =
47
+ | ClaudeSessionUserRecord
48
+ | ClaudeSessionAssistantRecord
49
+ | ClaudeSessionSummaryRecord
50
+ | ClaudeSessionSystemRecord
51
+ | ClaudeSessionRecordBase
52
+
53
+ export interface ParsedClaudeSession {
54
+ sessionId: string
55
+ filePath: string
56
+ cwd: string
57
+ firstTimestamp: number
58
+ lastTimestamp: number
59
+ records: ClaudeSessionRecord[]
60
+ sourceHash: string
61
+ }