@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,315 @@
1
+ import { afterAll, afterEach, beforeAll, describe, expect, test } from "bun:test"
2
+ import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises"
3
+ import os from "node:os"
4
+ import path from "node:path"
5
+ import { TerminalManager } from "./terminal-manager"
6
+
7
+ const SHELL_START_TIMEOUT_MS = 5_000
8
+ const COMMAND_TIMEOUT_MS = 5_000
9
+ const FOCUS_IN_SEQUENCE = "\x1b[I"
10
+ const RAW_READ_HEX_COMMAND = `python3 -c "exec('import os,sys,tty,termios,select\\nfd=sys.stdin.fileno()\\nold=termios.tcgetattr(fd)\\ntty.setraw(fd)\\ntry:\\n sys.stdout.write(\"__RAW_READY__\\\\n\")\\n sys.stdout.flush()\\n r,_,_=select.select([fd],[],[],1)\\n data=os.read(fd,8) if r else b\"\"\\n print(data.hex() or \"__EMPTY__\")\\nfinally:\\n termios.tcsetattr(fd, termios.TCSADRAIN, old)')"\r`
11
+
12
+ const isSupportedPlatform = process.platform !== "win32" && typeof Bun.Terminal === "function"
13
+ const describeIfSupported = isSupportedPlatform ? describe : describe.skip
14
+
15
+ let tempProjectPath = ""
16
+ let tempHomePath = ""
17
+ const originalHome = process.env.HOME
18
+ const originalZdotdir = process.env.ZDOTDIR
19
+ const originalHistfile = process.env.HISTFILE
20
+
21
+ beforeAll(async () => {
22
+ if (!isSupportedPlatform) return
23
+ tempProjectPath = await mkdtemp(path.join(os.tmpdir(), "kanna-terminal-manager-"))
24
+ tempHomePath = await mkdtemp(path.join(os.tmpdir(), "kanna-terminal-home-"))
25
+ await mkdir(path.join(tempHomePath, ".config"), { recursive: true })
26
+ // Create a minimal .zshrc to prevent the zsh-newuser-install interactive dialog
27
+ // from running on first login (which would intercept test input).
28
+ await writeFile(path.join(tempHomePath, ".zshrc"), "# minimal test config\n", "utf8")
29
+ process.env.HOME = tempHomePath
30
+ process.env.ZDOTDIR = tempHomePath
31
+ process.env.HISTFILE = path.join(tempHomePath, ".zsh_history")
32
+ })
33
+
34
+ afterEach(async () => {
35
+ if (!tempProjectPath) return
36
+ await rm(tempProjectPath, { recursive: true, force: true })
37
+ tempProjectPath = await mkdtemp(path.join(os.tmpdir(), "kanna-terminal-manager-"))
38
+ })
39
+
40
+ afterAll(async () => {
41
+ if (originalHome === undefined) {
42
+ delete process.env.HOME
43
+ } else {
44
+ process.env.HOME = originalHome
45
+ }
46
+
47
+ if (originalZdotdir === undefined) {
48
+ delete process.env.ZDOTDIR
49
+ } else {
50
+ process.env.ZDOTDIR = originalZdotdir
51
+ }
52
+
53
+ if (originalHistfile === undefined) {
54
+ delete process.env.HISTFILE
55
+ } else {
56
+ process.env.HISTFILE = originalHistfile
57
+ }
58
+
59
+ if (tempHomePath) {
60
+ await rm(tempHomePath, { recursive: true, force: true })
61
+ }
62
+ })
63
+
64
+ async function waitFor(check: () => boolean, timeoutMs: number, intervalMs = 25) {
65
+ const startedAt = Date.now()
66
+ while (Date.now() - startedAt < timeoutMs) {
67
+ if (check()) return
68
+ await Bun.sleep(intervalMs)
69
+ }
70
+ throw new Error(`Timed out after ${timeoutMs}ms`)
71
+ }
72
+
73
+ async function createSession(terminalId: string) {
74
+ const manager = new TerminalManager()
75
+ let output = ""
76
+ manager.onEvent((event) => {
77
+ if (event.type === "terminal.output" && event.terminalId === terminalId) {
78
+ output += event.data
79
+ }
80
+ })
81
+
82
+ manager.createTerminal({
83
+ projectPath: tempProjectPath,
84
+ terminalId,
85
+ cols: 80,
86
+ rows: 24,
87
+ scrollback: 1_000,
88
+ })
89
+
90
+ manager.write(terminalId, "printf '__KANNA_READY__\\n'\r")
91
+ // Wait for the actual command output (the bare string on its own line: __KANNA_READY__\r\n)
92
+ // rather than the terminal echo of the input (which also contains __KANNA_READY__ inside quotes).
93
+ await waitFor(() => output.includes("__KANNA_READY__\r\n"), SHELL_START_TIMEOUT_MS)
94
+
95
+ return {
96
+ manager,
97
+ getOutput: () => output,
98
+ }
99
+ }
100
+
101
+ async function waitForOutputToContain(getOutput: () => string, value: string, timeoutMs = COMMAND_TIMEOUT_MS) {
102
+ await waitFor(() => getOutput().includes(value), timeoutMs)
103
+ }
104
+
105
+ describeIfSupported("TerminalManager", () => {
106
+ test("ctrl+c interrupts the foreground job and keeps the shell alive", async () => {
107
+ const terminalId = "terminal-ctrl-c-foreground"
108
+ const { manager, getOutput } = await createSession(terminalId)
109
+
110
+ try {
111
+ manager.write(terminalId, `python3 -c "import time; print('__KANNA_SLEEP__', flush=True); time.sleep(30)"\r`)
112
+ await waitFor(() => getOutput().includes("__KANNA_SLEEP__"), COMMAND_TIMEOUT_MS)
113
+
114
+ manager.write(terminalId, "\x03")
115
+ manager.write(terminalId, "printf '__KANNA_AFTER_INT__\\n'\r")
116
+
117
+ await waitFor(() => getOutput().includes("__KANNA_AFTER_INT__"), COMMAND_TIMEOUT_MS)
118
+
119
+ const snapshot = manager.getSnapshot(terminalId)
120
+ expect(snapshot?.status).toBe("running")
121
+ expect(getOutput()).toContain("__KANNA_AFTER_INT__")
122
+ } finally {
123
+ manager.close(terminalId)
124
+ }
125
+ })
126
+
127
+ test("ctrl+c at an idle prompt does not exit the shell", async () => {
128
+ const terminalId = "terminal-ctrl-c-prompt"
129
+ const { manager, getOutput } = await createSession(terminalId)
130
+
131
+ try {
132
+ const before = getOutput()
133
+ manager.write(terminalId, "\x03")
134
+
135
+ await waitFor(() => getOutput().length > before.length, COMMAND_TIMEOUT_MS)
136
+
137
+ const snapshot = manager.getSnapshot(terminalId)
138
+ expect(snapshot?.status).toBe("running")
139
+ expect(getOutput().length).toBeGreaterThan(before.length)
140
+ } finally {
141
+ manager.close(terminalId)
142
+ }
143
+ })
144
+
145
+ test("ctrl+d preserves eof behavior", async () => {
146
+ const terminalId = "terminal-ctrl-d"
147
+ const { manager } = await createSession(terminalId)
148
+
149
+ try {
150
+ manager.write(terminalId, "\x04")
151
+
152
+ await waitFor(() => manager.getSnapshot(terminalId)?.status === "exited", COMMAND_TIMEOUT_MS)
153
+
154
+ expect(manager.getSnapshot(terminalId)?.exitCode).toBe(0)
155
+ } finally {
156
+ manager.close(terminalId)
157
+ }
158
+ })
159
+
160
+ test("filters leaked focus reports while focus mode is disabled", async () => {
161
+ const terminalId = "terminal-focus-filtered"
162
+ const { manager, getOutput } = await createSession(terminalId)
163
+
164
+ try {
165
+ const beforeLength = getOutput().length
166
+ manager.write(terminalId, RAW_READ_HEX_COMMAND)
167
+ await waitForOutputToContain(getOutput, "__RAW_READY__")
168
+
169
+ manager.write(terminalId, FOCUS_IN_SEQUENCE)
170
+ await waitForOutputToContain(getOutput, "__EMPTY__")
171
+
172
+ const interactionOutput = getOutput().slice(beforeLength)
173
+ expect(interactionOutput).toContain("__EMPTY__")
174
+ expect(interactionOutput).not.toContain("1b5b49")
175
+ } finally {
176
+ manager.close(terminalId)
177
+ }
178
+ })
179
+
180
+ test("forwards focus reports when the session mode is enabled", () => {
181
+ const manager = new TerminalManager() as unknown as {
182
+ sessions: Map<
183
+ string,
184
+ {
185
+ status: "running" | "exited"
186
+ focusReportingEnabled: boolean
187
+ terminal: { write: (data: string) => void }
188
+ process: Bun.Subprocess | null
189
+ }
190
+ >
191
+ write: (terminalId: string, data: string) => void
192
+ }
193
+ const writes: string[] = []
194
+
195
+ manager.sessions.set("terminal-focus-forwarded", {
196
+ status: "running",
197
+ focusReportingEnabled: true,
198
+ terminal: {
199
+ write(data: string) {
200
+ writes.push(data)
201
+ },
202
+ },
203
+ process: null,
204
+ })
205
+
206
+ manager.write("terminal-focus-forwarded", FOCUS_IN_SEQUENCE)
207
+
208
+ expect(writes).toEqual([FOCUS_IN_SEQUENCE])
209
+ })
210
+
211
+ test("resize signals the shell process group with SIGWINCH", () => {
212
+ const manager = new TerminalManager() as unknown as {
213
+ sessions: Map<
214
+ string,
215
+ {
216
+ cols: number
217
+ rows: number
218
+ headless: { resize: (cols: number, rows: number) => void }
219
+ terminal: { resize: (cols: number, rows: number) => void }
220
+ process: { pid: number } | null
221
+ }
222
+ >
223
+ resize: (terminalId: string, cols: number, rows: number) => void
224
+ }
225
+ const resizeCalls: Array<{ cols: number; rows: number }> = []
226
+ const killCalls: Array<{ pid: number; signal: NodeJS.Signals }> = []
227
+ const originalKill = process.kill
228
+
229
+ ;(process as typeof process & {
230
+ kill: (pid: number, signal?: NodeJS.Signals | number) => boolean
231
+ }).kill = ((pid: number, signal?: NodeJS.Signals | number) => {
232
+ if (typeof signal === "string") {
233
+ killCalls.push({ pid, signal })
234
+ }
235
+ return true
236
+ }) as typeof process.kill
237
+
238
+ manager.sessions.set("terminal-resize-sigwinch", {
239
+ cols: 80,
240
+ rows: 24,
241
+ headless: {
242
+ resize(cols, rows) {
243
+ resizeCalls.push({ cols, rows })
244
+ },
245
+ },
246
+ terminal: {
247
+ resize(cols, rows) {
248
+ resizeCalls.push({ cols, rows })
249
+ },
250
+ },
251
+ process: { pid: 4321 },
252
+ })
253
+
254
+ try {
255
+ manager.resize("terminal-resize-sigwinch", 120, 40)
256
+ } finally {
257
+ process.kill = originalKill
258
+ }
259
+
260
+ expect(resizeCalls).toEqual([
261
+ { cols: 120, rows: 40 },
262
+ { cols: 120, rows: 40 },
263
+ ])
264
+ expect(killCalls).toContainEqual({ pid: -4321, signal: "SIGWINCH" })
265
+ })
266
+
267
+ test("new sessions reset focus mode back to filtered", async () => {
268
+ const manager = new TerminalManager()
269
+ const firstTerminalId = "terminal-focus-first"
270
+ const secondTerminalId = "terminal-focus-second"
271
+ let outputByTerminalId = new Map<string, string>()
272
+
273
+ manager.onEvent((event) => {
274
+ if (event.type !== "terminal.output") return
275
+ outputByTerminalId.set(event.terminalId, `${outputByTerminalId.get(event.terminalId) ?? ""}${event.data}`)
276
+ })
277
+
278
+ const getOutput = (terminalId: string) => outputByTerminalId.get(terminalId) ?? ""
279
+
280
+ const createManagedSession = async (terminalId: string) => {
281
+ manager.createTerminal({
282
+ projectPath: tempProjectPath,
283
+ terminalId,
284
+ cols: 80,
285
+ rows: 24,
286
+ scrollback: 1_000,
287
+ })
288
+ manager.write(terminalId, "printf '__KANNA_READY__\\n'\r")
289
+ await waitForOutputToContain(() => getOutput(terminalId), "__KANNA_READY__", SHELL_START_TIMEOUT_MS)
290
+ }
291
+
292
+ try {
293
+ await createManagedSession(firstTerminalId)
294
+ const firstBeforeLength = getOutput(firstTerminalId).length
295
+ manager.write(firstTerminalId, "printf '\\033[?1004h'\r")
296
+ await waitFor(() => getOutput(firstTerminalId).length > firstBeforeLength, COMMAND_TIMEOUT_MS)
297
+ manager.close(firstTerminalId)
298
+
299
+ await createManagedSession(secondTerminalId)
300
+ const before = getOutput(secondTerminalId).length
301
+ manager.write(secondTerminalId, "cat -v\r")
302
+ await waitFor(() => getOutput(secondTerminalId).length > before, COMMAND_TIMEOUT_MS)
303
+ manager.write(secondTerminalId, FOCUS_IN_SEQUENCE)
304
+ manager.write(secondTerminalId, "\x03")
305
+ manager.write(secondTerminalId, "printf '__KANNA_FRESH_SESSION__\\n'\r")
306
+ await waitForOutputToContain(() => getOutput(secondTerminalId), "__KANNA_FRESH_SESSION__")
307
+
308
+ const interactionOutput = getOutput(secondTerminalId).slice(before)
309
+ expect(interactionOutput).not.toContain("^[[I")
310
+ } finally {
311
+ manager.close(firstTerminalId)
312
+ manager.close(secondTerminalId)
313
+ }
314
+ })
315
+ })
@@ -0,0 +1,350 @@
1
+ import path from "node:path"
2
+ import process from "node:process"
3
+ import defaultShell, { detectDefaultShell } from "default-shell"
4
+ import { Terminal } from "@xterm/headless"
5
+ import { SerializeAddon } from "@xterm/addon-serialize"
6
+ import type { TerminalEvent, TerminalSnapshot } from "../shared/protocol"
7
+
8
+ const DEFAULT_COLS = 80
9
+ const DEFAULT_ROWS = 24
10
+ const DEFAULT_SCROLLBACK = 1_000
11
+ const MIN_SCROLLBACK = 500
12
+ const MAX_SCROLLBACK = 5_000
13
+ const FOCUS_IN_SEQUENCE = "\x1b[I"
14
+ const FOCUS_OUT_SEQUENCE = "\x1b[O"
15
+ const MODE_SEQUENCE_TAIL_LENGTH = 16
16
+
17
+ interface CreateTerminalArgs {
18
+ projectPath: string
19
+ terminalId: string
20
+ cols: number
21
+ rows: number
22
+ scrollback: number
23
+ }
24
+
25
+ interface TerminalSession {
26
+ terminalId: string
27
+ title: string
28
+ cwd: string
29
+ shell: string
30
+ cols: number
31
+ rows: number
32
+ scrollback: number
33
+ status: "running" | "exited"
34
+ exitCode: number | null
35
+ process: Bun.Subprocess | null
36
+ terminal: Bun.Terminal
37
+ headless: Terminal
38
+ serializeAddon: SerializeAddon
39
+ focusReportingEnabled: boolean
40
+ modeSequenceTail: string
41
+ }
42
+
43
+ function clampScrollback(value: number) {
44
+ if (!Number.isFinite(value)) return DEFAULT_SCROLLBACK
45
+ return Math.min(MAX_SCROLLBACK, Math.max(MIN_SCROLLBACK, Math.round(value)))
46
+ }
47
+
48
+ function normalizeTerminalDimension(value: number, fallback: number) {
49
+ if (!Number.isFinite(value)) return fallback
50
+ return Math.max(1, Math.round(value))
51
+ }
52
+
53
+ function resolveShell() {
54
+ try {
55
+ return detectDefaultShell()
56
+ } catch {
57
+ if (defaultShell) return defaultShell
58
+ if (process.platform === "win32") {
59
+ return process.env.ComSpec || "cmd.exe"
60
+ }
61
+ return process.env.SHELL || "/bin/sh"
62
+ }
63
+ }
64
+
65
+ function resolveShellArgs(shellPath: string) {
66
+ if (process.platform === "win32") {
67
+ return []
68
+ }
69
+
70
+ const shellName = path.basename(shellPath)
71
+ if (["bash", "zsh", "fish", "sh", "ksh"].includes(shellName)) {
72
+ return ["-l"]
73
+ }
74
+
75
+ return []
76
+ }
77
+
78
+ function createTerminalEnv() {
79
+ return {
80
+ ...process.env,
81
+ TERM: "xterm-256color",
82
+ COLORTERM: "truecolor",
83
+ }
84
+ }
85
+
86
+ function updateFocusReportingState(session: Pick<TerminalSession, "focusReportingEnabled" | "modeSequenceTail">, chunk: string) {
87
+ const combined = session.modeSequenceTail + chunk
88
+ const regex = /\x1b\[\?1004([hl])/g
89
+
90
+ for (const match of combined.matchAll(regex)) {
91
+ session.focusReportingEnabled = match[1] === "h"
92
+ }
93
+
94
+ session.modeSequenceTail = combined.slice(-MODE_SEQUENCE_TAIL_LENGTH)
95
+ }
96
+
97
+ function filterFocusReportInput(data: string, allowFocusReporting: boolean) {
98
+ if (allowFocusReporting) {
99
+ return data
100
+ }
101
+
102
+ return data.replaceAll(FOCUS_IN_SEQUENCE, "").replaceAll(FOCUS_OUT_SEQUENCE, "")
103
+ }
104
+
105
+ function killTerminalProcessTree(subprocess: Bun.Subprocess | null) {
106
+ if (!subprocess) return
107
+
108
+ const pid = subprocess.pid
109
+ if (typeof pid !== "number") return
110
+
111
+ if (process.platform !== "win32") {
112
+ try {
113
+ process.kill(-pid, "SIGKILL")
114
+ return
115
+ } catch {
116
+ // Fall back to killing only the shell process if group termination fails.
117
+ }
118
+ }
119
+
120
+ try {
121
+ subprocess.kill("SIGKILL")
122
+ } catch {
123
+ // Ignore subprocess shutdown errors during disposal.
124
+ }
125
+ }
126
+
127
+ function signalTerminalProcessGroup(subprocess: Bun.Subprocess | null, signal: NodeJS.Signals) {
128
+ if (!subprocess) return false
129
+
130
+ const pid = subprocess.pid
131
+ if (typeof pid !== "number") return false
132
+
133
+ if (process.platform !== "win32") {
134
+ try {
135
+ process.kill(-pid, signal)
136
+ return true
137
+ } catch {
138
+ // Fall back to signaling only the shell if group signaling fails.
139
+ }
140
+ }
141
+
142
+ try {
143
+ subprocess.kill(signal)
144
+ return true
145
+ } catch {
146
+ return false
147
+ }
148
+ }
149
+
150
+ export class TerminalManager {
151
+ private readonly sessions = new Map<string, TerminalSession>()
152
+ private readonly listeners = new Set<(event: TerminalEvent) => void>()
153
+
154
+ onEvent(listener: (event: TerminalEvent) => void) {
155
+ this.listeners.add(listener)
156
+ return () => {
157
+ this.listeners.delete(listener)
158
+ }
159
+ }
160
+
161
+ createTerminal(args: CreateTerminalArgs) {
162
+ if (process.platform === "win32") {
163
+ throw new Error("Embedded terminal is currently supported on macOS/Linux only.")
164
+ }
165
+ if (typeof Bun.Terminal !== "function") {
166
+ throw new Error("Embedded terminal requires Bun 1.3.5+ with Bun.Terminal support.")
167
+ }
168
+
169
+ const existing = this.sessions.get(args.terminalId)
170
+ if (existing) {
171
+ existing.scrollback = clampScrollback(args.scrollback)
172
+ existing.cols = normalizeTerminalDimension(args.cols, existing.cols)
173
+ existing.rows = normalizeTerminalDimension(args.rows, existing.rows)
174
+ existing.headless.options.scrollback = existing.scrollback
175
+ existing.headless.resize(existing.cols, existing.rows)
176
+ existing.terminal.resize(existing.cols, existing.rows)
177
+ signalTerminalProcessGroup(existing.process, "SIGWINCH")
178
+ return this.snapshotOf(existing)
179
+ }
180
+
181
+ const shell = resolveShell()
182
+ const cols = normalizeTerminalDimension(args.cols, DEFAULT_COLS)
183
+ const rows = normalizeTerminalDimension(args.rows, DEFAULT_ROWS)
184
+ const scrollback = clampScrollback(args.scrollback)
185
+ const title = path.basename(shell) || "shell"
186
+ const headless = new Terminal({ cols, rows, scrollback, allowProposedApi: true })
187
+ const serializeAddon = new SerializeAddon()
188
+ headless.loadAddon(serializeAddon)
189
+
190
+ const session: TerminalSession = {
191
+ terminalId: args.terminalId,
192
+ title,
193
+ cwd: args.projectPath,
194
+ shell,
195
+ cols,
196
+ rows,
197
+ scrollback,
198
+ status: "running",
199
+ exitCode: null,
200
+ process: null,
201
+ terminal: new Bun.Terminal({
202
+ cols,
203
+ rows,
204
+ name: "xterm-256color",
205
+ data: (_terminal, data) => {
206
+ const chunk = Buffer.from(data).toString("utf8")
207
+ updateFocusReportingState(session, chunk)
208
+ headless.write(chunk)
209
+ this.emit({
210
+ type: "terminal.output",
211
+ terminalId: args.terminalId,
212
+ data: chunk,
213
+ })
214
+ },
215
+ }),
216
+ headless,
217
+ serializeAddon,
218
+ focusReportingEnabled: false,
219
+ modeSequenceTail: "",
220
+ }
221
+
222
+ try {
223
+ session.process = Bun.spawn([shell, ...resolveShellArgs(shell)], {
224
+ cwd: args.projectPath,
225
+ env: createTerminalEnv(),
226
+ terminal: session.terminal,
227
+ })
228
+ } catch (error) {
229
+ session.terminal.close()
230
+ session.serializeAddon.dispose()
231
+ session.headless.dispose()
232
+ throw error
233
+ }
234
+ void session.process.exited.then((exitCode) => {
235
+ const active = this.sessions.get(args.terminalId)
236
+ if (!active) return
237
+ active.status = "exited"
238
+ active.exitCode = exitCode
239
+ this.emit({
240
+ type: "terminal.exit",
241
+ terminalId: args.terminalId,
242
+ exitCode,
243
+ })
244
+ }).catch((error) => {
245
+ const active = this.sessions.get(args.terminalId)
246
+ if (!active) return
247
+ active.status = "exited"
248
+ active.exitCode = 1
249
+ this.emit({
250
+ type: "terminal.output",
251
+ terminalId: args.terminalId,
252
+ data: `\r\n[terminal error] ${error instanceof Error ? error.message : String(error)}\r\n`,
253
+ })
254
+ this.emit({
255
+ type: "terminal.exit",
256
+ terminalId: args.terminalId,
257
+ exitCode: 1,
258
+ })
259
+ })
260
+
261
+ this.sessions.set(args.terminalId, session)
262
+ return this.snapshotOf(session)
263
+ }
264
+
265
+ getSnapshot(terminalId: string): TerminalSnapshot | null {
266
+ const session = this.sessions.get(terminalId)
267
+ return session ? this.snapshotOf(session) : null
268
+ }
269
+
270
+ write(terminalId: string, data: string) {
271
+ const session = this.sessions.get(terminalId)
272
+ if (!session || session.status === "exited") return
273
+
274
+ const filteredData = filterFocusReportInput(data, session.focusReportingEnabled)
275
+ if (!filteredData) return
276
+
277
+ let cursor = 0
278
+
279
+ while (cursor < filteredData.length) {
280
+ const ctrlCIndex = filteredData.indexOf("\x03", cursor)
281
+
282
+ if (ctrlCIndex === -1) {
283
+ session.terminal.write(filteredData.slice(cursor))
284
+ return
285
+ }
286
+
287
+ if (ctrlCIndex > cursor) {
288
+ session.terminal.write(filteredData.slice(cursor, ctrlCIndex))
289
+ }
290
+
291
+ signalTerminalProcessGroup(session.process, "SIGINT")
292
+ cursor = ctrlCIndex + 1
293
+ }
294
+ }
295
+
296
+ resize(terminalId: string, cols: number, rows: number) {
297
+ const session = this.sessions.get(terminalId)
298
+ if (!session) return
299
+ session.cols = normalizeTerminalDimension(cols, session.cols)
300
+ session.rows = normalizeTerminalDimension(rows, session.rows)
301
+ session.headless.resize(session.cols, session.rows)
302
+ session.terminal.resize(session.cols, session.rows)
303
+ signalTerminalProcessGroup(session.process, "SIGWINCH")
304
+ }
305
+
306
+ close(terminalId: string) {
307
+ const session = this.sessions.get(terminalId)
308
+ if (!session) return
309
+
310
+ this.sessions.delete(terminalId)
311
+ killTerminalProcessTree(session.process)
312
+ session.terminal.close()
313
+ session.serializeAddon.dispose()
314
+ session.headless.dispose()
315
+ }
316
+
317
+ closeByCwd(cwd: string) {
318
+ for (const [terminalId, session] of this.sessions.entries()) {
319
+ if (session.cwd !== cwd) continue
320
+ this.close(terminalId)
321
+ }
322
+ }
323
+
324
+ closeAll() {
325
+ for (const terminalId of this.sessions.keys()) {
326
+ this.close(terminalId)
327
+ }
328
+ }
329
+
330
+ private snapshotOf(session: TerminalSession): TerminalSnapshot {
331
+ return {
332
+ terminalId: session.terminalId,
333
+ title: session.title,
334
+ cwd: session.cwd,
335
+ shell: session.shell,
336
+ cols: session.cols,
337
+ rows: session.rows,
338
+ scrollback: session.scrollback,
339
+ serializedState: session.serializeAddon.serialize({ scrollback: session.scrollback }),
340
+ status: session.status,
341
+ exitCode: session.exitCode,
342
+ }
343
+ }
344
+
345
+ private emit(event: TerminalEvent) {
346
+ for (const listener of this.listeners) {
347
+ listener(event)
348
+ }
349
+ }
350
+ }
@@ -0,0 +1,52 @@
1
+ /** Async iterable queue used in tests to feed events into a session stream. */
2
+ export class AsyncEventQueue<T> implements AsyncIterable<T> {
3
+ private readonly values: T[] = []
4
+ private readonly waiters: Array<{ resolve: (result: IteratorResult<T>) => void; reject: (error: unknown) => void }> = []
5
+ private closed = false
6
+ private pendingError: unknown = null
7
+
8
+ push(value: T): void {
9
+ const waiter = this.waiters.shift()
10
+ if (waiter) {
11
+ waiter.resolve({ done: false, value })
12
+ return
13
+ }
14
+ this.values.push(value)
15
+ }
16
+
17
+ throw(error: unknown): void {
18
+ this.pendingError = error
19
+ const waiter = this.waiters.shift()
20
+ if (waiter) {
21
+ waiter.reject(error)
22
+ }
23
+ }
24
+
25
+ close(): void {
26
+ this.closed = true
27
+ while (this.waiters.length > 0) {
28
+ this.waiters.shift()?.resolve({ done: true, value: undefined as never })
29
+ }
30
+ }
31
+
32
+ [Symbol.asyncIterator](): AsyncIterator<T> {
33
+ return {
34
+ next: async () => {
35
+ if (this.pendingError !== null) {
36
+ const err = this.pendingError
37
+ this.pendingError = null
38
+ throw err
39
+ }
40
+ if (this.values.length > 0) {
41
+ return { done: false, value: this.values.shift() as T }
42
+ }
43
+ if (this.closed) {
44
+ return { done: true, value: undefined as never }
45
+ }
46
+ return await new Promise<IteratorResult<T>>((resolve, reject) => {
47
+ this.waiters.push({ resolve, reject })
48
+ })
49
+ },
50
+ }
51
+ }
52
+ }