@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,548 @@
1
+ import { randomUUID } from "node:crypto"
2
+ import { watch, type FSWatcher } from "node:fs"
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises"
4
+ import { homedir } from "node:os"
5
+ import path from "node:path"
6
+ import { getSettingsFilePath, LOG_PREFIX } from "../shared/branding"
7
+ import {
8
+ CLOUDFLARE_TUNNEL_DEFAULTS,
9
+ DEFAULT_CLAUDE_MODEL_OPTIONS,
10
+ DEFAULT_CODEX_MODEL_OPTIONS,
11
+ isClaudeReasoningEffort,
12
+ isCodexReasoningEffort,
13
+ normalizeClaudeContextWindow,
14
+ normalizeClaudeModelId,
15
+ normalizeCodexModelId,
16
+ supportsClaudeMaxReasoningEffort,
17
+ type AppSettingsPatch,
18
+ type AppSettingsSnapshot,
19
+ type AppThemePreference,
20
+ type ChatProviderPreferences,
21
+ type ChatSoundId,
22
+ type ChatSoundPreference,
23
+ type ClaudeModelOptions,
24
+ type CloudflareTunnelSettings,
25
+ type CodexModelOptions,
26
+ type DefaultProviderPreference,
27
+ type EditorPreset,
28
+ type ProviderPreference,
29
+ } from "../shared/types"
30
+
31
+ interface AppSettingsFile {
32
+ analyticsEnabled?: unknown
33
+ analyticsUserId?: unknown
34
+ browserSettingsMigrated?: unknown
35
+ theme?: unknown
36
+ chatSoundPreference?: unknown
37
+ chatSoundId?: unknown
38
+ terminal?: {
39
+ scrollbackLines?: unknown
40
+ minColumnWidth?: unknown
41
+ }
42
+ editor?: {
43
+ preset?: unknown
44
+ commandTemplate?: unknown
45
+ }
46
+ defaultProvider?: unknown
47
+ providerDefaults?: {
48
+ claude?: Partial<ProviderPreference<Partial<ClaudeModelOptions>>> & { effort?: unknown }
49
+ codex?: Partial<ProviderPreference<Partial<CodexModelOptions>>> & { effort?: unknown }
50
+ }
51
+ cloudflareTunnel?: unknown
52
+ }
53
+
54
+ interface AppSettingsState extends AppSettingsSnapshot {
55
+ analyticsUserId: string
56
+ }
57
+
58
+ interface NormalizedAppSettings {
59
+ payload: AppSettingsState
60
+ warning: string | null
61
+ shouldWrite: boolean
62
+ }
63
+
64
+ const DEFAULT_TERMINAL_SCROLLBACK = 1_000
65
+ const MIN_TERMINAL_SCROLLBACK = 500
66
+ const MAX_TERMINAL_SCROLLBACK = 5_000
67
+ const DEFAULT_TERMINAL_MIN_COLUMN_WIDTH = 450
68
+ const MIN_TERMINAL_MIN_COLUMN_WIDTH = 250
69
+ const MAX_TERMINAL_MIN_COLUMN_WIDTH = 900
70
+ const DEFAULT_EDITOR_PRESET: EditorPreset = "cursor"
71
+ const DEFAULT_CHAT_SOUND_PREFERENCE: ChatSoundPreference = "always"
72
+ const DEFAULT_CHAT_SOUND_ID: ChatSoundId = "funk"
73
+
74
+ function formatDisplayPath(filePath: string) {
75
+ const homePath = homedir()
76
+ if (filePath === homePath) return "~"
77
+ if (filePath.startsWith(`${homePath}${path.sep}`)) {
78
+ return `~${filePath.slice(homePath.length)}`
79
+ }
80
+ return filePath
81
+ }
82
+
83
+ function createAnalyticsUserId() {
84
+ return `anon_${randomUUID()}`
85
+ }
86
+
87
+ function getDefaultEditorCommandTemplate(preset: EditorPreset) {
88
+ switch (preset) {
89
+ case "vscode":
90
+ return "code {path}"
91
+ case "xcode":
92
+ return "xed {path}"
93
+ case "windsurf":
94
+ return "windsurf {path}"
95
+ case "custom":
96
+ case "cursor":
97
+ default:
98
+ return "cursor {path}"
99
+ }
100
+ }
101
+
102
+ function createDefaultProviderDefaults(): ChatProviderPreferences {
103
+ return {
104
+ claude: {
105
+ model: "claude-opus-4-7",
106
+ modelOptions: { ...DEFAULT_CLAUDE_MODEL_OPTIONS },
107
+ planMode: false,
108
+ },
109
+ codex: {
110
+ model: "gpt-5.5",
111
+ modelOptions: { ...DEFAULT_CODEX_MODEL_OPTIONS },
112
+ planMode: false,
113
+ },
114
+ }
115
+ }
116
+
117
+ function clampNumber(value: unknown, fallback: number, min: number, max: number) {
118
+ const numberValue = typeof value === "number" ? value : Number(value)
119
+ if (!Number.isFinite(numberValue)) return fallback
120
+ return Math.min(max, Math.max(min, Math.round(numberValue)))
121
+ }
122
+
123
+ function normalizeTheme(value: unknown): AppThemePreference {
124
+ return value === "light" || value === "dark" || value === "system" ? value : "system"
125
+ }
126
+
127
+ function normalizeChatSoundPreference(value: unknown): ChatSoundPreference {
128
+ return value === "never" || value === "unfocused" || value === "always" ? value : DEFAULT_CHAT_SOUND_PREFERENCE
129
+ }
130
+
131
+ function normalizeChatSoundId(value: unknown): ChatSoundId {
132
+ switch (value) {
133
+ case "blow":
134
+ case "bottle":
135
+ case "frog":
136
+ case "funk":
137
+ case "glass":
138
+ case "ping":
139
+ case "pop":
140
+ case "purr":
141
+ case "tink":
142
+ return value
143
+ default:
144
+ return DEFAULT_CHAT_SOUND_ID
145
+ }
146
+ }
147
+
148
+ function normalizeDefaultProvider(value: unknown): DefaultProviderPreference {
149
+ return value === "claude" || value === "codex" || value === "last_used" ? value : "last_used"
150
+ }
151
+
152
+ function normalizeEditorPreset(value: unknown): EditorPreset {
153
+ return value === "vscode" || value === "xcode" || value === "windsurf" || value === "custom" || value === "cursor"
154
+ ? value
155
+ : DEFAULT_EDITOR_PRESET
156
+ }
157
+
158
+ function normalizeEditorCommandTemplate(value: unknown, preset: EditorPreset) {
159
+ const trimmed = typeof value === "string" ? value.trim() : ""
160
+ return trimmed || getDefaultEditorCommandTemplate(preset)
161
+ }
162
+
163
+ function normalizeClaudePreference(value?: {
164
+ model?: unknown
165
+ effort?: unknown
166
+ modelOptions?: Partial<Record<keyof ClaudeModelOptions, unknown>>
167
+ planMode?: unknown
168
+ }): ProviderPreference<ClaudeModelOptions> {
169
+ const model = normalizeClaudeModelId(typeof value?.model === "string" ? value.model : undefined)
170
+ const reasoningEffort = value?.modelOptions?.reasoningEffort
171
+ const normalizedEffort = isClaudeReasoningEffort(reasoningEffort)
172
+ ? reasoningEffort
173
+ : isClaudeReasoningEffort(value?.effort)
174
+ ? value.effort
175
+ : DEFAULT_CLAUDE_MODEL_OPTIONS.reasoningEffort
176
+
177
+ return {
178
+ model,
179
+ modelOptions: {
180
+ reasoningEffort: !supportsClaudeMaxReasoningEffort(model) && normalizedEffort === "max" ? "high" : normalizedEffort,
181
+ contextWindow: normalizeClaudeContextWindow(model, value?.modelOptions?.contextWindow),
182
+ },
183
+ planMode: value?.planMode === true,
184
+ }
185
+ }
186
+
187
+ function normalizeCodexPreference(value?: {
188
+ model?: unknown
189
+ effort?: unknown
190
+ modelOptions?: Partial<Record<keyof CodexModelOptions, unknown>>
191
+ planMode?: unknown
192
+ }): ProviderPreference<CodexModelOptions> {
193
+ const reasoningEffort = value?.modelOptions?.reasoningEffort
194
+ return {
195
+ model: normalizeCodexModelId(typeof value?.model === "string" ? value.model : undefined),
196
+ modelOptions: {
197
+ reasoningEffort: isCodexReasoningEffort(reasoningEffort)
198
+ ? reasoningEffort
199
+ : isCodexReasoningEffort(value?.effort)
200
+ ? value.effort
201
+ : DEFAULT_CODEX_MODEL_OPTIONS.reasoningEffort,
202
+ fastMode: typeof value?.modelOptions?.fastMode === "boolean"
203
+ ? value.modelOptions.fastMode
204
+ : DEFAULT_CODEX_MODEL_OPTIONS.fastMode,
205
+ },
206
+ planMode: value?.planMode === true,
207
+ }
208
+ }
209
+
210
+ function normalizeProviderDefaults(value: AppSettingsFile["providerDefaults"] | undefined): ChatProviderPreferences {
211
+ const defaults = createDefaultProviderDefaults()
212
+ return {
213
+ claude: normalizeClaudePreference(value?.claude ?? defaults.claude),
214
+ codex: normalizeCodexPreference(value?.codex ?? defaults.codex),
215
+ }
216
+ }
217
+
218
+ function normalizeCloudflareTunnel(value: unknown, warnings: string[]): CloudflareTunnelSettings {
219
+ const tunnelSource = value && typeof value === "object" && !Array.isArray(value)
220
+ ? value as Record<string, unknown>
221
+ : null
222
+ if (value !== undefined && !tunnelSource) {
223
+ warnings.push("cloudflareTunnel must be an object")
224
+ }
225
+
226
+ const enabled = typeof tunnelSource?.enabled === "boolean"
227
+ ? tunnelSource.enabled
228
+ : CLOUDFLARE_TUNNEL_DEFAULTS.enabled
229
+ if (tunnelSource?.enabled !== undefined && typeof tunnelSource.enabled !== "boolean") {
230
+ warnings.push("cloudflareTunnel.enabled must be a boolean")
231
+ }
232
+
233
+ const cloudflaredPath = typeof tunnelSource?.cloudflaredPath === "string" && tunnelSource.cloudflaredPath.trim()
234
+ ? tunnelSource.cloudflaredPath.trim()
235
+ : CLOUDFLARE_TUNNEL_DEFAULTS.cloudflaredPath
236
+ if (tunnelSource?.cloudflaredPath !== undefined && typeof tunnelSource.cloudflaredPath !== "string") {
237
+ warnings.push("cloudflareTunnel.cloudflaredPath must be a string")
238
+ }
239
+
240
+ const rawMode = tunnelSource?.mode
241
+ const mode: CloudflareTunnelSettings["mode"] =
242
+ rawMode === "always-ask" || rawMode === "auto-expose"
243
+ ? rawMode
244
+ : CLOUDFLARE_TUNNEL_DEFAULTS.mode
245
+ if (tunnelSource?.mode !== undefined && rawMode !== "always-ask" && rawMode !== "auto-expose") {
246
+ warnings.push(`cloudflareTunnel.mode must be "always-ask" or "auto-expose"`)
247
+ }
248
+
249
+ return { enabled, cloudflaredPath, mode }
250
+ }
251
+
252
+ function toFilePayload(state: AppSettingsState) {
253
+ return {
254
+ analyticsEnabled: state.analyticsEnabled,
255
+ analyticsUserId: state.analyticsUserId,
256
+ browserSettingsMigrated: state.browserSettingsMigrated,
257
+ theme: state.theme,
258
+ chatSoundPreference: state.chatSoundPreference,
259
+ chatSoundId: state.chatSoundId,
260
+ terminal: state.terminal,
261
+ editor: state.editor,
262
+ defaultProvider: state.defaultProvider,
263
+ providerDefaults: state.providerDefaults,
264
+ cloudflareTunnel: state.cloudflareTunnel,
265
+ }
266
+ }
267
+
268
+ function toSnapshot(state: AppSettingsState): AppSettingsSnapshot {
269
+ return {
270
+ analyticsEnabled: state.analyticsEnabled,
271
+ browserSettingsMigrated: state.browserSettingsMigrated,
272
+ theme: state.theme,
273
+ chatSoundPreference: state.chatSoundPreference,
274
+ chatSoundId: state.chatSoundId,
275
+ terminal: state.terminal,
276
+ editor: state.editor,
277
+ defaultProvider: state.defaultProvider,
278
+ providerDefaults: state.providerDefaults,
279
+ warning: state.warning,
280
+ filePathDisplay: state.filePathDisplay,
281
+ cloudflareTunnel: state.cloudflareTunnel,
282
+ }
283
+ }
284
+
285
+ function normalizeAppSettings(
286
+ value: unknown,
287
+ filePath = getSettingsFilePath(homedir())
288
+ ): NormalizedAppSettings {
289
+ const source = value && typeof value === "object" && !Array.isArray(value)
290
+ ? value as AppSettingsFile
291
+ : null
292
+ const warnings: string[] = []
293
+
294
+ if (value !== undefined && value !== null && !source) {
295
+ warnings.push("Settings file must contain a JSON object")
296
+ }
297
+
298
+ const analyticsEnabled = typeof source?.analyticsEnabled === "boolean" ? source.analyticsEnabled : true
299
+ if (source?.analyticsEnabled !== undefined && typeof source.analyticsEnabled !== "boolean") {
300
+ warnings.push("analyticsEnabled must be a boolean")
301
+ }
302
+
303
+ const rawAnalyticsUserId = typeof source?.analyticsUserId === "string" ? source.analyticsUserId.trim() : ""
304
+ if (source?.analyticsUserId !== undefined && typeof source.analyticsUserId !== "string") {
305
+ warnings.push("analyticsUserId must be a string")
306
+ }
307
+ const analyticsUserId = rawAnalyticsUserId || createAnalyticsUserId()
308
+ if (!rawAnalyticsUserId && source?.analyticsUserId !== undefined) {
309
+ warnings.push("analyticsUserId must be a non-empty string")
310
+ }
311
+
312
+ const cloudflareTunnel = normalizeCloudflareTunnel(source?.cloudflareTunnel, warnings)
313
+
314
+ const editorPreset = normalizeEditorPreset(source?.editor?.preset)
315
+ const state: AppSettingsState = {
316
+ analyticsEnabled,
317
+ analyticsUserId,
318
+ browserSettingsMigrated: source?.browserSettingsMigrated === true,
319
+ theme: normalizeTheme(source?.theme),
320
+ chatSoundPreference: normalizeChatSoundPreference(source?.chatSoundPreference),
321
+ chatSoundId: normalizeChatSoundId(source?.chatSoundId),
322
+ terminal: {
323
+ scrollbackLines: clampNumber(source?.terminal?.scrollbackLines, DEFAULT_TERMINAL_SCROLLBACK, MIN_TERMINAL_SCROLLBACK, MAX_TERMINAL_SCROLLBACK),
324
+ minColumnWidth: clampNumber(source?.terminal?.minColumnWidth, DEFAULT_TERMINAL_MIN_COLUMN_WIDTH, MIN_TERMINAL_MIN_COLUMN_WIDTH, MAX_TERMINAL_MIN_COLUMN_WIDTH),
325
+ },
326
+ editor: {
327
+ preset: editorPreset,
328
+ commandTemplate: normalizeEditorCommandTemplate(source?.editor?.commandTemplate, editorPreset),
329
+ },
330
+ defaultProvider: normalizeDefaultProvider(source?.defaultProvider),
331
+ providerDefaults: normalizeProviderDefaults(source?.providerDefaults),
332
+ warning: null,
333
+ filePathDisplay: formatDisplayPath(filePath),
334
+ cloudflareTunnel,
335
+ }
336
+
337
+ const shouldWrite = JSON.stringify(source ? toComparablePayload(source) : null) !== JSON.stringify(toFilePayload(state))
338
+ state.warning = warnings.length > 0
339
+ ? `Some settings were reset to defaults: ${warnings.join("; ")}`
340
+ : null
341
+
342
+ return {
343
+ payload: state,
344
+ warning: state.warning,
345
+ shouldWrite,
346
+ }
347
+ }
348
+
349
+ function toComparablePayload(source: AppSettingsFile) {
350
+ return {
351
+ analyticsEnabled: source.analyticsEnabled,
352
+ analyticsUserId: typeof source.analyticsUserId === "string" ? source.analyticsUserId.trim() : source.analyticsUserId,
353
+ browserSettingsMigrated: source.browserSettingsMigrated,
354
+ theme: source.theme,
355
+ chatSoundPreference: source.chatSoundPreference,
356
+ chatSoundId: source.chatSoundId,
357
+ terminal: source.terminal,
358
+ editor: source.editor,
359
+ defaultProvider: source.defaultProvider,
360
+ providerDefaults: source.providerDefaults,
361
+ cloudflareTunnel: source.cloudflareTunnel,
362
+ }
363
+ }
364
+
365
+ function applyPatch(state: AppSettingsState, patch: AppSettingsPatch): AppSettingsState {
366
+ return normalizeAppSettings({
367
+ ...toFilePayload(state),
368
+ ...patch,
369
+ terminal: {
370
+ ...state.terminal,
371
+ ...patch.terminal,
372
+ },
373
+ editor: {
374
+ ...state.editor,
375
+ ...patch.editor,
376
+ },
377
+ providerDefaults: {
378
+ claude: {
379
+ ...state.providerDefaults.claude,
380
+ ...patch.providerDefaults?.claude,
381
+ modelOptions: {
382
+ ...state.providerDefaults.claude.modelOptions,
383
+ ...patch.providerDefaults?.claude?.modelOptions,
384
+ },
385
+ },
386
+ codex: {
387
+ ...state.providerDefaults.codex,
388
+ ...patch.providerDefaults?.codex,
389
+ modelOptions: {
390
+ ...state.providerDefaults.codex.modelOptions,
391
+ ...patch.providerDefaults?.codex?.modelOptions,
392
+ },
393
+ },
394
+ },
395
+ cloudflareTunnel: {
396
+ ...state.cloudflareTunnel,
397
+ ...patch.cloudflareTunnel,
398
+ },
399
+ }, state.filePathDisplay).payload
400
+ }
401
+
402
+ export async function readAppSettingsSnapshot(filePath = getSettingsFilePath(homedir())) {
403
+ try {
404
+ const text = await readFile(filePath, "utf8")
405
+ if (!text.trim()) {
406
+ const normalized = normalizeAppSettings(undefined, filePath)
407
+ return {
408
+ ...toSnapshot(normalized.payload),
409
+ warning: "Settings file was empty. Using defaults.",
410
+ } satisfies AppSettingsSnapshot
411
+ }
412
+
413
+ return toSnapshot(normalizeAppSettings(JSON.parse(text), filePath).payload)
414
+ } catch (error) {
415
+ if ((error as NodeJS.ErrnoException)?.code === "ENOENT") {
416
+ return toSnapshot(normalizeAppSettings(undefined, filePath).payload)
417
+ }
418
+ if (error instanceof SyntaxError) {
419
+ return {
420
+ ...toSnapshot(normalizeAppSettings(undefined, filePath).payload),
421
+ warning: "Settings file is invalid JSON. Using defaults.",
422
+ } satisfies AppSettingsSnapshot
423
+ }
424
+ throw error
425
+ }
426
+ }
427
+
428
+ export class AppSettingsManager {
429
+ readonly filePath: string
430
+ private watcher: FSWatcher | null = null
431
+ private state: AppSettingsState
432
+ private readonly listeners = new Set<(snapshot: AppSettingsSnapshot) => void>()
433
+
434
+ constructor(filePath = getSettingsFilePath(homedir())) {
435
+ this.filePath = filePath
436
+ this.state = normalizeAppSettings(undefined, filePath).payload
437
+ }
438
+
439
+ async initialize() {
440
+ await mkdir(path.dirname(this.filePath), { recursive: true })
441
+ await this.reload({ persistNormalized: true })
442
+ this.startWatching()
443
+ }
444
+
445
+ dispose() {
446
+ this.watcher?.close()
447
+ this.watcher = null
448
+ this.listeners.clear()
449
+ }
450
+
451
+ getSnapshot() {
452
+ return toSnapshot(this.state)
453
+ }
454
+
455
+ getState() {
456
+ return this.state
457
+ }
458
+
459
+ onChange(listener: (snapshot: AppSettingsSnapshot) => void) {
460
+ this.listeners.add(listener)
461
+ return () => {
462
+ this.listeners.delete(listener)
463
+ }
464
+ }
465
+
466
+ async reload(options?: { persistNormalized?: boolean }) {
467
+ const nextState = await this.readState(options)
468
+ this.setState(nextState)
469
+ }
470
+
471
+ async write(value: { analyticsEnabled: boolean }) {
472
+ return this.writePatch({ analyticsEnabled: value.analyticsEnabled })
473
+ }
474
+
475
+ async setCloudflareTunnel(patch: Partial<CloudflareTunnelSettings>) {
476
+ if (patch.mode !== undefined && patch.mode !== "always-ask" && patch.mode !== "auto-expose") {
477
+ throw new Error("Invalid cloudflareTunnel.mode")
478
+ }
479
+ return this.writePatch({ cloudflareTunnel: patch })
480
+ }
481
+
482
+ async writePatch(patch: AppSettingsPatch) {
483
+ const nextState = {
484
+ ...applyPatch(this.state, patch),
485
+ warning: null,
486
+ filePathDisplay: formatDisplayPath(this.filePath),
487
+ }
488
+ await mkdir(path.dirname(this.filePath), { recursive: true })
489
+ await writeFile(this.filePath, `${JSON.stringify(toFilePayload(nextState), null, 2)}\n`, "utf8")
490
+ this.setState(nextState)
491
+ return toSnapshot(nextState)
492
+ }
493
+
494
+ private async readState(options?: { persistNormalized?: boolean }) {
495
+ const file = Bun.file(this.filePath)
496
+
497
+ try {
498
+ const text = await file.text()
499
+ const hasText = text.trim().length > 0
500
+ const normalized = normalizeAppSettings(hasText ? JSON.parse(text) : undefined, this.filePath)
501
+ if (options?.persistNormalized && (!hasText || normalized.shouldWrite)) {
502
+ await writeFile(this.filePath, `${JSON.stringify(toFilePayload(normalized.payload), null, 2)}\n`, "utf8")
503
+ }
504
+ return {
505
+ ...normalized.payload,
506
+ warning: !hasText ? "Settings file was empty. Using defaults." : normalized.warning,
507
+ } satisfies AppSettingsState
508
+ } catch (error) {
509
+ if ((error as NodeJS.ErrnoException)?.code !== "ENOENT" && !(error instanceof SyntaxError)) {
510
+ throw error
511
+ }
512
+
513
+ const normalized = normalizeAppSettings(undefined, this.filePath)
514
+ if (options?.persistNormalized) {
515
+ await writeFile(this.filePath, `${JSON.stringify(toFilePayload(normalized.payload), null, 2)}\n`, "utf8")
516
+ }
517
+ return {
518
+ ...normalized.payload,
519
+ warning: error instanceof SyntaxError ? "Settings file is invalid JSON. Using defaults." : null,
520
+ } satisfies AppSettingsState
521
+ }
522
+ }
523
+
524
+ private setState(state: AppSettingsState) {
525
+ this.state = state
526
+ const snapshot = toSnapshot(state)
527
+ for (const listener of this.listeners) {
528
+ listener(snapshot)
529
+ }
530
+ }
531
+
532
+ private startWatching() {
533
+ this.watcher?.close()
534
+ try {
535
+ this.watcher = watch(path.dirname(this.filePath), { persistent: false }, (_eventType, filename) => {
536
+ if (filename && filename !== path.basename(this.filePath)) {
537
+ return
538
+ }
539
+ void this.reload().catch((error: unknown) => {
540
+ console.warn(`${LOG_PREFIX} Failed to reload settings:`, error)
541
+ })
542
+ })
543
+ } catch (error) {
544
+ console.warn(`${LOG_PREFIX} Failed to watch settings file:`, error)
545
+ this.watcher = null
546
+ }
547
+ }
548
+ }