@joeyshi12/casper 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (408) hide show
  1. package/dist/agents/casper.json +14 -0
  2. package/dist/casper.js +23 -0
  3. package/dist/server.js +3506 -0
  4. package/dist/web/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  5. package/dist/web/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  6. package/dist/web/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  7. package/dist/web/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  8. package/dist/web/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  9. package/dist/web/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  10. package/dist/web/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  11. package/dist/web/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  12. package/dist/web/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  13. package/dist/web/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  14. package/dist/web/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  15. package/dist/web/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  16. package/dist/web/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  17. package/dist/web/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  18. package/dist/web/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  19. package/dist/web/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  20. package/dist/web/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  21. package/dist/web/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  22. package/dist/web/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  23. package/dist/web/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  24. package/dist/web/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  25. package/dist/web/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  26. package/dist/web/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  27. package/dist/web/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  28. package/dist/web/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  29. package/dist/web/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  30. package/dist/web/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  31. package/dist/web/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  32. package/dist/web/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  33. package/dist/web/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  34. package/dist/web/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  35. package/dist/web/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  36. package/dist/web/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  37. package/dist/web/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  38. package/dist/web/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  39. package/dist/web/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  40. package/dist/web/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  41. package/dist/web/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  42. package/dist/web/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  43. package/dist/web/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  44. package/dist/web/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  45. package/dist/web/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  46. package/dist/web/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  47. package/dist/web/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  48. package/dist/web/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  49. package/dist/web/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  50. package/dist/web/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  51. package/dist/web/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  52. package/dist/web/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  53. package/dist/web/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  54. package/dist/web/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  55. package/dist/web/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  56. package/dist/web/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  57. package/dist/web/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  58. package/dist/web/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  59. package/dist/web/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  60. package/dist/web/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  61. package/dist/web/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  62. package/dist/web/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  63. package/dist/web/assets/abap-DsBKuouk.js +1 -0
  64. package/dist/web/assets/abnfDiagram-N423BO3Z-D4dAP-1-.js +1 -0
  65. package/dist/web/assets/actionscript-3-D_z4Izcz.js +1 -0
  66. package/dist/web/assets/ada-727ZlQH0.js +1 -0
  67. package/dist/web/assets/andromeeda-C3khCPGq.js +1 -0
  68. package/dist/web/assets/angular-html-LfdN0zeE.js +1 -0
  69. package/dist/web/assets/angular-ts-CKsD7JZE.js +1 -0
  70. package/dist/web/assets/apache-Dn00JSTd.js +1 -0
  71. package/dist/web/assets/apex-COJ4H7py.js +1 -0
  72. package/dist/web/assets/apl-BBq3IX1j.js +1 -0
  73. package/dist/web/assets/applescript-Bu5BbsvL.js +1 -0
  74. package/dist/web/assets/ara-7O62HKoU.js +1 -0
  75. package/dist/web/assets/arc-CicRYRW0.js +1 -0
  76. package/dist/web/assets/architectureDiagram-T3A2C74G-C8_jIuYI.js +36 -0
  77. package/dist/web/assets/asciidoc-BPT9niGB.js +1 -0
  78. package/dist/web/assets/asm-Dhn9LcZ4.js +1 -0
  79. package/dist/web/assets/astro-CqkE3fuf.js +1 -0
  80. package/dist/web/assets/aurora-x-D-2ljcwZ.js +1 -0
  81. package/dist/web/assets/awk-eg146-Ew.js +1 -0
  82. package/dist/web/assets/ayu-dark-Cv9koXgw.js +1 -0
  83. package/dist/web/assets/ballerina-Du268qiB.js +1 -0
  84. package/dist/web/assets/bat-fje9CFhw.js +1 -0
  85. package/dist/web/assets/beancount-BwXTMy5W.js +1 -0
  86. package/dist/web/assets/berry-3xVqZejG.js +1 -0
  87. package/dist/web/assets/bibtex-xW4inM5L.js +1 -0
  88. package/dist/web/assets/bicep-DHo0CJ0O.js +1 -0
  89. package/dist/web/assets/blade-a8OxSdnT.js +1 -0
  90. package/dist/web/assets/blockDiagram-VBNYF7ZC-1z_qaXwF.js +132 -0
  91. package/dist/web/assets/bsl-Dgyn0ogV.js +1 -0
  92. package/dist/web/assets/c-C3t2pwGQ.js +1 -0
  93. package/dist/web/assets/c4Diagram-5PPSVZJV-DA2HxqMu.js +10 -0
  94. package/dist/web/assets/cadence-DNquZEk8.js +1 -0
  95. package/dist/web/assets/cairo--RitsXJZ.js +1 -0
  96. package/dist/web/assets/catppuccin-frappe-CD_QflpE.js +1 -0
  97. package/dist/web/assets/catppuccin-latte-DRW-0cLl.js +1 -0
  98. package/dist/web/assets/catppuccin-macchiato-C-_shW-Y.js +1 -0
  99. package/dist/web/assets/catppuccin-mocha-LGGdnPYs.js +1 -0
  100. package/dist/web/assets/channel-DdA8Dcii.js +1 -0
  101. package/dist/web/assets/chunk-2GRJ4B5K-R9Gd-FO9.js +1 -0
  102. package/dist/web/assets/chunk-2Q5K7J3B-DBVFWztp.js +1 -0
  103. package/dist/web/assets/chunk-5RXB4S5H-bLTobZeO.js +231 -0
  104. package/dist/web/assets/chunk-5VM5RSS4-Bn8X-QeG.js +15 -0
  105. package/dist/web/assets/chunk-6Q2QTUOP-CQtChnAA.js +88 -0
  106. package/dist/web/assets/chunk-GF5L2VYU-DUrrlayx.js +206 -0
  107. package/dist/web/assets/chunk-JWPE2WC7-BgI2ovD2.js +1 -0
  108. package/dist/web/assets/chunk-KBJHAD2P-UMKTTxF1.js +1 -0
  109. package/dist/web/assets/chunk-RYQCIY6F-BOcO7eGL.js +1 -0
  110. package/dist/web/assets/chunk-XXDRQBXY-DE8-7iM5.js +1 -0
  111. package/dist/web/assets/clarity-BHOwM8T6.js +1 -0
  112. package/dist/web/assets/classDiagram-JCYQIIEL-Dxx1nEKY.js +1 -0
  113. package/dist/web/assets/classDiagram-v2-OCEON4UE-Dxx1nEKY.js +1 -0
  114. package/dist/web/assets/clojure-DxSadP1t.js +1 -0
  115. package/dist/web/assets/cmake-DbXoA79R.js +1 -0
  116. package/dist/web/assets/cobol-PTqiYgYu.js +1 -0
  117. package/dist/web/assets/codeowners-Bp6g37R7.js +1 -0
  118. package/dist/web/assets/codeql-sacFqUAJ.js +1 -0
  119. package/dist/web/assets/coffee-dyiR41kL.js +1 -0
  120. package/dist/web/assets/common-lisp-C7gG9l05.js +1 -0
  121. package/dist/web/assets/coq-Dsg_Bt_b.js +1 -0
  122. package/dist/web/assets/cose-bilkent-JH36ORCC-BAygCVU2.js +1 -0
  123. package/dist/web/assets/cpp-BksuvNSY.js +1 -0
  124. package/dist/web/assets/crystal-DtDmRg-F.js +1 -0
  125. package/dist/web/assets/csharp-D9R-vmeu.js +1 -0
  126. package/dist/web/assets/css-BPhBrDlE.js +1 -0
  127. package/dist/web/assets/csv-B0qRVHPH.js +1 -0
  128. package/dist/web/assets/cue-DtFQj3wx.js +1 -0
  129. package/dist/web/assets/cynefin-VYW2F7L2-CSJk_SPs.js +178 -0
  130. package/dist/web/assets/cynefinDiagram-MW4NZA55-C0yeigu-.js +62 -0
  131. package/dist/web/assets/cypher-m2LEI-9-.js +1 -0
  132. package/dist/web/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
  133. package/dist/web/assets/d-BoXegm-a.js +1 -0
  134. package/dist/web/assets/dagre-VZM6K2ZE-C7Wka8DN.js +4 -0
  135. package/dist/web/assets/dark-plus-C3mMm8J8.js +1 -0
  136. package/dist/web/assets/dart-B9wLZaAG.js +1 -0
  137. package/dist/web/assets/dax-ClGRhx96.js +1 -0
  138. package/dist/web/assets/defaultLocale-DX6XiGOO.js +1 -0
  139. package/dist/web/assets/desktop-DEIpsLCJ.js +1 -0
  140. package/dist/web/assets/diagram-7IWD3JNH-D6dg2rEx.js +30 -0
  141. package/dist/web/assets/diagram-B4RE2ZJO-D4_nbLMB.js +3 -0
  142. package/dist/web/assets/diagram-LBJQPF4R-Nht6dUst.js +24 -0
  143. package/dist/web/assets/diagram-Q27KOJAE-Dr47m9ou.js +24 -0
  144. package/dist/web/assets/diagram-UB23O5K3-wBr0ZbcL.js +41 -0
  145. package/dist/web/assets/diff-BgYniUM_.js +1 -0
  146. package/dist/web/assets/docker-COcR7UxN.js +1 -0
  147. package/dist/web/assets/dotenv-BjQB5zDj.js +1 -0
  148. package/dist/web/assets/dracula-BzJJZx-M.js +1 -0
  149. package/dist/web/assets/dracula-soft-BXkSAIEj.js +1 -0
  150. package/dist/web/assets/dream-maker-C-nORZOA.js +1 -0
  151. package/dist/web/assets/ebnfDiagram-BXEA7PRR-BLB0X9R-.js +1 -0
  152. package/dist/web/assets/edge-D5gP-w-T.js +1 -0
  153. package/dist/web/assets/elixir-CLiX3zqd.js +1 -0
  154. package/dist/web/assets/elm-CmHSxxaM.js +1 -0
  155. package/dist/web/assets/emacs-lisp-BX77sIaO.js +1 -0
  156. package/dist/web/assets/erDiagram-JOGREHBK-Dju9_T31.js +85 -0
  157. package/dist/web/assets/erb-BYTLMnw6.js +1 -0
  158. package/dist/web/assets/erlang-B-DoSBHF.js +1 -0
  159. package/dist/web/assets/everforest-dark-BgDCqdQA.js +1 -0
  160. package/dist/web/assets/everforest-light-C8M2exoo.js +1 -0
  161. package/dist/web/assets/fennel-bCA53EVm.js +1 -0
  162. package/dist/web/assets/fish-w-ucz2PV.js +1 -0
  163. package/dist/web/assets/flowDiagram-UKHOOZJN-DGKxclCZ.js +156 -0
  164. package/dist/web/assets/fluent-Dayu4EKP.js +1 -0
  165. package/dist/web/assets/fortran-fixed-form-TqA4NnZg.js +1 -0
  166. package/dist/web/assets/fortran-free-form-DKXYxT9g.js +1 -0
  167. package/dist/web/assets/fsharp-XplgxFYe.js +1 -0
  168. package/dist/web/assets/ganttDiagram-PKOTCBZU-CO3GCXs9.js +292 -0
  169. package/dist/web/assets/gdresource-BHYsBjWJ.js +1 -0
  170. package/dist/web/assets/gdscript-DfxzS6Rs.js +1 -0
  171. package/dist/web/assets/gdshader-SKMF96pI.js +1 -0
  172. package/dist/web/assets/genie-ajMbGru0.js +1 -0
  173. package/dist/web/assets/gherkin--30QC5Em.js +1 -0
  174. package/dist/web/assets/git-commit-i4q6IMui.js +1 -0
  175. package/dist/web/assets/git-rebase-B-v9cOL2.js +1 -0
  176. package/dist/web/assets/gitGraphDiagram-DS77QQ5N-HJ2a9e8_.js +106 -0
  177. package/dist/web/assets/github-dark-DHJKELXO.js +1 -0
  178. package/dist/web/assets/github-dark-default-Cuk6v7N8.js +1 -0
  179. package/dist/web/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  180. package/dist/web/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  181. package/dist/web/assets/github-light-DAi9KRSo.js +1 -0
  182. package/dist/web/assets/github-light-default-D7oLnXFd.js +1 -0
  183. package/dist/web/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  184. package/dist/web/assets/gleam-B430Bg39.js +1 -0
  185. package/dist/web/assets/glimmer-js-D-cwc0-E.js +1 -0
  186. package/dist/web/assets/glimmer-ts-pgjy16dm.js +1 -0
  187. package/dist/web/assets/glsl-DBO2IWDn.js +1 -0
  188. package/dist/web/assets/gnuplot-CM8KxXT1.js +1 -0
  189. package/dist/web/assets/go-B1SYOhNW.js +1 -0
  190. package/dist/web/assets/graph-C9eacEi8.js +1 -0
  191. package/dist/web/assets/graphql-cDcHW_If.js +1 -0
  192. package/dist/web/assets/groovy-DkBy-JyN.js +1 -0
  193. package/dist/web/assets/hack-D1yCygmZ.js +1 -0
  194. package/dist/web/assets/haml-B2EZWmdv.js +1 -0
  195. package/dist/web/assets/handlebars-BQGss363.js +1 -0
  196. package/dist/web/assets/haskell-BILxekzW.js +1 -0
  197. package/dist/web/assets/haxe-C5wWYbrZ.js +1 -0
  198. package/dist/web/assets/hcl-HzYwdGDm.js +1 -0
  199. package/dist/web/assets/hjson-T-Tgc4AT.js +1 -0
  200. package/dist/web/assets/hlsl-ifBTmRxC.js +1 -0
  201. package/dist/web/assets/houston-DnULxvSX.js +1 -0
  202. package/dist/web/assets/html-C2L_23MC.js +1 -0
  203. package/dist/web/assets/html-derivative-CSfWNPLT.js +1 -0
  204. package/dist/web/assets/http-FRrOvY1W.js +1 -0
  205. package/dist/web/assets/hxml-TIA70rKU.js +1 -0
  206. package/dist/web/assets/hy-BMj5Y0dO.js +1 -0
  207. package/dist/web/assets/imba-bv_oIlVt.js +1 -0
  208. package/dist/web/assets/index-56vlRQsM.js +342 -0
  209. package/dist/web/assets/index-CZ8H0616.css +1 -0
  210. package/dist/web/assets/index-CguDTw5b.js +13 -0
  211. package/dist/web/assets/infoDiagram-6WML65LV-BaVcr3f7.js +2 -0
  212. package/dist/web/assets/ini-BjABl1g7.js +1 -0
  213. package/dist/web/assets/init-Gi6I4Gst.js +1 -0
  214. package/dist/web/assets/ishikawaDiagram-WSZJBQD7-CBW9kPX2.js +70 -0
  215. package/dist/web/assets/java-xI-RfyKK.js +1 -0
  216. package/dist/web/assets/javascript-ySlJ1b_l.js +1 -0
  217. package/dist/web/assets/jinja-DGy0s7-h.js +1 -0
  218. package/dist/web/assets/jison-BqZprYcd.js +1 -0
  219. package/dist/web/assets/journeyDiagram-NVQOT4AX-ByxvAnge.js +139 -0
  220. package/dist/web/assets/json-BQoSv7ci.js +1 -0
  221. package/dist/web/assets/json5-w8dY5SsB.js +1 -0
  222. package/dist/web/assets/jsonc-TU54ms6u.js +1 -0
  223. package/dist/web/assets/jsonl-DREVFZK8.js +1 -0
  224. package/dist/web/assets/jsonnet-BfivnA6A.js +1 -0
  225. package/dist/web/assets/jssm-P4WzXJd0.js +1 -0
  226. package/dist/web/assets/jsx-BAng5TT0.js +1 -0
  227. package/dist/web/assets/julia-BBuGR-5E.js +1 -0
  228. package/dist/web/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  229. package/dist/web/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  230. package/dist/web/assets/kanagawa-wave-DWedfzmr.js +1 -0
  231. package/dist/web/assets/kanban-definition-27J2QSJJ-Cax1fcgb.js +89 -0
  232. package/dist/web/assets/kotlin-B5lbUyaz.js +1 -0
  233. package/dist/web/assets/kusto-mebxcVVE.js +1 -0
  234. package/dist/web/assets/laserwave-DUszq2jm.js +1 -0
  235. package/dist/web/assets/latex-C-cWTeAZ.js +1 -0
  236. package/dist/web/assets/layout-DEXfKzaS.js +1 -0
  237. package/dist/web/assets/lean-XBlWyCtg.js +1 -0
  238. package/dist/web/assets/less-BfCpw3nA.js +1 -0
  239. package/dist/web/assets/light-plus-B7mTdjB0.js +1 -0
  240. package/dist/web/assets/linear-CBgLWOFx.js +1 -0
  241. package/dist/web/assets/liquid-D3W5UaiH.js +1 -0
  242. package/dist/web/assets/log-Cc5clBb7.js +1 -0
  243. package/dist/web/assets/logo-IuBKFhSY.js +1 -0
  244. package/dist/web/assets/lua-CvWAzNxB.js +1 -0
  245. package/dist/web/assets/luau-Du5NY7AG.js +1 -0
  246. package/dist/web/assets/make-Bvotw-X0.js +1 -0
  247. package/dist/web/assets/map-Czzmt4hB.js +1 -0
  248. package/dist/web/assets/markdown-UIAJJxZW.js +1 -0
  249. package/dist/web/assets/marko-z0MBrx5-.js +1 -0
  250. package/dist/web/assets/material-theme-D5KoaKCx.js +1 -0
  251. package/dist/web/assets/material-theme-darker-BfHTSMKl.js +1 -0
  252. package/dist/web/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  253. package/dist/web/assets/material-theme-ocean-CyktbL80.js +1 -0
  254. package/dist/web/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  255. package/dist/web/assets/matlab-D9-PGadD.js +1 -0
  256. package/dist/web/assets/mdc-DB_EDNY_.js +1 -0
  257. package/dist/web/assets/mdx-sdHcTMYB.js +1 -0
  258. package/dist/web/assets/mermaid-Ci6OQyBP.js +1 -0
  259. package/dist/web/assets/mermaid.core-B3afE_Bn.js +314 -0
  260. package/dist/web/assets/min-dark-CafNBF8u.js +1 -0
  261. package/dist/web/assets/min-light-CTRr51gU.js +1 -0
  262. package/dist/web/assets/mindmap-definition-FAOFIHXS-sS1s_igC.js +96 -0
  263. package/dist/web/assets/mipsasm-BC5c_5Pe.js +1 -0
  264. package/dist/web/assets/mojo-Tz6hzZYG.js +1 -0
  265. package/dist/web/assets/monokai-D4h5O-jR.js +1 -0
  266. package/dist/web/assets/move-DB_GagMm.js +1 -0
  267. package/dist/web/assets/narrat-DLbgOhZU.js +1 -0
  268. package/dist/web/assets/nextflow-B0XVJmRM.js +1 -0
  269. package/dist/web/assets/nginx-D_VnBJ67.js +1 -0
  270. package/dist/web/assets/night-owl-C39BiMTA.js +1 -0
  271. package/dist/web/assets/nim-ZlGxZxc3.js +1 -0
  272. package/dist/web/assets/nix-shcSOmrb.js +1 -0
  273. package/dist/web/assets/nord-Ddv68eIx.js +1 -0
  274. package/dist/web/assets/nushell-D4Tzg5kh.js +1 -0
  275. package/dist/web/assets/objective-c-Deuh7S70.js +1 -0
  276. package/dist/web/assets/objective-cpp-BUEGK8hf.js +1 -0
  277. package/dist/web/assets/ocaml-BNioltXt.js +1 -0
  278. package/dist/web/assets/one-dark-pro-GBQ2dnAY.js +1 -0
  279. package/dist/web/assets/one-light-PoHY5YXO.js +1 -0
  280. package/dist/web/assets/ordinal-Cboi1Yqb.js +1 -0
  281. package/dist/web/assets/pascal-JqZropPD.js +1 -0
  282. package/dist/web/assets/pegDiagram-VL7TDLO6-CCQpyXjO.js +1 -0
  283. package/dist/web/assets/perl-CHQXSrWU.js +1 -0
  284. package/dist/web/assets/php-B5ebYQev.js +1 -0
  285. package/dist/web/assets/pieDiagram-7S7Q4E2Y-CZ_Lof4Z.js +39 -0
  286. package/dist/web/assets/plastic-3e1v2bzS.js +1 -0
  287. package/dist/web/assets/plsql-LKU2TuZ1.js +1 -0
  288. package/dist/web/assets/po-BFLt1xDp.js +1 -0
  289. package/dist/web/assets/poimandres-CS3Unz2-.js +1 -0
  290. package/dist/web/assets/polar-DKykz6zU.js +1 -0
  291. package/dist/web/assets/postcss-B3ZDOciz.js +1 -0
  292. package/dist/web/assets/powerquery-CSHBycmS.js +1 -0
  293. package/dist/web/assets/powershell-BIEUsx6d.js +1 -0
  294. package/dist/web/assets/prisma-B48N-Iqd.js +1 -0
  295. package/dist/web/assets/prolog-BY-TUvya.js +1 -0
  296. package/dist/web/assets/proto-zocC4JxJ.js +1 -0
  297. package/dist/web/assets/pug-CM9l7STV.js +1 -0
  298. package/dist/web/assets/puppet-Cza_XSSt.js +1 -0
  299. package/dist/web/assets/purescript-Bg-kzb6g.js +1 -0
  300. package/dist/web/assets/python-DhUJRlN_.js +1 -0
  301. package/dist/web/assets/qml-D8XfuvdV.js +1 -0
  302. package/dist/web/assets/qmldir-C8lEn-DE.js +1 -0
  303. package/dist/web/assets/qss-DhMKtDLN.js +1 -0
  304. package/dist/web/assets/quadrantDiagram-CIZ2JOQS-hJaOjkKi.js +7 -0
  305. package/dist/web/assets/r-CwjWoCRV.js +1 -0
  306. package/dist/web/assets/racket-CzouJOBO.js +1 -0
  307. package/dist/web/assets/railroadDiagram-AXF67PYL-D9ueOsqF.js +1 -0
  308. package/dist/web/assets/raku-B1bQXN8T.js +1 -0
  309. package/dist/web/assets/razor-CNLDkMZG.js +1 -0
  310. package/dist/web/assets/red-bN70gL4F.js +1 -0
  311. package/dist/web/assets/reg-5LuOXUq_.js +1 -0
  312. package/dist/web/assets/regexp-DWJ3fJO_.js +1 -0
  313. package/dist/web/assets/rel-DJlmqQ1C.js +1 -0
  314. package/dist/web/assets/requirementDiagram-LRYGKXZP-CmMkOQTX.js +84 -0
  315. package/dist/web/assets/riscv-QhoSD0DR.js +1 -0
  316. package/dist/web/assets/rose-pine-CmCqftbK.js +1 -0
  317. package/dist/web/assets/rose-pine-dawn-Ds-gbosJ.js +1 -0
  318. package/dist/web/assets/rose-pine-moon-CjDtw9vr.js +1 -0
  319. package/dist/web/assets/rst-4NLicBqY.js +1 -0
  320. package/dist/web/assets/ruby-DeZ3UC14.js +1 -0
  321. package/dist/web/assets/rust-Be6lgOlo.js +1 -0
  322. package/dist/web/assets/sankeyDiagram-W5VNT64P-CXMCYDJB.js +40 -0
  323. package/dist/web/assets/sas-BmTFh92c.js +1 -0
  324. package/dist/web/assets/sass-BJ4Li9vH.js +1 -0
  325. package/dist/web/assets/scala-DQVVAn-B.js +1 -0
  326. package/dist/web/assets/scheme-BJGe-b2p.js +1 -0
  327. package/dist/web/assets/scss-C31hgJw-.js +1 -0
  328. package/dist/web/assets/sdbl-BLhTXw86.js +1 -0
  329. package/dist/web/assets/sequenceDiagram-SI44F4Z6-iT0A9JkH.js +162 -0
  330. package/dist/web/assets/shaderlab-B7qAK45m.js +1 -0
  331. package/dist/web/assets/shellscript-atvbtKCR.js +1 -0
  332. package/dist/web/assets/shellsession-C_rIy8kc.js +1 -0
  333. package/dist/web/assets/sizeCapture-X5ZJPWSS-mUYstuDe.js +1 -0
  334. package/dist/web/assets/slack-dark-BthQWCQV.js +1 -0
  335. package/dist/web/assets/slack-ochin-DqwNpetd.js +1 -0
  336. package/dist/web/assets/smalltalk-DkLiglaE.js +1 -0
  337. package/dist/web/assets/snazzy-light-Bw305WKR.js +1 -0
  338. package/dist/web/assets/solarized-dark-DXbdFlpD.js +1 -0
  339. package/dist/web/assets/solarized-light-L9t79GZl.js +1 -0
  340. package/dist/web/assets/solidity-C1w2a3ep.js +1 -0
  341. package/dist/web/assets/soy-C-lX7w71.js +1 -0
  342. package/dist/web/assets/sparql-bYkjHRlG.js +1 -0
  343. package/dist/web/assets/splunk-Cf8iN4DR.js +1 -0
  344. package/dist/web/assets/sql-COK4E0Yg.js +1 -0
  345. package/dist/web/assets/ssh-config-BknIz3MU.js +1 -0
  346. package/dist/web/assets/stata-DorPZHa4.js +1 -0
  347. package/dist/web/assets/stateDiagram-OKZ733FA-B7jqZn_1.js +1 -0
  348. package/dist/web/assets/stateDiagram-v2-UEYNNEHI-5syMa302.js +1 -0
  349. package/dist/web/assets/stylus-BeQkCIfX.js +1 -0
  350. package/dist/web/assets/svelte-MSaWC3Je.js +1 -0
  351. package/dist/web/assets/swift-BSxZ-RaX.js +1 -0
  352. package/dist/web/assets/swimlanes-SLNWSIFB-B3iAV6cM.js +2 -0
  353. package/dist/web/assets/swimlanesDiagram-ULZ7WXOC-CwnN5SDz.js +8 -0
  354. package/dist/web/assets/synthwave-84-CbfX1IO0.js +1 -0
  355. package/dist/web/assets/system-verilog-C7L56vO4.js +1 -0
  356. package/dist/web/assets/systemd-CUnW07Te.js +1 -0
  357. package/dist/web/assets/talonscript-C1XDQQGZ.js +1 -0
  358. package/dist/web/assets/tasl-CQjiPCtT.js +1 -0
  359. package/dist/web/assets/tcl-DQ1-QYvQ.js +1 -0
  360. package/dist/web/assets/templ-dwX3ZSMB.js +1 -0
  361. package/dist/web/assets/terraform-BbSNqyBO.js +1 -0
  362. package/dist/web/assets/tex-rYs2v40G.js +1 -0
  363. package/dist/web/assets/timeline-definition-Z64GVDOM-KdcKK-sf.js +120 -0
  364. package/dist/web/assets/tokyo-night-DBQeEorK.js +1 -0
  365. package/dist/web/assets/toml-CB2ApiWb.js +1 -0
  366. package/dist/web/assets/ts-tags-CipyTH0X.js +1 -0
  367. package/dist/web/assets/tsv-B_m7g4N7.js +1 -0
  368. package/dist/web/assets/tsx-B6W0miNI.js +1 -0
  369. package/dist/web/assets/turtle-BMR_PYu6.js +1 -0
  370. package/dist/web/assets/twig-NC5TFiHP.js +1 -0
  371. package/dist/web/assets/typescript-Dj6nwHGl.js +1 -0
  372. package/dist/web/assets/typespec-BpWG_bgh.js +1 -0
  373. package/dist/web/assets/typst-BVUVsWT6.js +1 -0
  374. package/dist/web/assets/v-CAQ2eGtk.js +1 -0
  375. package/dist/web/assets/vala-BFOHcciG.js +1 -0
  376. package/dist/web/assets/vb-CdO5JTpU.js +1 -0
  377. package/dist/web/assets/vennDiagram-T6HMQDX7-rJqmKTgU.js +34 -0
  378. package/dist/web/assets/verilog-CJaU5se_.js +1 -0
  379. package/dist/web/assets/vesper-BEBZ7ncR.js +1 -0
  380. package/dist/web/assets/vhdl-DYoNaHQp.js +1 -0
  381. package/dist/web/assets/viml-m4uW47V2.js +1 -0
  382. package/dist/web/assets/vitesse-black-Bkuqu6BP.js +1 -0
  383. package/dist/web/assets/vitesse-dark-D0r3Knsf.js +1 -0
  384. package/dist/web/assets/vitesse-light-CVO1_9PV.js +1 -0
  385. package/dist/web/assets/vue-BuYVFjOK.js +1 -0
  386. package/dist/web/assets/vue-html-xdeiXROB.js +1 -0
  387. package/dist/web/assets/vyper-nyqBNV6O.js +1 -0
  388. package/dist/web/assets/wardleyDiagram-T6FBY63Y-CI33_pUs.js +78 -0
  389. package/dist/web/assets/wasm-C6j12Q_x.js +1 -0
  390. package/dist/web/assets/wasm-CG6Dc4jp.js +1 -0
  391. package/dist/web/assets/wenyan-7A4Fjokl.js +1 -0
  392. package/dist/web/assets/wgsl-CB0Krxn9.js +1 -0
  393. package/dist/web/assets/wikitext-DCE3LsBG.js +1 -0
  394. package/dist/web/assets/wolfram-C3FkfJm5.js +1 -0
  395. package/dist/web/assets/xml-e3z08dGr.js +1 -0
  396. package/dist/web/assets/xsl-Dd0NUgwM.js +1 -0
  397. package/dist/web/assets/xychartDiagram-ELKLHX3M-C1TnCm9i.js +7 -0
  398. package/dist/web/assets/yaml-CVw76BM1.js +1 -0
  399. package/dist/web/assets/zenscript-HnGAYVZD.js +1 -0
  400. package/dist/web/assets/zig-BVz_zdnA.js +1 -0
  401. package/dist/web/casper.png +0 -0
  402. package/dist/web/index.html +27 -0
  403. package/dist/web/logo.svg +30 -0
  404. package/dist/web/manifest.webmanifest +1 -0
  405. package/dist/web/registerSW.js +1 -0
  406. package/dist/web/sw.js +1 -0
  407. package/dist/web/workbox-9c191d2f.js +1 -0
  408. package/package.json +61 -0
package/dist/server.js ADDED
@@ -0,0 +1,3506 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __esm = (fn, res) => function __init() {
4
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
+ };
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+
11
+ // server/src/paths.ts
12
+ import os from "node:os";
13
+ import path from "node:path";
14
+ function fromEnv(name) {
15
+ const v = process.env[name];
16
+ return v === void 0 || v.trim() === "" ? void 0 : v;
17
+ }
18
+ function configFilePath() {
19
+ const base = fromEnv("XDG_CONFIG_HOME") ?? path.join(os.homedir(), ".config");
20
+ return path.join(base, "casper", "config.json");
21
+ }
22
+ function dataDirPath() {
23
+ return fromEnv("CASPER_DATA_DIR") ?? path.join(os.homedir(), ".casper");
24
+ }
25
+ var init_paths = __esm({
26
+ "server/src/paths.ts"() {
27
+ "use strict";
28
+ }
29
+ });
30
+
31
+ // server/src/cli/settings.ts
32
+ import crypto from "node:crypto";
33
+ import fs from "node:fs";
34
+ import path2 from "node:path";
35
+ function readSettings(file) {
36
+ try {
37
+ const doc = JSON.parse(fs.readFileSync(file, "utf8"));
38
+ if (typeof doc !== "object" || doc === null || Array.isArray(doc)) return {};
39
+ return doc;
40
+ } catch {
41
+ return {};
42
+ }
43
+ }
44
+ function updateSettings(file, changes) {
45
+ const merged = { ...readSettings(file), ...changes };
46
+ fs.mkdirSync(path2.dirname(file), { recursive: true });
47
+ const tmp = `${file}.${process.pid}.tmp`;
48
+ fs.writeFileSync(tmp, `${JSON.stringify(merged, null, 2)}
49
+ `, { mode: 384 });
50
+ fs.renameSync(tmp, file);
51
+ }
52
+ function generateToken() {
53
+ return crypto.randomBytes(24).toString("hex");
54
+ }
55
+ function printTokenBlock(token, heading) {
56
+ const line = "\u2500".repeat(Math.max(heading.length, token.length) + 2);
57
+ process.stdout.write(
58
+ `
59
+ \u250C${line}\u2510
60
+ \u2502 ${heading.padEnd(line.length - 2)} \u2502
61
+ \u2502 ${"".padEnd(line.length - 2)} \u2502
62
+ \u2502 ${token.padEnd(line.length - 2)} \u2502
63
+ \u2514${line}\u2518
64
+
65
+ `
66
+ );
67
+ }
68
+ var init_settings = __esm({
69
+ "server/src/cli/settings.ts"() {
70
+ "use strict";
71
+ }
72
+ });
73
+
74
+ // server/src/cli/agentFile.ts
75
+ var agentFile_exports = {};
76
+ __export(agentFile_exports, {
77
+ installAgentFile: () => installAgentFile
78
+ });
79
+ import crypto2 from "node:crypto";
80
+ import fs2 from "node:fs";
81
+ import path3 from "node:path";
82
+ import url from "node:url";
83
+ function sourceFile() {
84
+ const here = path3.dirname(url.fileURLToPath(import.meta.url));
85
+ const packaged = path3.resolve(here, "agents/casper.json");
86
+ const workspace = path3.resolve(here, "../../../assets/agents/casper.json");
87
+ return fs2.existsSync(packaged) ? packaged : workspace;
88
+ }
89
+ function sha256(text) {
90
+ return crypto2.createHash("sha256").update(text).digest("hex");
91
+ }
92
+ function installAgentFile(home2, dataDir) {
93
+ const src = sourceFile();
94
+ const target = path3.join(home2, ".kiro", "agents", "casper.json");
95
+ if (!fs2.existsSync(src)) return { action: "no-source", target };
96
+ const desired = fs2.readFileSync(src, "utf8");
97
+ const stampFile = path3.join(dataDir, "agent-stamp");
98
+ const stamp = fs2.existsSync(stampFile) ? fs2.readFileSync(stampFile, "utf8").trim() : "";
99
+ const write = () => {
100
+ fs2.mkdirSync(path3.dirname(target), { recursive: true });
101
+ fs2.writeFileSync(target, desired);
102
+ fs2.mkdirSync(dataDir, { recursive: true });
103
+ fs2.writeFileSync(stampFile, `${sha256(desired)}
104
+ `);
105
+ };
106
+ let current;
107
+ try {
108
+ current = fs2.lstatSync(target);
109
+ } catch {
110
+ current = void 0;
111
+ }
112
+ if (!current) {
113
+ write();
114
+ return { action: "installed", target };
115
+ }
116
+ if (current.isSymbolicLink()) {
117
+ fs2.rmSync(target);
118
+ write();
119
+ return { action: "updated", target };
120
+ }
121
+ const onDisk = fs2.readFileSync(target, "utf8");
122
+ if (sha256(onDisk) === sha256(desired)) return { action: "unchanged", target };
123
+ if (stamp !== "" && sha256(onDisk) === stamp) {
124
+ write();
125
+ return { action: "updated", target };
126
+ }
127
+ return { action: "kept-yours", target };
128
+ }
129
+ var init_agentFile = __esm({
130
+ "server/src/cli/agentFile.ts"() {
131
+ "use strict";
132
+ }
133
+ });
134
+
135
+ // server/src/util/logger.ts
136
+ import pino from "pino";
137
+ var logger;
138
+ var init_logger = __esm({
139
+ "server/src/util/logger.ts"() {
140
+ "use strict";
141
+ logger = pino({
142
+ level: process.env.LOG_LEVEL ?? "info",
143
+ transport: process.env.NODE_ENV === "production" ? void 0 : {
144
+ target: "pino/file",
145
+ options: { destination: 2 }
146
+ // stderr, so stdout stays clean
147
+ }
148
+ });
149
+ }
150
+ });
151
+
152
+ // server/src/config.ts
153
+ import { execFileSync } from "node:child_process";
154
+ import fs3 from "node:fs";
155
+ import os2 from "node:os";
156
+ import path4 from "node:path";
157
+ import url2 from "node:url";
158
+ function env(name, fallback) {
159
+ const v = process.env[name];
160
+ return v === void 0 || v === "" ? fallback : v;
161
+ }
162
+ function defaultWebDist() {
163
+ const here = path4.dirname(url2.fileURLToPath(import.meta.url));
164
+ const bundled = path4.join(here, "web");
165
+ const workspace = path4.resolve(here, "../../web/dist");
166
+ return fs3.existsSync(bundled) ? bundled : workspace;
167
+ }
168
+ function parseConfigDoc(raw, onWarn = () => {
169
+ }) {
170
+ let parsed;
171
+ try {
172
+ parsed = JSON.parse(raw);
173
+ } catch (err) {
174
+ onWarn("config: invalid JSON, ignoring the file entirely", err);
175
+ return {};
176
+ }
177
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
178
+ onWarn("config: expected a JSON object, ignoring");
179
+ return {};
180
+ }
181
+ const obj = parsed;
182
+ const unknown = Object.keys(obj).filter((k) => !KNOWN_KEYS.has(k));
183
+ if (unknown.length > 0) onWarn("config: ignoring unrecognised keys", unknown);
184
+ return obj;
185
+ }
186
+ function loadConfigFile() {
187
+ const file = configFilePath();
188
+ let raw;
189
+ try {
190
+ raw = fs3.readFileSync(file, "utf8");
191
+ } catch (err) {
192
+ if (err.code !== "ENOENT") {
193
+ logger.warn({ file, err }, "config: unreadable, falling back to defaults");
194
+ }
195
+ return {};
196
+ }
197
+ const obj = parseConfigDoc(raw, (msg, detail) => logger.warn({ file, detail }, msg));
198
+ if (typeof obj.token === "string" && obj.token !== "") {
199
+ try {
200
+ const mode = fs3.statSync(file).mode & 63;
201
+ if (mode !== 0) {
202
+ logger.warn({ file }, "config: holds a token but is readable by others; chmod 600 it");
203
+ }
204
+ } catch {
205
+ }
206
+ }
207
+ return obj;
208
+ }
209
+ function pickString(fromEnv2, fromFile, fallback) {
210
+ if (fromEnv2 !== void 0 && fromEnv2 !== "") return fromEnv2;
211
+ if (typeof fromFile === "string" && fromFile !== "") return fromFile;
212
+ return fallback;
213
+ }
214
+ function pickInt(fromEnv2, fromFile, fallback) {
215
+ if (fromEnv2 !== void 0 && fromEnv2 !== "") {
216
+ const n = Number.parseInt(fromEnv2, 10);
217
+ if (Number.isFinite(n)) return n;
218
+ }
219
+ if (typeof fromFile === "number" && Number.isFinite(fromFile)) return fromFile;
220
+ if (typeof fromFile === "string") {
221
+ const n = Number.parseInt(fromFile, 10);
222
+ if (Number.isFinite(n)) return n;
223
+ }
224
+ return fallback;
225
+ }
226
+ function resolveKiroBin(explicit, home2) {
227
+ if (explicit.includes("/") && fs3.existsSync(explicit)) return explicit;
228
+ try {
229
+ const found = execFileSync("/bin/sh", ["-lc", `command -v ${explicit}`], {
230
+ encoding: "utf8"
231
+ }).trim();
232
+ if (found && fs3.existsSync(found)) return found;
233
+ } catch {
234
+ }
235
+ const candidates = [
236
+ path4.join(home2, ".toolbox", "bin", explicit),
237
+ path4.join(home2, ".local", "bin", explicit),
238
+ path4.join("/usr", "local", "bin", explicit),
239
+ path4.join("/opt", "homebrew", "bin", explicit)
240
+ ];
241
+ for (const c of candidates) {
242
+ if (fs3.existsSync(c)) return c;
243
+ }
244
+ return explicit;
245
+ }
246
+ var home, KNOWN_KEYS, fileConfig, setting, settingInt, config;
247
+ var init_config = __esm({
248
+ "server/src/config.ts"() {
249
+ "use strict";
250
+ init_paths();
251
+ init_logger();
252
+ home = os2.homedir();
253
+ KNOWN_KEYS = /* @__PURE__ */ new Set([
254
+ "host",
255
+ "port",
256
+ "token",
257
+ "sessionTtlSeconds",
258
+ "kiroBin",
259
+ "defaultCwd",
260
+ "maxLiveSessions",
261
+ "defaultAgent",
262
+ "fileRoot",
263
+ "eventBufferSize",
264
+ "maxUploadBytes"
265
+ ]);
266
+ fileConfig = loadConfigFile();
267
+ setting = (envName, key, fallback) => pickString(process.env[envName], fileConfig[key], fallback);
268
+ settingInt = (envName, key, fallback) => pickInt(process.env[envName], fileConfig[key], fallback);
269
+ config = {
270
+ host: setting("HOST", "host", "0.0.0.0"),
271
+ port: settingInt("PORT", "port", 4319),
272
+ /** Shared-secret token required to log in. Empty string disables auth (dev only). */
273
+ token: setting("CASPER_TOKEN", "token", ""),
274
+ /** Device-login lifetime in seconds; slid forward on activity. Default 7 days. */
275
+ sessionTtlSeconds: settingInt("CASPER_SESSION_TTL_SECONDS", "sessionTtlSeconds", 60 * 60 * 24 * 7),
276
+ kiroBin: resolveKiroBin(setting("KIRO_BIN", "kiroBin", "kiro-cli"), home),
277
+ defaultCwd: setting("DEFAULT_CWD", "defaultCwd", process.cwd()),
278
+ maxLiveSessions: settingInt("MAX_LIVE_SESSIONS", "maxLiveSessions", 6),
279
+ defaultAgent: setting("DEFAULT_AGENT", "defaultAgent", "kiro_default"),
280
+ /**
281
+ * Filesystem root that file-serving endpoints (/api/fs/dirs, /api/fs/image)
282
+ * are confined to. Requests resolving outside this root are rejected.
283
+ * Defaults to the filesystem root (/), so file browsing spans everything the
284
+ * server process can read. Set CASPER_FILE_ROOT to a narrower path (e.g. the
285
+ * user's home directory) to stop authenticated users from reading system
286
+ * files such as /etc or SSH keys.
287
+ */
288
+ fileRoot: path4.resolve(setting("CASPER_FILE_ROOT", "fileRoot", "/")),
289
+ /** Directory where kiro-cli persists its own session files. */
290
+ kiroSessionsDir: path4.join(home, ".kiro", "sessions", "cli"),
291
+ /** Where casper.db lives. Env-only: it says where data is, and the config
292
+ * file isn't there. */
293
+ casperDataDir: dataDirPath(),
294
+ /** Built web app to serve. Env-only: install layout, not a user preference. */
295
+ webDist: env("CASPER_WEB_DIST", defaultWebDist()),
296
+ /** Per-session in-memory event ring buffer size. */
297
+ eventBufferSize: settingInt("EVENT_BUFFER_SIZE", "eventBufferSize", 5e3),
298
+ /** Max size (bytes) for a single uploaded file. Default 100 MB. */
299
+ maxUploadBytes: settingInt("CASPER_MAX_UPLOAD_BYTES", "maxUploadBytes", 100 * 1024 * 1024),
300
+ /** Absolute path of the settings file, for diagnostics. */
301
+ configFile: configFilePath()
302
+ };
303
+ }
304
+ });
305
+
306
+ // server/src/session/db.ts
307
+ import fs4 from "node:fs";
308
+ import path5 from "node:path";
309
+ import { DatabaseSync } from "node:sqlite";
310
+ function db() {
311
+ if (!handle) handle = open();
312
+ return handle;
313
+ }
314
+ function open() {
315
+ fs4.mkdirSync(config.casperDataDir, { recursive: true, mode: 448 });
316
+ const file = path5.join(config.casperDataDir, "casper.db");
317
+ const d = new DatabaseSync(file);
318
+ d.exec("PRAGMA journal_mode = WAL");
319
+ d.exec(SCHEMA);
320
+ restrict(config.casperDataDir, 448);
321
+ for (const f of [file, `${file}-wal`, `${file}-shm`]) restrict(f, 384);
322
+ return d;
323
+ }
324
+ function restrict(target, mode) {
325
+ try {
326
+ if ((fs4.statSync(target).mode & 511) !== mode) fs4.chmodSync(target, mode);
327
+ } catch {
328
+ }
329
+ }
330
+ var SCHEMA, handle;
331
+ var init_db = __esm({
332
+ "server/src/session/db.ts"() {
333
+ "use strict";
334
+ init_config();
335
+ SCHEMA = `
336
+ CREATE TABLE IF NOT EXISTS sessions (
337
+ session_id TEXT PRIMARY KEY,
338
+ title TEXT,
339
+ cwd TEXT
340
+ );
341
+ CREATE TABLE IF NOT EXISTS logins (
342
+ id TEXT PRIMARY KEY,
343
+ hash TEXT NOT NULL UNIQUE,
344
+ created_at TEXT NOT NULL,
345
+ last_seen_at TEXT NOT NULL,
346
+ user_agent TEXT
347
+ );
348
+ CREATE INDEX IF NOT EXISTS logins_hash ON logins (hash);
349
+ `;
350
+ }
351
+ });
352
+
353
+ // server/src/session/logins.ts
354
+ var logins_exports = {};
355
+ __export(logins_exports, {
356
+ LoginStore: () => LoginStore
357
+ });
358
+ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
359
+ function sha2562(s) {
360
+ return createHash("sha256").update(s).digest("hex");
361
+ }
362
+ function sameHash(a, b) {
363
+ if (a.length !== b.length) return false;
364
+ return timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
365
+ }
366
+ var LAST_SEEN_WRITE_INTERVAL_MS, str, toRecord, LoginStore;
367
+ var init_logins = __esm({
368
+ "server/src/session/logins.ts"() {
369
+ "use strict";
370
+ init_config();
371
+ init_db();
372
+ LAST_SEEN_WRITE_INTERVAL_MS = 6e4;
373
+ str = (v) => typeof v === "string" ? v : "";
374
+ toRecord = (r) => ({
375
+ id: str(r.id),
376
+ hash: str(r.hash),
377
+ createdAt: str(r.created_at),
378
+ lastSeenAt: str(r.last_seen_at),
379
+ userAgent: typeof r.user_agent === "string" ? r.user_agent : void 0
380
+ });
381
+ LoginStore = class {
382
+ constructor() {
383
+ this.pruneExpired();
384
+ }
385
+ ttlMs() {
386
+ return config.sessionTtlSeconds * 1e3;
387
+ }
388
+ /** Drop logins whose last activity is older than the TTL. */
389
+ pruneExpired(now = Date.now()) {
390
+ const cutoff = new Date(now - this.ttlMs()).toISOString();
391
+ db().prepare("DELETE FROM logins WHERE last_seen_at < ?").run(cutoff);
392
+ }
393
+ /** Create a login. Returns the raw token to set as the cookie value. */
394
+ create(userAgent) {
395
+ const token = randomBytes(32).toString("base64url");
396
+ const nowIso = (/* @__PURE__ */ new Date()).toISOString();
397
+ const record = {
398
+ id: randomBytes(9).toString("base64url"),
399
+ hash: sha2562(token),
400
+ createdAt: nowIso,
401
+ lastSeenAt: nowIso,
402
+ userAgent
403
+ };
404
+ db().prepare(
405
+ `INSERT INTO logins (id, hash, created_at, last_seen_at, user_agent)
406
+ VALUES (?, ?, ?, ?, ?)`
407
+ ).run(record.id, record.hash, nowIso, nowIso, userAgent ?? null);
408
+ return { token, record };
409
+ }
410
+ /**
411
+ * Verify a raw token. Returns the record (sliding its expiry forward) or null
412
+ * if unknown/expired. Writes the lastSeen bump at most once a minute.
413
+ */
414
+ verify(token) {
415
+ if (!token) return null;
416
+ const now = Date.now();
417
+ const row = db().prepare("SELECT * FROM logins WHERE hash = ?").get(sha2562(token));
418
+ if (!row) return null;
419
+ const record = toRecord(row);
420
+ if (now - Date.parse(record.lastSeenAt) > this.ttlMs()) {
421
+ db().prepare("DELETE FROM logins WHERE id = ?").run(record.id);
422
+ return null;
423
+ }
424
+ if (now - Date.parse(record.lastSeenAt) >= LAST_SEEN_WRITE_INTERVAL_MS) {
425
+ record.lastSeenAt = new Date(now).toISOString();
426
+ db().prepare("UPDATE logins SET last_seen_at = ? WHERE id = ?").run(record.lastSeenAt, record.id);
427
+ }
428
+ return record;
429
+ }
430
+ /** List all active devices, marking the one owning `currentToken`. */
431
+ list(currentToken) {
432
+ this.pruneExpired();
433
+ const currentHash = currentToken ? sha2562(currentToken) : void 0;
434
+ const rows = db().prepare("SELECT * FROM logins ORDER BY last_seen_at DESC").all();
435
+ return rows.map(toRecord).map((r) => ({
436
+ id: r.id,
437
+ createdAt: r.createdAt,
438
+ lastSeenAt: r.lastSeenAt,
439
+ userAgent: r.userAgent,
440
+ current: currentHash !== void 0 && sameHash(r.hash, currentHash)
441
+ }));
442
+ }
443
+ /** Revoke the device holding this token (used on logout). */
444
+ revokeToken(token) {
445
+ if (!token) return;
446
+ db().prepare("DELETE FROM logins WHERE hash = ?").run(sha2562(token));
447
+ }
448
+ /** Revoke a device by its public id. Returns false if there was no such device. */
449
+ revokeId(id) {
450
+ const res = db().prepare("DELETE FROM logins WHERE id = ?").run(id);
451
+ return res.changes > 0;
452
+ }
453
+ /** Log out every device. */
454
+ revokeAll() {
455
+ db().prepare("DELETE FROM logins").run();
456
+ }
457
+ };
458
+ }
459
+ });
460
+
461
+ // server/src/cli/service.ts
462
+ var service_exports = {};
463
+ __export(service_exports, {
464
+ hasUserSystemd: () => hasUserSystemd,
465
+ serviceActive: () => serviceActive,
466
+ serviceInstall: () => serviceInstall,
467
+ serviceStatus: () => serviceStatus,
468
+ serviceUninstall: () => serviceUninstall
469
+ });
470
+ import { execFileSync as execFileSync2, spawnSync } from "node:child_process";
471
+ import fs5 from "node:fs";
472
+ import os3 from "node:os";
473
+ import path6 from "node:path";
474
+ import url3 from "node:url";
475
+ function unitPath() {
476
+ const base = process.env.XDG_CONFIG_HOME?.trim() || path6.join(os3.homedir(), ".config");
477
+ return path6.join(base, "systemd", "user", SERVICE);
478
+ }
479
+ function systemctl(args, quiet = false) {
480
+ const r = spawnSync("systemctl", ["--user", ...args], {
481
+ stdio: quiet ? "ignore" : "inherit"
482
+ });
483
+ return r.status ?? 1;
484
+ }
485
+ function hasUserSystemd() {
486
+ const r = spawnSync("systemctl", ["--user", "show-environment"], { stdio: "ignore" });
487
+ return r.status === 0;
488
+ }
489
+ function entryScript() {
490
+ const here = path6.dirname(url3.fileURLToPath(import.meta.url));
491
+ const bundled = path6.join(here, "casper.js");
492
+ if (fs5.existsSync(bundled)) return bundled;
493
+ return path6.resolve(here, "../index.js");
494
+ }
495
+ function serviceInstall() {
496
+ if (!hasUserSystemd()) {
497
+ process.stderr.write(
498
+ "casper: no user systemd here. Run Casper with `casper`, or start it from your own init system.\n"
499
+ );
500
+ return 1;
501
+ }
502
+ const unit = unitPath();
503
+ fs5.mkdirSync(path6.dirname(unit), { recursive: true });
504
+ fs5.writeFileSync(
505
+ unit,
506
+ `[Unit]
507
+ Description=Casper (kiro-cli web client)
508
+ After=network-online.target
509
+ Wants=network-online.target
510
+
511
+ [Service]
512
+ Type=simple
513
+ WorkingDirectory=${os3.homedir()}
514
+ Environment=NODE_ENV=production
515
+ ExecStart=${process.execPath} ${entryScript()}
516
+ Restart=on-failure
517
+ RestartSec=3
518
+
519
+ [Install]
520
+ WantedBy=default.target
521
+ `
522
+ );
523
+ process.stdout.write(`wrote ${unit}
524
+ `);
525
+ try {
526
+ execFileSync2("loginctl", ["enable-linger", os3.userInfo().username], { stdio: "ignore" });
527
+ } catch {
528
+ process.stderr.write(
529
+ `! could not enable linger; the service may stop at logout. Run: sudo loginctl enable-linger ${os3.userInfo().username}
530
+ `
531
+ );
532
+ }
533
+ systemctl(["daemon-reload"]);
534
+ if (systemctl(["enable", "--now", SERVICE]) !== 0) {
535
+ process.stderr.write(`! could not start the service. Check: systemctl --user status ${SERVICE}
536
+ `);
537
+ return 1;
538
+ }
539
+ process.stdout.write("casper is running as a systemd user service\n");
540
+ return 0;
541
+ }
542
+ function serviceUninstall() {
543
+ if (!hasUserSystemd()) {
544
+ process.stderr.write("casper: no user systemd here; nothing to remove.\n");
545
+ return 1;
546
+ }
547
+ systemctl(["disable", "--now", SERVICE], true);
548
+ const unit = unitPath();
549
+ if (fs5.existsSync(unit)) {
550
+ fs5.rmSync(unit);
551
+ process.stdout.write(`removed ${unit}
552
+ `);
553
+ }
554
+ systemctl(["daemon-reload"], true);
555
+ process.stdout.write("service removed; your sessions and settings are untouched\n");
556
+ return 0;
557
+ }
558
+ function serviceStatus() {
559
+ if (!hasUserSystemd()) {
560
+ process.stdout.write("no user systemd here; Casper is not running as a service\n");
561
+ return 0;
562
+ }
563
+ return systemctl(["status", "--no-pager", SERVICE]);
564
+ }
565
+ function serviceActive() {
566
+ return spawnSync("systemctl", ["--user", "is-active", "--quiet", SERVICE], { stdio: "ignore" }).status === 0;
567
+ }
568
+ var SERVICE;
569
+ var init_service = __esm({
570
+ "server/src/cli/service.ts"() {
571
+ "use strict";
572
+ SERVICE = "casper.service";
573
+ }
574
+ });
575
+
576
+ // shared/dist/acp.js
577
+ function isJsonRpcResponse(m) {
578
+ return "id" in m && m.id !== void 0 && ("result" in m || "error" in m);
579
+ }
580
+ function isJsonRpcError(m) {
581
+ return "error" in m;
582
+ }
583
+ function isJsonRpcNotification(m) {
584
+ return !("id" in m) && "method" in m;
585
+ }
586
+ var ACP_METHODS, KIRO_NOTIFICATIONS;
587
+ var init_acp = __esm({
588
+ "shared/dist/acp.js"() {
589
+ "use strict";
590
+ ACP_METHODS = {
591
+ initialize: "initialize",
592
+ sessionNew: "session/new",
593
+ sessionLoad: "session/load",
594
+ sessionPrompt: "session/prompt",
595
+ sessionCancel: "session/cancel",
596
+ sessionSetMode: "session/set_mode",
597
+ sessionSetModel: "session/set_model",
598
+ sessionUpdate: "session/update",
599
+ commandsExecute: "_kiro.dev/commands/execute"
600
+ };
601
+ KIRO_NOTIFICATIONS = {
602
+ metadata: "_kiro.dev/metadata",
603
+ subagentListUpdate: "_kiro.dev/subagent/list_update",
604
+ mcpServerInitialized: "_kiro.dev/mcp/server_initialized",
605
+ mcpServerInitFailure: "_kiro.dev/mcp/server_init_failure",
606
+ commandsAvailable: "_kiro.dev/commands/available",
607
+ mcpOauthRequest: "_kiro.dev/mcp/oauth_request",
608
+ compactionStatus: "_kiro.dev/compaction/status"
609
+ };
610
+ }
611
+ });
612
+
613
+ // shared/dist/attachments.js
614
+ function attachmentPaths(text) {
615
+ const line = text.split("\n").find((l) => l.startsWith(ATTACHMENTS_PREFIX));
616
+ if (!line)
617
+ return [];
618
+ return line.slice(ATTACHMENTS_PREFIX.length).split(", ").map((s) => s.trim()).filter(Boolean);
619
+ }
620
+ function imageAttachmentPaths(text) {
621
+ return attachmentPaths(text).filter((p) => {
622
+ const lower = p.toLowerCase();
623
+ return IMAGE_EXTS.some((ext) => lower.endsWith(ext));
624
+ });
625
+ }
626
+ function stripAttachmentsLine(text) {
627
+ return text.split("\n").filter((l) => !l.startsWith(ATTACHMENTS_PREFIX)).join("\n").trim();
628
+ }
629
+ var ATTACHMENTS_PREFIX, IMAGE_EXTS;
630
+ var init_attachments = __esm({
631
+ "shared/dist/attachments.js"() {
632
+ "use strict";
633
+ ATTACHMENTS_PREFIX = "Attached files: ";
634
+ IMAGE_EXTS = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".ico", ".avif"];
635
+ }
636
+ });
637
+
638
+ // shared/dist/observability.js
639
+ function emptyObservabilitySnapshot() {
640
+ return {
641
+ turnStatus: "idle",
642
+ creditsSpent: 0,
643
+ lastTurnCredits: 0,
644
+ contextUsagePercentage: 0,
645
+ lastTurnDurationMs: 0,
646
+ subagents: [],
647
+ pendingStages: [],
648
+ mcpServers: [],
649
+ availableCommands: [],
650
+ oauthPrompts: [],
651
+ compacting: false
652
+ };
653
+ }
654
+ var init_observability = __esm({
655
+ "shared/dist/observability.js"() {
656
+ "use strict";
657
+ }
658
+ });
659
+
660
+ // shared/dist/rest-dto.js
661
+ var init_rest_dto = __esm({
662
+ "shared/dist/rest-dto.js"() {
663
+ "use strict";
664
+ }
665
+ });
666
+
667
+ // shared/dist/ws-protocol.js
668
+ var init_ws_protocol = __esm({
669
+ "shared/dist/ws-protocol.js"() {
670
+ "use strict";
671
+ }
672
+ });
673
+
674
+ // shared/dist/index.js
675
+ var init_dist = __esm({
676
+ "shared/dist/index.js"() {
677
+ "use strict";
678
+ init_acp();
679
+ init_attachments();
680
+ init_observability();
681
+ init_rest_dto();
682
+ init_ws_protocol();
683
+ }
684
+ });
685
+
686
+ // server/src/util/paths.ts
687
+ import fs6 from "node:fs";
688
+ import fsp from "node:fs/promises";
689
+ import path7 from "node:path";
690
+ function isWithinRoot(root, target) {
691
+ const resolved = path7.resolve(target);
692
+ if (root === path7.sep) return true;
693
+ return resolved === root || resolved.startsWith(root + path7.sep);
694
+ }
695
+ function confineToRoot(root, input) {
696
+ const resolved = path7.resolve(root, input);
697
+ return isWithinRoot(root, resolved) ? resolved : null;
698
+ }
699
+ function resolveRealRoot(root) {
700
+ if (realRootCache && realRootCache.root === root) return realRootCache.real;
701
+ let real;
702
+ try {
703
+ real = fs6.realpathSync(root);
704
+ } catch {
705
+ real = root;
706
+ }
707
+ realRootCache = { root, real };
708
+ return real;
709
+ }
710
+ async function realConfineToRoot(root, absPath) {
711
+ let realTarget;
712
+ try {
713
+ realTarget = await fsp.realpath(absPath);
714
+ } catch {
715
+ return null;
716
+ }
717
+ return isWithinRoot(resolveRealRoot(root), realTarget) ? realTarget : null;
718
+ }
719
+ function isValidSessionId(id) {
720
+ return /^[A-Za-z0-9._-]+$/.test(id) && id !== "." && !id.includes("..");
721
+ }
722
+ var realRootCache;
723
+ var init_paths2 = __esm({
724
+ "server/src/util/paths.ts"() {
725
+ "use strict";
726
+ realRootCache = null;
727
+ }
728
+ });
729
+
730
+ // server/src/acp/errors.ts
731
+ function describeError(error) {
732
+ const detail = detailText(error.data);
733
+ return detail ? `${detail} (${error.message}, code ${error.code})` : `${error.message} (code ${error.code})`;
734
+ }
735
+ function detailText(data) {
736
+ if (data == null) return "";
737
+ if (typeof data === "string") return truncate(data.trim());
738
+ if (typeof data === "number" || typeof data === "boolean") return String(data);
739
+ if (typeof data === "object") {
740
+ const rec = data;
741
+ for (const key of ["message", "error", "detail", "details", "reason", "cause"]) {
742
+ const v = rec[key];
743
+ if (typeof v === "string" && v.trim()) return truncate(v.trim());
744
+ }
745
+ try {
746
+ return truncate(JSON.stringify(data));
747
+ } catch {
748
+ return "";
749
+ }
750
+ }
751
+ return "";
752
+ }
753
+ var MAX_DETAIL, truncate;
754
+ var init_errors = __esm({
755
+ "server/src/acp/errors.ts"() {
756
+ "use strict";
757
+ MAX_DETAIL = 2e3;
758
+ truncate = (s) => s.length > MAX_DETAIL ? `${s.slice(0, MAX_DETAIL)}\u2026 (truncated)` : s;
759
+ }
760
+ });
761
+
762
+ // server/src/acp/AcpClient.ts
763
+ import { EventEmitter } from "node:events";
764
+ import split2 from "split2";
765
+ var AcpClient;
766
+ var init_AcpClient = __esm({
767
+ "server/src/acp/AcpClient.ts"() {
768
+ "use strict";
769
+ init_dist();
770
+ init_errors();
771
+ AcpClient = class extends EventEmitter {
772
+ nextId = 1;
773
+ pending = /* @__PURE__ */ new Map();
774
+ writable;
775
+ log;
776
+ closed = false;
777
+ constructor(readable, writable, log) {
778
+ super();
779
+ this.writable = writable;
780
+ this.log = log;
781
+ readable.pipe(split2()).on("data", (line) => {
782
+ const trimmed = line.trim();
783
+ if (!trimmed) return;
784
+ let msg;
785
+ try {
786
+ msg = JSON.parse(trimmed);
787
+ } catch (err) {
788
+ this.emit("parseError", err, trimmed);
789
+ this.log.warn({ line: trimmed.slice(0, 200) }, "acp: failed to parse line");
790
+ return;
791
+ }
792
+ this.handleMessage(msg);
793
+ });
794
+ }
795
+ handleMessage(msg) {
796
+ if (isJsonRpcResponse(msg)) {
797
+ const id = "id" in msg ? msg.id : null;
798
+ if (id === null) return;
799
+ const pending = this.pending.get(id);
800
+ if (!pending) {
801
+ this.log.warn({ id }, "acp: response for unknown request id");
802
+ return;
803
+ }
804
+ this.pending.delete(id);
805
+ if (pending.timer) clearTimeout(pending.timer);
806
+ if (isJsonRpcError(msg)) {
807
+ pending.reject(new Error(`ACP ${pending.method} failed: ${describeError(msg.error)}`));
808
+ } else {
809
+ pending.resolve(msg.result);
810
+ }
811
+ return;
812
+ }
813
+ if (isJsonRpcNotification(msg)) {
814
+ this.emit("notification", msg);
815
+ return;
816
+ }
817
+ if ("id" in msg && "method" in msg) {
818
+ this.emit("serverRequest", msg);
819
+ }
820
+ }
821
+ /** Send a request and await its result. */
822
+ request(method, params, timeoutMs = 12e4) {
823
+ if (this.closed) return Promise.reject(new Error("ACP client is closed"));
824
+ const id = this.nextId++;
825
+ const payload = { jsonrpc: "2.0", id, method, params };
826
+ return new Promise((resolve, reject) => {
827
+ const timer = timeoutMs > 0 ? setTimeout(() => {
828
+ this.pending.delete(id);
829
+ reject(new Error(`ACP ${method} timed out after ${timeoutMs}ms`));
830
+ }, timeoutMs) : void 0;
831
+ this.pending.set(id, {
832
+ resolve,
833
+ reject,
834
+ method,
835
+ timer
836
+ });
837
+ this.write(payload);
838
+ });
839
+ }
840
+ notify(method, params) {
841
+ if (this.closed) return;
842
+ this.write({ jsonrpc: "2.0", method, params });
843
+ }
844
+ respond(id, result) {
845
+ if (this.closed) return;
846
+ this.write({ jsonrpc: "2.0", id, result });
847
+ }
848
+ write(msg) {
849
+ this.writable.write(JSON.stringify(msg) + "\n");
850
+ }
851
+ /**
852
+ * Reject all pending requests; call when the child process exits. The reason
853
+ * is passed through verbatim because it reaches the user as a turn_error -
854
+ * the caller supplies something readable, so don't bury it behind a prefix
855
+ * about ACP internals.
856
+ */
857
+ fail(reason) {
858
+ this.closed = true;
859
+ for (const [id, pending] of this.pending) {
860
+ if (pending.timer) clearTimeout(pending.timer);
861
+ pending.reject(new Error(reason));
862
+ this.pending.delete(id);
863
+ }
864
+ }
865
+ };
866
+ }
867
+ });
868
+
869
+ // server/src/session/KiroProcess.ts
870
+ import { spawn } from "node:child_process";
871
+ import { EventEmitter as EventEmitter2 } from "node:events";
872
+ import split22 from "split2";
873
+ var STDERR_KEEP, KiroProcess;
874
+ var init_KiroProcess = __esm({
875
+ "server/src/session/KiroProcess.ts"() {
876
+ "use strict";
877
+ init_dist();
878
+ init_config();
879
+ init_AcpClient();
880
+ STDERR_KEEP = 8;
881
+ KiroProcess = class extends EventEmitter2 {
882
+ client;
883
+ child;
884
+ log;
885
+ disposed = false;
886
+ recentStderr = [];
887
+ constructor(opts, log) {
888
+ super();
889
+ this.log = log;
890
+ const args = ["acp", "--trust-all-tools"];
891
+ if (opts.agent) args.push("--agent", opts.agent);
892
+ if (opts.model) args.push("--model", opts.model);
893
+ this.log.info({ bin: config.kiroBin, args, cwd: opts.cwd }, "spawning kiro-cli acp");
894
+ this.child = spawn(config.kiroBin, args, {
895
+ cwd: opts.cwd,
896
+ stdio: ["pipe", "pipe", "pipe"],
897
+ env: { ...process.env }
898
+ });
899
+ this.client = new AcpClient(this.child.stdout, this.child.stdin, log);
900
+ this.client.on("notification", (n) => {
901
+ this.emit("notification", n);
902
+ });
903
+ this.client.on("serverRequest", (req) => {
904
+ this.log.debug({ method: req.method }, "acp: unhandled server request");
905
+ this.client.respond(req.id, {});
906
+ });
907
+ this.child.stderr.pipe(split22()).on("data", (line) => {
908
+ if (!line.trim()) return;
909
+ this.log.debug({ stderr: line }, "kiro-cli stderr");
910
+ this.recentStderr.push(line.trim());
911
+ if (this.recentStderr.length > STDERR_KEEP) this.recentStderr.shift();
912
+ });
913
+ this.child.on("exit", (code2, signal) => {
914
+ this.client.fail(this.exitReason(code2, signal));
915
+ if (!this.disposed) {
916
+ this.log.warn({ code: code2, signal }, "kiro-cli acp exited unexpectedly");
917
+ }
918
+ this.emit("exit", code2, signal);
919
+ });
920
+ this.child.on("error", (err) => {
921
+ this.log.error({ err }, "kiro-cli acp spawn error");
922
+ this.client.fail(err.message);
923
+ });
924
+ this.child.stdin.on("error", () => {
925
+ });
926
+ this.child.stdout.on("error", () => {
927
+ });
928
+ }
929
+ /** Perform the ACP initialize handshake. Idempotent. */
930
+ async initialize() {
931
+ const result = await this.client.request(ACP_METHODS.initialize, {
932
+ protocolVersion: 1,
933
+ clientCapabilities: {
934
+ fs: { readTextFile: false, writeTextFile: false },
935
+ terminal: false
936
+ },
937
+ // Informational only - kiro logs it and nothing reads it back, so it isn't
938
+ // worth wiring to package.json.
939
+ clientInfo: { name: "casper", version: "0.5.0" }
940
+ });
941
+ return result;
942
+ }
943
+ newSession(params) {
944
+ return this.client.request(ACP_METHODS.sessionNew, params);
945
+ }
946
+ loadSession(params) {
947
+ return this.client.request(ACP_METHODS.sessionLoad, params);
948
+ }
949
+ /** Run a prompt turn to completion. Resolves with the stop reason. */
950
+ prompt(params) {
951
+ return this.client.request(
952
+ ACP_METHODS.sessionPrompt,
953
+ params,
954
+ 0
955
+ );
956
+ }
957
+ cancel(sessionId) {
958
+ this.client.notify(ACP_METHODS.sessionCancel, { sessionId });
959
+ }
960
+ setMode(sessionId, modeId) {
961
+ return this.client.request(ACP_METHODS.sessionSetMode, { sessionId, modeId });
962
+ }
963
+ setModel(sessionId, modelId) {
964
+ return this.client.request(ACP_METHODS.sessionSetModel, { sessionId, modelId });
965
+ }
966
+ execCommand(sessionId, command, args) {
967
+ return this.client.request(ACP_METHODS.commandsExecute, {
968
+ sessionId,
969
+ command: { command: command.replace(/^\//, ""), args: args ? { input: args } : {} }
970
+ });
971
+ }
972
+ /** Cleanly shut down: close stdin (triggers kiro's graceful exit), then kill. */
973
+ /**
974
+ * Recent stderr from kiro, for attaching to a failure.
975
+ *
976
+ * A failed request doesn't always carry its cause in the JSON-RPC error - when
977
+ * it doesn't, kiro has usually written something here. Shared across turns, so
978
+ * treat it as context rather than proof.
979
+ */
980
+ stderrTail() {
981
+ return this.recentStderr.join("\n").trim();
982
+ }
983
+ /**
984
+ * Why the child died, in terms a user can act on. The exit code alone is
985
+ * opaque, so append what kiro printed - that's where "credentials have
986
+ * expired, run kiro-cli login" and friends show up.
987
+ */
988
+ exitReason(code2, signal) {
989
+ const base = signal ? `kiro-cli exited on ${signal}` : `kiro-cli exited with code ${code2}`;
990
+ const tail = this.recentStderr.join("\n").trim();
991
+ return tail ? `${base}: ${tail}` : base;
992
+ }
993
+ dispose() {
994
+ if (this.disposed) return;
995
+ this.disposed = true;
996
+ try {
997
+ this.child.stdin.end();
998
+ } catch {
999
+ }
1000
+ setTimeout(() => {
1001
+ if (this.child.exitCode === null) this.child.kill("SIGTERM");
1002
+ }, 1e3).unref();
1003
+ }
1004
+ /**
1005
+ * Dispose and resolve once the child has actually exited. kiro writes its
1006
+ * session file on shutdown, so a caller that then deletes those files must
1007
+ * wait for exit first, or kiro's write recreates them.
1008
+ */
1009
+ disposeAndWait(timeoutMs = 4e3) {
1010
+ if (this.child.exitCode !== null) {
1011
+ this.dispose();
1012
+ return Promise.resolve();
1013
+ }
1014
+ return new Promise((resolve) => {
1015
+ let settled = false;
1016
+ const finish = () => {
1017
+ if (settled) return;
1018
+ settled = true;
1019
+ resolve();
1020
+ };
1021
+ this.child.once("exit", finish);
1022
+ this.dispose();
1023
+ setTimeout(finish, timeoutMs).unref();
1024
+ });
1025
+ }
1026
+ };
1027
+ }
1028
+ });
1029
+
1030
+ // server/src/session/EventStore.ts
1031
+ import { EventEmitter as EventEmitter3 } from "node:events";
1032
+ var EventStore;
1033
+ var init_EventStore = __esm({
1034
+ "server/src/session/EventStore.ts"() {
1035
+ "use strict";
1036
+ init_config();
1037
+ EventStore = class extends EventEmitter3 {
1038
+ sessionId;
1039
+ buffer = [];
1040
+ capacity;
1041
+ seq = 0;
1042
+ constructor(sessionId) {
1043
+ super();
1044
+ this.sessionId = sessionId;
1045
+ this.capacity = config.eventBufferSize;
1046
+ }
1047
+ /** Append an event, assign it the next seq, and fan it out. */
1048
+ append(payload) {
1049
+ this.seq += 1;
1050
+ const event = {
1051
+ seq: this.seq,
1052
+ ts: Date.now(),
1053
+ sessionId: this.sessionId,
1054
+ payload
1055
+ };
1056
+ this.buffer.push(event);
1057
+ if (this.buffer.length > this.capacity) this.buffer.shift();
1058
+ this.emit("event", event);
1059
+ return event;
1060
+ }
1061
+ /** Highest assigned seq. Clients start their cursor here after a full refetch. */
1062
+ head() {
1063
+ return this.seq;
1064
+ }
1065
+ /** Oldest seq still in the buffer (0 if empty). */
1066
+ tail() {
1067
+ return this.buffer.length > 0 ? this.buffer[0].seq : 0;
1068
+ }
1069
+ /**
1070
+ * Events with seq > cursor, in order. Returns { events, gap } - gap is true
1071
+ * when the cursor is older than the buffer tail, meaning some events were
1072
+ * evicted and the client must resync (full transcript refetch).
1073
+ */
1074
+ getSince(cursor) {
1075
+ if (this.buffer.length === 0) {
1076
+ return { events: [], gap: cursor > 0 };
1077
+ }
1078
+ const tail = this.tail();
1079
+ const gap = cursor > 0 && cursor < tail - 1;
1080
+ const events = this.buffer.filter((e) => e.seq > cursor);
1081
+ return { events, gap };
1082
+ }
1083
+ dispose() {
1084
+ this.removeAllListeners();
1085
+ }
1086
+ };
1087
+ }
1088
+ });
1089
+
1090
+ // server/src/session/TurnState.ts
1091
+ var TurnState;
1092
+ var init_TurnState = __esm({
1093
+ "server/src/session/TurnState.ts"() {
1094
+ "use strict";
1095
+ init_dist();
1096
+ TurnState = class {
1097
+ snapshot = emptyObservabilitySnapshot();
1098
+ get() {
1099
+ return this.snapshot;
1100
+ }
1101
+ /** Apply one event; returns the updated snapshot. */
1102
+ apply(payload) {
1103
+ const s = this.snapshot;
1104
+ switch (payload.kind) {
1105
+ case "turn_started": {
1106
+ this.snapshot = { ...s, turnStatus: "running", currentTurnStartedAt: Date.now() };
1107
+ break;
1108
+ }
1109
+ case "turn_ended": {
1110
+ this.snapshot = { ...s, turnStatus: "idle", currentTurnStartedAt: void 0 };
1111
+ break;
1112
+ }
1113
+ case "turn_error": {
1114
+ this.snapshot = { ...s, turnStatus: "idle", currentTurnStartedAt: void 0 };
1115
+ break;
1116
+ }
1117
+ case "process_exited": {
1118
+ this.snapshot = { ...s, turnStatus: "idle", currentTurnStartedAt: void 0 };
1119
+ break;
1120
+ }
1121
+ case "metadata": {
1122
+ const p = payload.params;
1123
+ const turnCredits = (p.meteringUsage ?? []).reduce((sum, m) => sum + m.value, 0);
1124
+ this.snapshot = {
1125
+ ...s,
1126
+ contextUsagePercentage: p.contextUsagePercentage ?? s.contextUsagePercentage,
1127
+ // meteringUsage is emitted per-turn; accumulate into the session total.
1128
+ creditsSpent: turnCredits > 0 ? s.creditsSpent + turnCredits : s.creditsSpent,
1129
+ lastTurnCredits: turnCredits > 0 ? turnCredits : s.lastTurnCredits,
1130
+ lastTurnDurationMs: p.turnDurationMs ?? s.lastTurnDurationMs
1131
+ };
1132
+ break;
1133
+ }
1134
+ case "subagent_update": {
1135
+ this.snapshot = {
1136
+ ...s,
1137
+ subagents: payload.params.subagents,
1138
+ pendingStages: payload.params.pendingStages
1139
+ };
1140
+ break;
1141
+ }
1142
+ case "mcp_health": {
1143
+ const { serverName, error } = payload.params;
1144
+ const status = payload.ok ? "initialized" : "failed";
1145
+ const next = s.mcpServers.filter((m) => m.serverName !== serverName);
1146
+ next.push({ serverName, status, error, updatedAt: Date.now() });
1147
+ this.snapshot = { ...s, mcpServers: next };
1148
+ break;
1149
+ }
1150
+ case "commands_available": {
1151
+ this.snapshot = { ...s, availableCommands: payload.params.commands };
1152
+ break;
1153
+ }
1154
+ case "compaction": {
1155
+ this.snapshot = { ...s, compacting: payload.params.status.type === "started" };
1156
+ break;
1157
+ }
1158
+ case "oauth_request": {
1159
+ const prompts = [
1160
+ ...s.oauthPrompts,
1161
+ {
1162
+ serverName: payload.params.serverName,
1163
+ url: payload.params.url,
1164
+ createdAt: Date.now()
1165
+ }
1166
+ ];
1167
+ this.snapshot = { ...s, oauthPrompts: prompts };
1168
+ break;
1169
+ }
1170
+ default:
1171
+ break;
1172
+ }
1173
+ return this.snapshot;
1174
+ }
1175
+ /** Seed cumulative credits/context from kiro's persisted metadata on resume. */
1176
+ seed(creditsSpent, contextUsagePercentage) {
1177
+ this.snapshot = { ...this.snapshot, creditsSpent, contextUsagePercentage };
1178
+ }
1179
+ };
1180
+ }
1181
+ });
1182
+
1183
+ // server/src/session/kiroFiles.ts
1184
+ import fs7 from "node:fs/promises";
1185
+ import path8 from "node:path";
1186
+ function isContentBlock(v) {
1187
+ return isRecord(v) && typeof v.kind === "string";
1188
+ }
1189
+ function toolUseData(b) {
1190
+ if (b.kind !== "toolUse" || !isRecord(b.data)) return null;
1191
+ const { toolUseId, name, input } = b.data;
1192
+ if (typeof toolUseId !== "string") return null;
1193
+ return { toolUseId, name: typeof name === "string" ? name : void 0, input };
1194
+ }
1195
+ function toolResultData(b) {
1196
+ if (b.kind !== "toolResult" || !isRecord(b.data)) return null;
1197
+ const { toolUseId, status, content } = b.data;
1198
+ if (typeof toolUseId !== "string") return null;
1199
+ return { toolUseId, status: typeof status === "string" ? status : void 0, content };
1200
+ }
1201
+ function contentBlocks(content) {
1202
+ return Array.isArray(content) ? content.filter(isContentBlock) : [];
1203
+ }
1204
+ function blockText(c) {
1205
+ if (typeof c.data === "string") return c.data;
1206
+ if (isRecord(c.data) && typeof c.data.text === "string") return c.data.text;
1207
+ return "";
1208
+ }
1209
+ function renderableBlocks(content) {
1210
+ return contentBlocks(content).filter((b) => b.kind !== "image");
1211
+ }
1212
+ function summarize(j) {
1213
+ const state = j.session_state;
1214
+ const turns = state?.conversation_metadata?.user_turn_metadatas ?? [];
1215
+ const creditsSpent = turns.reduce(
1216
+ (sum, t) => sum + (t.metering_usage ?? []).reduce((a, m) => a + m.value, 0),
1217
+ 0
1218
+ );
1219
+ const contextUsagePercentage = state?.rts_model_state?.context_usage_percentage ?? turns[turns.length - 1]?.context_usage_percentage;
1220
+ return {
1221
+ sessionId: j.session_id,
1222
+ title: j.title?.trim() || "Untitled session",
1223
+ cwd: j.cwd,
1224
+ createdAt: j.created_at,
1225
+ updatedAt: j.updated_at,
1226
+ liveness: "dormant",
1227
+ agentId: state?.agent_name,
1228
+ modelId: state?.rts_model_state?.model_info?.model_id,
1229
+ running: false,
1230
+ creditsSpent,
1231
+ contextUsagePercentage
1232
+ };
1233
+ }
1234
+ async function listPersistedSessions(log) {
1235
+ let files;
1236
+ try {
1237
+ files = await fs7.readdir(config.kiroSessionsDir);
1238
+ } catch {
1239
+ return [];
1240
+ }
1241
+ const jsonFiles = files.filter((f) => f.endsWith(".json"));
1242
+ const summaries = [];
1243
+ await Promise.all(
1244
+ jsonFiles.map(async (f) => {
1245
+ try {
1246
+ const raw = await fs7.readFile(path8.join(config.kiroSessionsDir, f), "utf8");
1247
+ summaries.push(summarize(JSON.parse(raw)));
1248
+ } catch (err) {
1249
+ log.debug({ err, f }, "kiroFiles: skipping unreadable session file");
1250
+ }
1251
+ })
1252
+ );
1253
+ summaries.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
1254
+ return summaries;
1255
+ }
1256
+ async function deletePersistedSession(sessionId) {
1257
+ if (!isValidSessionId(sessionId)) return;
1258
+ const targets = [
1259
+ path8.join(config.kiroSessionsDir, `${sessionId}.json`),
1260
+ path8.join(config.kiroSessionsDir, `${sessionId}.jsonl`),
1261
+ path8.join(config.kiroSessionsDir, `${sessionId}.history`),
1262
+ path8.join(config.kiroSessionsDir, sessionId)
1263
+ ];
1264
+ await Promise.all(targets.map((f) => fs7.rm(f, { recursive: true, force: true })));
1265
+ }
1266
+ async function readPersistedSession(sessionId) {
1267
+ if (!isValidSessionId(sessionId)) return null;
1268
+ try {
1269
+ const raw = await fs7.readFile(
1270
+ path8.join(config.kiroSessionsDir, `${sessionId}.json`),
1271
+ "utf8"
1272
+ );
1273
+ return summarize(JSON.parse(raw));
1274
+ } catch {
1275
+ return null;
1276
+ }
1277
+ }
1278
+ async function hydrateTranscript(sessionId) {
1279
+ if (!isValidSessionId(sessionId)) return [];
1280
+ let raw;
1281
+ try {
1282
+ raw = await fs7.readFile(
1283
+ path8.join(config.kiroSessionsDir, `${sessionId}.jsonl`),
1284
+ "utf8"
1285
+ );
1286
+ } catch {
1287
+ return [];
1288
+ }
1289
+ const items = [];
1290
+ const toolsById = /* @__PURE__ */ new Map();
1291
+ for (const line of raw.split("\n")) {
1292
+ const trimmed = line.trim();
1293
+ if (!trimmed) continue;
1294
+ let entry;
1295
+ try {
1296
+ entry = JSON.parse(trimmed);
1297
+ } catch {
1298
+ continue;
1299
+ }
1300
+ const content = contentBlocks(entry.data.content);
1301
+ const textOf = (kind) => content.filter((c) => c.kind === kind).map(blockText).join("");
1302
+ const baseId = entry.data.message_id ?? `${items.length}`;
1303
+ const ts = entry.data.meta?.timestamp;
1304
+ const pushMsg = (msg) => items.push({ type: "message", message: msg });
1305
+ if (entry.kind === "Prompt") {
1306
+ const raw2 = textOf("text");
1307
+ const text = stripAttachmentsLine(raw2);
1308
+ const imagePaths = imageAttachmentPaths(raw2);
1309
+ if (text.trim() || imagePaths.length)
1310
+ pushMsg({ id: `u-${baseId}`, role: "user", text, timestamp: ts, imagePaths });
1311
+ } else if (entry.kind === "AssistantMessage") {
1312
+ const thinking = textOf("thinking");
1313
+ if (thinking.trim())
1314
+ pushMsg({ id: `t-${baseId}`, role: "thinking", text: thinking, timestamp: ts });
1315
+ const text = textOf("text");
1316
+ if (text.trim())
1317
+ pushMsg({ id: `a-${baseId}`, role: "assistant", text, timestamp: ts });
1318
+ for (const c of content) {
1319
+ const d = toolUseData(c);
1320
+ if (!d) continue;
1321
+ const tool = {
1322
+ id: d.toolUseId,
1323
+ name: d.name,
1324
+ title: d.name ?? d.toolUseId,
1325
+ status: "completed",
1326
+ input: d.input,
1327
+ content: []
1328
+ };
1329
+ toolsById.set(d.toolUseId, tool);
1330
+ items.push({ type: "tool_call", tool });
1331
+ }
1332
+ } else if (entry.kind === "ToolResults") {
1333
+ for (const c of content) {
1334
+ const d = toolResultData(c);
1335
+ if (!d) continue;
1336
+ const tool = toolsById.get(d.toolUseId);
1337
+ if (!tool) continue;
1338
+ tool.status = d.status === "error" ? "failed" : "completed";
1339
+ tool.content = renderableBlocks(d.content);
1340
+ }
1341
+ } else if (entry.kind === "Compaction") {
1342
+ const summary = entry.data.summary ?? "";
1343
+ if (summary.trim())
1344
+ items.push({ type: "compaction", id: `c-${baseId}`, summary, timestamp: ts });
1345
+ }
1346
+ }
1347
+ return items;
1348
+ }
1349
+ var isRecord;
1350
+ var init_kiroFiles = __esm({
1351
+ "server/src/session/kiroFiles.ts"() {
1352
+ "use strict";
1353
+ init_dist();
1354
+ init_config();
1355
+ init_paths2();
1356
+ isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
1357
+ }
1358
+ });
1359
+
1360
+ // server/src/session/sessionStore.ts
1361
+ var SessionStore;
1362
+ var init_sessionStore = __esm({
1363
+ "server/src/session/sessionStore.ts"() {
1364
+ "use strict";
1365
+ init_db();
1366
+ SessionStore = class {
1367
+ getTitle(sessionId) {
1368
+ return this.read(sessionId, "title");
1369
+ }
1370
+ setTitle(sessionId, title) {
1371
+ this.write(sessionId, "title", title);
1372
+ }
1373
+ getCwd(sessionId) {
1374
+ return this.read(sessionId, "cwd");
1375
+ }
1376
+ setCwd(sessionId, cwd) {
1377
+ this.write(sessionId, "cwd", cwd);
1378
+ }
1379
+ /** Forget every override for a session, when it's permanently deleted. */
1380
+ remove(sessionId) {
1381
+ db().prepare("DELETE FROM sessions WHERE session_id = ?").run(sessionId);
1382
+ }
1383
+ read(sessionId, column) {
1384
+ const row = db().prepare(`SELECT ${column} AS value FROM sessions WHERE session_id = ?`).get(sessionId);
1385
+ return row?.value ?? void 0;
1386
+ }
1387
+ write(sessionId, column, value) {
1388
+ db().prepare(
1389
+ `INSERT INTO sessions (session_id, ${column}) VALUES (?, ?)
1390
+ ON CONFLICT(session_id) DO UPDATE SET ${column} = excluded.${column}`
1391
+ ).run(sessionId, value);
1392
+ }
1393
+ };
1394
+ }
1395
+ });
1396
+
1397
+ // server/src/session/SessionManager.ts
1398
+ import fs8 from "node:fs";
1399
+ import path9 from "node:path";
1400
+ function resolveCwd(input) {
1401
+ const raw = input?.trim();
1402
+ const abs = raw ? path9.resolve(config.defaultCwd, raw) : config.defaultCwd;
1403
+ if (!isWithinRoot(config.fileRoot, abs)) {
1404
+ throw new Error(`Working directory is outside the allowed root: ${abs}`);
1405
+ }
1406
+ let stat;
1407
+ try {
1408
+ stat = fs8.statSync(abs);
1409
+ } catch {
1410
+ stat = void 0;
1411
+ }
1412
+ if (stat && !stat.isDirectory()) {
1413
+ throw new Error(`Working directory path is a file, not a directory: ${abs}`);
1414
+ }
1415
+ if (!stat) {
1416
+ fs8.mkdirSync(abs, { recursive: true });
1417
+ }
1418
+ return abs;
1419
+ }
1420
+ function mapNotification(n) {
1421
+ switch (n.method) {
1422
+ case "session/update":
1423
+ return { kind: "session_update", update: n.params.update };
1424
+ case KIRO_NOTIFICATIONS.metadata:
1425
+ return { kind: "metadata", params: n.params };
1426
+ case KIRO_NOTIFICATIONS.subagentListUpdate:
1427
+ return { kind: "subagent_update", params: n.params };
1428
+ case KIRO_NOTIFICATIONS.mcpServerInitialized:
1429
+ return { kind: "mcp_health", params: n.params, ok: true };
1430
+ case KIRO_NOTIFICATIONS.mcpServerInitFailure:
1431
+ return { kind: "mcp_health", params: n.params, ok: false };
1432
+ case KIRO_NOTIFICATIONS.commandsAvailable:
1433
+ return { kind: "commands_available", params: n.params };
1434
+ case KIRO_NOTIFICATIONS.compactionStatus:
1435
+ return { kind: "compaction", params: n.params };
1436
+ case KIRO_NOTIFICATIONS.mcpOauthRequest:
1437
+ return { kind: "oauth_request", params: n.params };
1438
+ default:
1439
+ return null;
1440
+ }
1441
+ }
1442
+ var Session, TRANSCRIPT_PAGE_SIZE, SessionManager;
1443
+ var init_SessionManager = __esm({
1444
+ "server/src/session/SessionManager.ts"() {
1445
+ "use strict";
1446
+ init_dist();
1447
+ init_config();
1448
+ init_paths2();
1449
+ init_KiroProcess();
1450
+ init_EventStore();
1451
+ init_TurnState();
1452
+ init_kiroFiles();
1453
+ init_sessionStore();
1454
+ Session = class {
1455
+ sessionId;
1456
+ store;
1457
+ turnState = new TurnState();
1458
+ cwd;
1459
+ agentId;
1460
+ modelId;
1461
+ currentModeId;
1462
+ availableModes = [];
1463
+ title = "New session";
1464
+ createdAt = (/* @__PURE__ */ new Date()).toISOString();
1465
+ updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1466
+ lastActivity = Date.now();
1467
+ running = false;
1468
+ // True once kiro has created or loaded this session id.
1469
+ everLive = false;
1470
+ proc;
1471
+ // In-flight spawn, so concurrent actions share one process.
1472
+ spawning;
1473
+ // True while kiro is replaying history during session/load. The transcript is
1474
+ // already hydrated from disk, so replayed notifications must not be appended
1475
+ // to the live store (doing so floods the chat with duplicate tool calls).
1476
+ replaying = false;
1477
+ constructor(sessionId, store, cwd) {
1478
+ this.sessionId = sessionId;
1479
+ this.store = store;
1480
+ this.cwd = cwd;
1481
+ }
1482
+ markLive() {
1483
+ this.everLive = true;
1484
+ }
1485
+ get hasBeenLive() {
1486
+ return this.everLive;
1487
+ }
1488
+ /**
1489
+ * Single append path: fold the event into the live snapshot AND persist/fan
1490
+ * it out. Using this everywhere keeps turnState in sync with the event log,
1491
+ * so a client that refetches mid-turn sees turnStatus 'running' rather than
1492
+ * a stale 'idle'.
1493
+ */
1494
+ record(payload) {
1495
+ this.turnState.apply(payload);
1496
+ return this.store.append(payload);
1497
+ }
1498
+ };
1499
+ TRANSCRIPT_PAGE_SIZE = 80;
1500
+ SessionManager = class {
1501
+ sessions = /* @__PURE__ */ new Map();
1502
+ log;
1503
+ store = new SessionStore();
1504
+ constructor(log) {
1505
+ this.log = log;
1506
+ }
1507
+ /** Set a user title override for a session. */
1508
+ renameSession(sessionId, title) {
1509
+ this.store.setTitle(sessionId, title);
1510
+ const s = this.sessions.get(sessionId);
1511
+ if (s) s.title = title.trim() || s.title;
1512
+ }
1513
+ /**
1514
+ * Re-point a session at a different working directory. A session's cwd is
1515
+ * fixed at creation, so this is needed when the original folder was moved or
1516
+ * deleted (the workspace endpoints and the kiro process are scoped to it).
1517
+ *
1518
+ * The target is validated and created exactly like a new session's cwd, so a
1519
+ * folder is only created on this explicit action - never silently on resume.
1520
+ * Any live kiro process was spawned with the old cwd, so it's disposed here;
1521
+ * the next turn respawns in the new directory with the transcript intact
1522
+ * (kiro reloads the session from its own file).
1523
+ *
1524
+ * Returns the resolved absolute path.
1525
+ */
1526
+ async setSessionCwd(sessionId, input) {
1527
+ const resolved = resolveCwd(input);
1528
+ const s = await this.ensureOpen(sessionId);
1529
+ this.store.setCwd(sessionId, resolved);
1530
+ if (s.cwd !== resolved) {
1531
+ s.cwd = resolved;
1532
+ s.proc?.dispose();
1533
+ s.proc = void 0;
1534
+ this.log.info({ sessionId, cwd: resolved }, "session working directory changed");
1535
+ }
1536
+ return resolved;
1537
+ }
1538
+ get liveCount() {
1539
+ let n = 0;
1540
+ for (const s of this.sessions.values()) if (s.proc) n++;
1541
+ return n;
1542
+ }
1543
+ // -------------------------------------------------------------------------
1544
+ // Event subscription - works for any opened session, spawned or not.
1545
+ // -------------------------------------------------------------------------
1546
+ onEvent(sessionId, cb) {
1547
+ const s = this.sessions.get(sessionId);
1548
+ if (!s) return null;
1549
+ s.store.on("event", cb);
1550
+ return () => s.store.off("event", cb);
1551
+ }
1552
+ getStore(sessionId) {
1553
+ return this.sessions.get(sessionId)?.store;
1554
+ }
1555
+ /** Get a session's working directory. Opens the session in memory if needed. */
1556
+ async getSessionCwd(sessionId) {
1557
+ const s = await this.ensureOpen(sessionId);
1558
+ return s.cwd;
1559
+ }
1560
+ /** Open a session in memory (store + metadata) WITHOUT spawning a process. */
1561
+ async ensureOpen(sessionId) {
1562
+ const existing = this.sessions.get(sessionId);
1563
+ if (existing) return existing;
1564
+ const persisted = await readPersistedSession(sessionId);
1565
+ if (!persisted) throw new Error(`Unknown session: ${sessionId}`);
1566
+ const effectiveCwd = this.store.getCwd(sessionId) ?? persisted.cwd;
1567
+ if (!isWithinRoot(config.fileRoot, effectiveCwd)) {
1568
+ throw new Error(
1569
+ `Session working directory is outside the allowed root: ${effectiveCwd}`
1570
+ );
1571
+ }
1572
+ const store = new EventStore(sessionId);
1573
+ const s = new Session(sessionId, store, effectiveCwd);
1574
+ s.title = persisted.title;
1575
+ s.agentId = persisted.agentId;
1576
+ s.currentModeId = persisted.agentId;
1577
+ s.modelId = persisted.modelId;
1578
+ s.createdAt = persisted.createdAt;
1579
+ s.updatedAt = persisted.updatedAt;
1580
+ s.markLive();
1581
+ s.turnState.seed(
1582
+ persisted.creditsSpent ?? 0,
1583
+ persisted.contextUsagePercentage ?? 0
1584
+ );
1585
+ this.sessions.set(sessionId, s);
1586
+ return s;
1587
+ }
1588
+ // -------------------------------------------------------------------------
1589
+ // Lazy process spawning
1590
+ // -------------------------------------------------------------------------
1591
+ wire(s, proc) {
1592
+ proc.on("notification", (n) => {
1593
+ if (s.replaying) return;
1594
+ const payload = mapNotification(n);
1595
+ if (!payload) return;
1596
+ s.record(payload);
1597
+ });
1598
+ proc.on("exit", (code2, signal) => {
1599
+ if (s.proc !== proc) return;
1600
+ s.record({ kind: "process_exited", code: code2, signal });
1601
+ s.proc = void 0;
1602
+ s.running = false;
1603
+ });
1604
+ }
1605
+ /** Get (or spawn + initialize + create/load) the kiro process for a session. */
1606
+ async ensureProc(s) {
1607
+ if (s.proc) return s.proc;
1608
+ if (s.spawning) return s.spawning;
1609
+ s.spawning = (async () => {
1610
+ await this.ensureCapacity();
1611
+ const proc = new KiroProcess(
1612
+ { cwd: s.cwd, agent: s.agentId, model: s.modelId },
1613
+ this.log
1614
+ );
1615
+ this.wire(s, proc);
1616
+ await proc.initialize();
1617
+ let res;
1618
+ if (s.hasBeenLive) {
1619
+ s.replaying = true;
1620
+ try {
1621
+ res = await proc.loadSession({ sessionId: s.sessionId, cwd: s.cwd, mcpServers: [] });
1622
+ } finally {
1623
+ s.replaying = false;
1624
+ }
1625
+ } else {
1626
+ res = await proc.newSession({ cwd: s.cwd, mcpServers: [] });
1627
+ }
1628
+ if (!s.hasBeenLive && res.sessionId !== s.sessionId) {
1629
+ this.sessions.delete(s.sessionId);
1630
+ s.sessionId = res.sessionId;
1631
+ this.sessions.set(res.sessionId, s);
1632
+ }
1633
+ s.availableModes = res.modes.availableModes;
1634
+ s.currentModeId = res.modes.currentModeId;
1635
+ s.agentId = res.modes.currentModeId ?? s.agentId;
1636
+ s.markLive();
1637
+ s.proc = proc;
1638
+ s.spawning = void 0;
1639
+ return proc;
1640
+ })();
1641
+ try {
1642
+ return await s.spawning;
1643
+ } catch (err) {
1644
+ s.spawning = void 0;
1645
+ throw err;
1646
+ }
1647
+ }
1648
+ async ensureCapacity() {
1649
+ const liveIds = [...this.sessions.values()].filter((s) => s.proc);
1650
+ if (liveIds.length < config.maxLiveSessions) return;
1651
+ let victim = null;
1652
+ let oldest = Infinity;
1653
+ for (const s of liveIds) {
1654
+ if (!s.running && s.lastActivity < oldest) {
1655
+ oldest = s.lastActivity;
1656
+ victim = s;
1657
+ }
1658
+ }
1659
+ if (victim) {
1660
+ this.log.info({ sessionId: victim.sessionId }, "idle process evicted for capacity");
1661
+ victim.proc?.dispose();
1662
+ victim.proc = void 0;
1663
+ } else {
1664
+ this.log.warn("at capacity but all processes are busy");
1665
+ }
1666
+ }
1667
+ // -------------------------------------------------------------------------
1668
+ // Creating / opening
1669
+ // -------------------------------------------------------------------------
1670
+ /** Create a new session. Spawns immediately so we get a real kiro sessionId. */
1671
+ async createSession(opts) {
1672
+ const cwd = resolveCwd(opts.cwd);
1673
+ const tempId = `pending-${Date.now()}-${Math.floor(this.sessions.size)}`;
1674
+ const store = new EventStore(tempId);
1675
+ const s = new Session(tempId, store, cwd);
1676
+ s.agentId = opts.agentId ?? config.defaultAgent;
1677
+ s.currentModeId = s.agentId;
1678
+ s.modelId = opts.modelId;
1679
+ this.sessions.set(tempId, s);
1680
+ try {
1681
+ await this.ensureProc(s);
1682
+ } catch (err) {
1683
+ this.evict(s.sessionId);
1684
+ throw err;
1685
+ }
1686
+ const folder = path9.basename(s.cwd);
1687
+ if (folder) {
1688
+ this.store.setTitle(s.sessionId, folder);
1689
+ s.title = folder;
1690
+ }
1691
+ return this.buildDetail(s, []);
1692
+ }
1693
+ // -------------------------------------------------------------------------
1694
+ // Actions - these spawn the process lazily.
1695
+ // -------------------------------------------------------------------------
1696
+ async runPrompt(sessionId, content) {
1697
+ const s = await this.ensureOpen(sessionId);
1698
+ const proc = await this.ensureProc(s);
1699
+ if (s.running) throw new Error("A turn is already running for this session");
1700
+ s.running = true;
1701
+ s.lastActivity = Date.now();
1702
+ s.record({ kind: "turn_started", prompt: content });
1703
+ proc.prompt({ sessionId: s.sessionId, prompt: content }).then((res) => s.record({ kind: "turn_ended", stopReason: res.stopReason })).catch((err) => {
1704
+ this.log.error({ err, sessionId: s.sessionId }, "prompt turn failed");
1705
+ const tail = proc.stderrTail();
1706
+ const message = tail && !err.message.includes(tail) ? `${err.message}
1707
+
1708
+ kiro-cli output:
1709
+ ${tail}` : err.message;
1710
+ s.record({ kind: "turn_error", message });
1711
+ }).finally(() => {
1712
+ s.running = false;
1713
+ s.lastActivity = Date.now();
1714
+ });
1715
+ }
1716
+ cancel(sessionId) {
1717
+ const s = this.sessions.get(sessionId);
1718
+ s?.proc?.cancel(s.sessionId);
1719
+ }
1720
+ async setMode(sessionId, modeId) {
1721
+ const s = await this.ensureOpen(sessionId);
1722
+ const proc = await this.ensureProc(s);
1723
+ await proc.setMode(s.sessionId, modeId);
1724
+ s.currentModeId = modeId;
1725
+ s.agentId = modeId;
1726
+ s.lastActivity = Date.now();
1727
+ }
1728
+ async setModel(sessionId, modelId) {
1729
+ const s = await this.ensureOpen(sessionId);
1730
+ const proc = await this.ensureProc(s);
1731
+ await proc.setModel(s.sessionId, modelId);
1732
+ s.modelId = modelId;
1733
+ s.lastActivity = Date.now();
1734
+ }
1735
+ async execCommand(sessionId, command, args) {
1736
+ const s = await this.ensureOpen(sessionId);
1737
+ const proc = await this.ensureProc(s);
1738
+ await proc.execCommand(s.sessionId, command, args);
1739
+ s.lastActivity = Date.now();
1740
+ }
1741
+ // -------------------------------------------------------------------------
1742
+ // Listing / detail - never spawns.
1743
+ // -------------------------------------------------------------------------
1744
+ async listSessions() {
1745
+ const persisted = await listPersistedSessions(this.log);
1746
+ const byId = /* @__PURE__ */ new Map();
1747
+ for (const p of persisted) {
1748
+ byId.set(p.sessionId, {
1749
+ ...p,
1750
+ title: this.store.getTitle(p.sessionId) ?? p.title,
1751
+ cwd: this.store.getCwd(p.sessionId) ?? p.cwd
1752
+ });
1753
+ }
1754
+ for (const s of this.sessions.values()) {
1755
+ const snap = s.turnState.get();
1756
+ const base = byId.get(s.sessionId);
1757
+ byId.set(s.sessionId, {
1758
+ sessionId: s.sessionId,
1759
+ title: this.store.getTitle(s.sessionId) ?? base?.title ?? s.title,
1760
+ cwd: s.cwd,
1761
+ createdAt: base?.createdAt ?? s.createdAt,
1762
+ updatedAt: base?.updatedAt ?? s.updatedAt,
1763
+ liveness: s.proc ? "live" : "dormant",
1764
+ agentId: s.agentId ?? base?.agentId,
1765
+ modelId: s.modelId ?? base?.modelId,
1766
+ running: s.running,
1767
+ creditsSpent: snap.creditsSpent || base?.creditsSpent,
1768
+ contextUsagePercentage: snap.contextUsagePercentage || base?.contextUsagePercentage
1769
+ });
1770
+ }
1771
+ return [...byId.values()].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
1772
+ }
1773
+ async getDetail(sessionId) {
1774
+ const transcript = await hydrateTranscript(sessionId);
1775
+ const s = this.sessions.get(sessionId);
1776
+ if (s) return this.buildDetail(s, transcript);
1777
+ const persisted = await readPersistedSession(sessionId);
1778
+ if (!persisted) throw new Error(`Unknown session: ${sessionId}`);
1779
+ return {
1780
+ summary: {
1781
+ ...persisted,
1782
+ title: this.store.getTitle(sessionId) ?? persisted.title,
1783
+ cwd: this.store.getCwd(sessionId) ?? persisted.cwd
1784
+ },
1785
+ modes: [],
1786
+ currentModeId: persisted.agentId,
1787
+ transcript: transcript.slice(-TRANSCRIPT_PAGE_SIZE),
1788
+ transcriptTotal: transcript.length,
1789
+ observability: {
1790
+ ...emptyObservabilitySnapshot(),
1791
+ creditsSpent: persisted.creditsSpent ?? 0,
1792
+ contextUsagePercentage: persisted.contextUsagePercentage ?? 0
1793
+ },
1794
+ head: 0
1795
+ };
1796
+ }
1797
+ /**
1798
+ * A slice of the transcript for lazy "load older on scroll up". Re-hydrates
1799
+ * from disk (fast: ~150ms even for a multi-MB session) and returns the
1800
+ * requested window. offset/limit are clamped to the transcript bounds.
1801
+ */
1802
+ async getTranscriptPage(sessionId, offset, limit) {
1803
+ const transcript = await hydrateTranscript(sessionId);
1804
+ const start = Math.max(0, Math.min(offset, transcript.length));
1805
+ const end = Math.max(start, Math.min(start + limit, transcript.length));
1806
+ return transcript.slice(start, end);
1807
+ }
1808
+ buildDetail(s, transcript) {
1809
+ const snap = s.turnState.get();
1810
+ const firstMessage = transcript.find((it) => it.type === "message");
1811
+ const firstText = firstMessage?.type === "message" ? firstMessage.message.text : void 0;
1812
+ return {
1813
+ summary: {
1814
+ sessionId: s.sessionId,
1815
+ title: this.store.getTitle(s.sessionId) ?? (firstText?.slice(0, 60) || s.title),
1816
+ cwd: s.cwd,
1817
+ createdAt: s.createdAt,
1818
+ updatedAt: s.updatedAt,
1819
+ liveness: s.proc ? "live" : "dormant",
1820
+ agentId: s.agentId,
1821
+ modelId: s.modelId,
1822
+ running: s.running,
1823
+ creditsSpent: snap.creditsSpent,
1824
+ contextUsagePercentage: snap.contextUsagePercentage
1825
+ },
1826
+ modes: s.availableModes,
1827
+ currentModeId: s.currentModeId,
1828
+ // Only the tail is sent on load; replayHead/title use the full transcript.
1829
+ transcript: transcript.slice(-TRANSCRIPT_PAGE_SIZE),
1830
+ transcriptTotal: transcript.length,
1831
+ observability: snap,
1832
+ head: this.replayHead(s, transcript)
1833
+ };
1834
+ }
1835
+ /**
1836
+ * The cursor a reconnecting client should start from. Normally the store
1837
+ * head, but while a turn is in flight the prompt/response are not yet in
1838
+ * kiro's persisted jsonl (kiro writes a turn only when it completes), so the
1839
+ * hydrated transcript is missing them. Rewind to just before the in-flight
1840
+ * turn_started so the WS replays the whole in-flight turn. Guarded against
1841
+ * duplication in case kiro ever persists the prompt at turn start.
1842
+ */
1843
+ replayHead(s, transcript) {
1844
+ const head = s.store.head();
1845
+ if (!s.running) return head;
1846
+ const { events } = s.store.getSince(0);
1847
+ let started;
1848
+ for (let i = events.length - 1; i >= 0; i--) {
1849
+ if (events[i].payload.kind === "turn_started") {
1850
+ started = events[i];
1851
+ break;
1852
+ }
1853
+ }
1854
+ if (!started || started.payload.kind !== "turn_started") return head;
1855
+ const promptText = stripAttachmentsLine(
1856
+ started.payload.prompt.filter((b) => b.type === "text").map((b) => b.text).join("")
1857
+ ).trim();
1858
+ for (let i = transcript.length - 1; i >= 0; i--) {
1859
+ const it = transcript[i];
1860
+ if (it.type === "message" && it.message.role === "user") {
1861
+ if (promptText && it.message.text.trim() === promptText) return head;
1862
+ break;
1863
+ }
1864
+ }
1865
+ return started.seq - 1;
1866
+ }
1867
+ // -------------------------------------------------------------------------
1868
+ // Teardown
1869
+ // -------------------------------------------------------------------------
1870
+ evict(sessionId) {
1871
+ const s = this.sessions.get(sessionId);
1872
+ if (!s) return;
1873
+ this.sessions.delete(sessionId);
1874
+ s.proc?.dispose();
1875
+ s.store.dispose();
1876
+ }
1877
+ // Permanently delete a session: evict it from memory, remove its on-disk
1878
+ // files, and drop any title override.
1879
+ async deleteSession(sessionId) {
1880
+ const s = this.sessions.get(sessionId);
1881
+ if (s?.proc) {
1882
+ await s.proc.disposeAndWait().catch(() => {
1883
+ });
1884
+ s.proc = void 0;
1885
+ s.running = false;
1886
+ }
1887
+ this.evict(sessionId);
1888
+ this.store.remove(sessionId);
1889
+ await deletePersistedSession(sessionId);
1890
+ setTimeout(() => void deletePersistedSession(sessionId).catch(() => {
1891
+ }), 2500).unref?.();
1892
+ }
1893
+ disposeAll() {
1894
+ for (const id of [...this.sessions.keys()]) this.evict(id);
1895
+ }
1896
+ };
1897
+ }
1898
+ });
1899
+
1900
+ // server/src/util/rateLimit.ts
1901
+ var AttemptLimiter;
1902
+ var init_rateLimit = __esm({
1903
+ "server/src/util/rateLimit.ts"() {
1904
+ "use strict";
1905
+ AttemptLimiter = class {
1906
+ constructor(max, windowMs) {
1907
+ this.max = max;
1908
+ this.windowMs = windowMs;
1909
+ }
1910
+ hits = /* @__PURE__ */ new Map();
1911
+ /** Whether this key may attempt again, and how long to wait if not. */
1912
+ check(key, now = Date.now()) {
1913
+ this.prune(now);
1914
+ const hit = this.hits.get(key);
1915
+ if (!hit || hit.count < this.max) return { allowed: true };
1916
+ return { allowed: false, retryAfterSeconds: Math.ceil((hit.resetAt - now) / 1e3) };
1917
+ }
1918
+ /** Record a failure, starting a fresh window if the previous one has expired. */
1919
+ fail(key, now = Date.now()) {
1920
+ this.prune(now);
1921
+ const hit = this.hits.get(key);
1922
+ if (!hit) {
1923
+ this.hits.set(key, { count: 1, resetAt: now + this.windowMs });
1924
+ return;
1925
+ }
1926
+ hit.count += 1;
1927
+ }
1928
+ /** Forget a key's failures, called after a successful attempt. */
1929
+ succeed(key) {
1930
+ this.hits.delete(key);
1931
+ }
1932
+ /**
1933
+ * Drop expired entries. Without this the map grows with every distinct source
1934
+ * address, which would turn the defence into a memory leak.
1935
+ */
1936
+ prune(now) {
1937
+ for (const [key, hit] of this.hits) {
1938
+ if (hit.resetAt <= now) this.hits.delete(key);
1939
+ }
1940
+ }
1941
+ };
1942
+ }
1943
+ });
1944
+
1945
+ // server/src/routes/auth.ts
1946
+ import crypto3 from "node:crypto";
1947
+ import fastifyCookie from "@fastify/cookie";
1948
+ function secretMatches(supplied, expected) {
1949
+ if (typeof supplied !== "string") return false;
1950
+ const a = crypto3.createHash("sha256").update(supplied).digest();
1951
+ const b = crypto3.createHash("sha256").update(expected).digest();
1952
+ return crypto3.timingSafeEqual(a, b);
1953
+ }
1954
+ function extractToken(req) {
1955
+ const auth = req.headers["authorization"];
1956
+ if (typeof auth === "string" && auth.startsWith("Bearer ")) {
1957
+ return auth.slice("Bearer ".length).trim();
1958
+ }
1959
+ const q = req.query;
1960
+ if (q?.token) return q.token;
1961
+ return void 0;
1962
+ }
1963
+ function authDisabled() {
1964
+ return !config.token;
1965
+ }
1966
+ function sessionToken(req) {
1967
+ return req.cookies?.[SESSION_COOKIE];
1968
+ }
1969
+ function hasValidSession(req) {
1970
+ return logins.verify(sessionToken(req)) !== null;
1971
+ }
1972
+ function cookieOptions() {
1973
+ return {
1974
+ path: "/",
1975
+ httpOnly: true,
1976
+ sameSite: "lax",
1977
+ secure: "auto",
1978
+ maxAge: config.sessionTtlSeconds
1979
+ };
1980
+ }
1981
+ async function registerAuth(app) {
1982
+ await app.register(fastifyCookie);
1983
+ app.post("/api/login", async (req, reply) => {
1984
+ if (!authDisabled()) {
1985
+ const limit = loginLimiter.check(req.ip);
1986
+ if (!limit.allowed) {
1987
+ return reply.code(429).header("retry-after", String(limit.retryAfterSeconds)).send({ error: `Too many attempts; try again in ${limit.retryAfterSeconds}s` });
1988
+ }
1989
+ const body = req.body ?? {};
1990
+ const supplied = body.token ?? extractToken(req);
1991
+ if (!secretMatches(supplied, config.token)) {
1992
+ loginLimiter.fail(req.ip);
1993
+ return reply.code(401).send({ error: "Invalid token" });
1994
+ }
1995
+ loginLimiter.succeed(req.ip);
1996
+ }
1997
+ const ua = req.headers["user-agent"];
1998
+ const { token } = logins.create(typeof ua === "string" ? ua : void 0);
1999
+ reply.setCookie(SESSION_COOKIE, token, cookieOptions());
2000
+ return { ok: true };
2001
+ });
2002
+ app.post("/api/logout", async (req, reply) => {
2003
+ logins.revokeToken(sessionToken(req));
2004
+ reply.clearCookie(SESSION_COOKIE, { path: "/" });
2005
+ return { ok: true };
2006
+ });
2007
+ app.get("/api/devices", async (req) => {
2008
+ return { devices: logins.list(sessionToken(req)) };
2009
+ });
2010
+ app.delete("/api/devices/:id", async (req, reply) => {
2011
+ const { id } = req.params;
2012
+ const current = logins.list(sessionToken(req)).find((d) => d.current);
2013
+ const removed = logins.revokeId(id);
2014
+ if (current?.id === id) reply.clearCookie(SESSION_COOKIE, { path: "/" });
2015
+ return { ok: removed };
2016
+ });
2017
+ app.post("/api/logout-all", async (_req, reply) => {
2018
+ logins.revokeAll();
2019
+ reply.clearCookie(SESSION_COOKIE, { path: "/" });
2020
+ return { ok: true };
2021
+ });
2022
+ app.addHook("onRequest", async (req, reply) => {
2023
+ if (!req.url.startsWith("/api/")) return;
2024
+ if (req.url === "/api/health" || req.url === "/api/login") return;
2025
+ if (authDisabled()) return;
2026
+ if (hasValidSession(req)) return;
2027
+ reply.code(401).send({ error: "Unauthorized: log in first" });
2028
+ });
2029
+ }
2030
+ var SESSION_COOKIE, logins, loginLimiter;
2031
+ var init_auth = __esm({
2032
+ "server/src/routes/auth.ts"() {
2033
+ "use strict";
2034
+ init_config();
2035
+ init_logins();
2036
+ init_rateLimit();
2037
+ SESSION_COOKIE = "casper.sid";
2038
+ logins = new LoginStore();
2039
+ loginLimiter = new AttemptLimiter(10, 15 * 60 * 1e3);
2040
+ }
2041
+ });
2042
+
2043
+ // server/src/session/models.ts
2044
+ import { execFile } from "node:child_process";
2045
+ import { promisify } from "node:util";
2046
+ async function listModels(log) {
2047
+ if (cache) return cache;
2048
+ const { stdout } = await execFileAsync(
2049
+ config.kiroBin,
2050
+ ["chat", "--list-models", "-f", "json"],
2051
+ { maxBuffer: 4 * 1024 * 1024 }
2052
+ );
2053
+ const parsed = JSON.parse(stdout);
2054
+ cache = parsed.models.map((m, i) => ({
2055
+ modelId: m.model_id,
2056
+ modelName: m.model_name,
2057
+ description: m.description,
2058
+ contextWindowTokens: m.context_window_tokens,
2059
+ rateMultiplier: m.rate_multiplier,
2060
+ rateUnit: m.rate_unit,
2061
+ isDefault: m.default_model ?? i === 0
2062
+ }));
2063
+ log.info({ count: cache.length }, "loaded model list");
2064
+ return cache;
2065
+ }
2066
+ async function kiroVersion() {
2067
+ try {
2068
+ const { stdout } = await execFileAsync(config.kiroBin, ["--version"]);
2069
+ return stdout.trim();
2070
+ } catch {
2071
+ return void 0;
2072
+ }
2073
+ }
2074
+ var execFileAsync, cache;
2075
+ var init_models = __esm({
2076
+ "server/src/session/models.ts"() {
2077
+ "use strict";
2078
+ init_config();
2079
+ execFileAsync = promisify(execFile);
2080
+ cache = null;
2081
+ }
2082
+ });
2083
+
2084
+ // server/src/routes/models.ts
2085
+ function registerModelRoutes(app) {
2086
+ app.get("/api/models", async () => {
2087
+ const models = await listModels(app.log);
2088
+ return { models };
2089
+ });
2090
+ }
2091
+ var init_models2 = __esm({
2092
+ "server/src/routes/models.ts"() {
2093
+ "use strict";
2094
+ init_models();
2095
+ }
2096
+ });
2097
+
2098
+ // server/src/session/agents.ts
2099
+ import { execFile as execFile2 } from "node:child_process";
2100
+ import { promisify as promisify2 } from "node:util";
2101
+ async function listAgents() {
2102
+ if (cache2) return cache2;
2103
+ const found = /* @__PURE__ */ new Map();
2104
+ for (const b of BUILTIN) found.set(b.id, b);
2105
+ try {
2106
+ const { stdout, stderr } = await execFileAsync2(config.kiroBin, ["agent", "list"], {
2107
+ cwd: config.defaultCwd,
2108
+ maxBuffer: 2 * 1024 * 1024
2109
+ });
2110
+ const text = (stderr || "") + "\n" + (stdout || "");
2111
+ for (const rawLine of text.split("\n")) {
2112
+ const line = rawLine.replace(ANSI, "");
2113
+ const m = /^\s{0,2}(\*\s)?([A-Za-z0-9_-]+)\s{2,}(Global|Workspace|Local)\b/.exec(
2114
+ line
2115
+ );
2116
+ if (!m) continue;
2117
+ const id = m[2];
2118
+ if (!found.has(id)) found.set(id, { id, name: id });
2119
+ }
2120
+ } catch {
2121
+ }
2122
+ cache2 = [...found.values()];
2123
+ return cache2;
2124
+ }
2125
+ var execFileAsync2, BUILTIN, cache2, ANSI;
2126
+ var init_agents = __esm({
2127
+ "server/src/session/agents.ts"() {
2128
+ "use strict";
2129
+ init_config();
2130
+ execFileAsync2 = promisify2(execFile2);
2131
+ BUILTIN = [
2132
+ { id: "kiro_default", name: "kiro_default", description: "General-purpose Kiro agent" },
2133
+ { id: "kiro_planner", name: "kiro_planner", description: "Planning-focused agent" },
2134
+ { id: "kiro_guide", name: "kiro_guide", description: "Guidance and Q&A agent" }
2135
+ ];
2136
+ cache2 = null;
2137
+ ANSI = /\x1b\[[0-9;]*m/g;
2138
+ }
2139
+ });
2140
+
2141
+ // server/src/routes/agents.ts
2142
+ function registerAgentRoutes(app) {
2143
+ app.get("/api/agents", async () => {
2144
+ return { agents: await listAgents(), defaultAgentId: config.defaultAgent };
2145
+ });
2146
+ }
2147
+ var init_agents2 = __esm({
2148
+ "server/src/routes/agents.ts"() {
2149
+ "use strict";
2150
+ init_agents();
2151
+ init_config();
2152
+ }
2153
+ });
2154
+
2155
+ // server/src/routes/fs.ts
2156
+ import fs9 from "node:fs/promises";
2157
+ import { createReadStream } from "node:fs";
2158
+ import path10 from "node:path";
2159
+ function confinedPath(input) {
2160
+ return confineToRoot(config.fileRoot, input);
2161
+ }
2162
+ function registerFsRoutes(app) {
2163
+ app.get(
2164
+ "/api/fs/dirs",
2165
+ async (req, reply) => {
2166
+ const input = (req.query.path ?? "").trim();
2167
+ const base = config.defaultCwd;
2168
+ const endsWithSep = input.endsWith("/");
2169
+ const resolved = input ? path10.resolve(base, input) : base;
2170
+ const dir = endsWithSep || !input ? resolved : path10.dirname(resolved);
2171
+ const prefix = endsWithSep || !input ? "" : path10.basename(resolved);
2172
+ const targetKind = await fs9.stat(resolved).then((s) => s.isDirectory() ? "directory" : "file").catch(() => "missing");
2173
+ if (confinedPath(dir) === null) {
2174
+ reply.code(403);
2175
+ return { error: "Path outside allowed root" };
2176
+ }
2177
+ const realDir = await realConfineToRoot(config.fileRoot, dir);
2178
+ if (!realDir) {
2179
+ return { dir, entries: [], target: resolved, targetKind };
2180
+ }
2181
+ let entries = [];
2182
+ try {
2183
+ const dirents = await fs9.readdir(realDir, { withFileTypes: true });
2184
+ const candidates = dirents.filter((d) => !d.name.startsWith("."));
2185
+ const checks2 = await Promise.all(
2186
+ candidates.map(async (d) => {
2187
+ if (d.isDirectory()) return d.name;
2188
+ if (!d.isSymbolicLink()) return null;
2189
+ const full = path10.join(realDir, d.name);
2190
+ const real = await realConfineToRoot(config.fileRoot, full);
2191
+ if (!real) return null;
2192
+ try {
2193
+ return (await fs9.stat(real)).isDirectory() ? d.name : null;
2194
+ } catch {
2195
+ return null;
2196
+ }
2197
+ })
2198
+ );
2199
+ entries = checks2.filter((name) => name !== null).filter((name) => name.toLowerCase().startsWith(prefix.toLowerCase())).sort((a, b) => a.localeCompare(b)).slice(0, 20).map((name) => path10.join(dir, name));
2200
+ } catch {
2201
+ entries = [];
2202
+ }
2203
+ return { dir, entries, target: resolved, targetKind };
2204
+ }
2205
+ );
2206
+ app.get(
2207
+ "/api/fs/image",
2208
+ async (req, reply) => {
2209
+ const filePath = (req.query.path ?? "").trim();
2210
+ if (!filePath) {
2211
+ reply.code(400);
2212
+ return { error: "path parameter is required" };
2213
+ }
2214
+ if (!path10.isAbsolute(filePath)) {
2215
+ reply.code(400);
2216
+ return { error: "path must be absolute" };
2217
+ }
2218
+ const resolved = confinedPath(filePath);
2219
+ if (resolved === null) {
2220
+ reply.code(403);
2221
+ return { error: "Path outside allowed root" };
2222
+ }
2223
+ const ext = path10.extname(resolved).toLowerCase();
2224
+ const mime = IMAGE_MIMES[ext];
2225
+ if (!mime) {
2226
+ reply.code(400);
2227
+ return { error: `Not a supported image type: ${ext}` };
2228
+ }
2229
+ const real = await realConfineToRoot(config.fileRoot, resolved);
2230
+ if (!real) {
2231
+ reply.code(404);
2232
+ return { error: "File not found" };
2233
+ }
2234
+ let stat;
2235
+ try {
2236
+ stat = await fs9.stat(real);
2237
+ } catch {
2238
+ reply.code(404);
2239
+ return { error: "File not found" };
2240
+ }
2241
+ if (!stat.isFile()) {
2242
+ reply.code(400);
2243
+ return { error: "Path is not a file" };
2244
+ }
2245
+ if (stat.size > MAX_IMAGE_BYTES) {
2246
+ reply.code(413);
2247
+ return { error: "Image too large" };
2248
+ }
2249
+ const etag = `W/"${stat.size}-${stat.mtimeMs}"`;
2250
+ reply.header("Cache-Control", "private, max-age=3600");
2251
+ reply.header("ETag", etag);
2252
+ reply.header("Last-Modified", stat.mtime.toUTCString());
2253
+ if (req.headers["if-none-match"] === etag) {
2254
+ reply.code(304);
2255
+ return reply.send();
2256
+ }
2257
+ reply.header("Content-Type", mime);
2258
+ reply.header("Content-Length", stat.size);
2259
+ return reply.send(createReadStream(real));
2260
+ }
2261
+ );
2262
+ }
2263
+ var IMAGE_MIMES, MAX_IMAGE_BYTES;
2264
+ var init_fs = __esm({
2265
+ "server/src/routes/fs.ts"() {
2266
+ "use strict";
2267
+ init_config();
2268
+ init_paths2();
2269
+ IMAGE_MIMES = {
2270
+ ".png": "image/png",
2271
+ ".jpg": "image/jpeg",
2272
+ ".jpeg": "image/jpeg",
2273
+ ".gif": "image/gif",
2274
+ ".webp": "image/webp",
2275
+ ".svg": "image/svg+xml",
2276
+ ".bmp": "image/bmp",
2277
+ ".ico": "image/x-icon",
2278
+ ".avif": "image/avif"
2279
+ };
2280
+ MAX_IMAGE_BYTES = 20 * 1024 * 1024;
2281
+ }
2282
+ });
2283
+
2284
+ // server/src/routes/sessions.ts
2285
+ function registerSessionRoutes(app, manager) {
2286
+ app.get("/api/sessions", async () => {
2287
+ return { sessions: await manager.listSessions() };
2288
+ });
2289
+ app.post("/api/sessions", async (req, reply) => {
2290
+ const body = req.body ?? {};
2291
+ try {
2292
+ return await manager.createSession({
2293
+ cwd: body.cwd,
2294
+ agentId: body.agentId,
2295
+ modelId: body.modelId
2296
+ });
2297
+ } catch (err) {
2298
+ reply.code(400);
2299
+ return { error: err.message };
2300
+ }
2301
+ });
2302
+ app.get("/api/sessions/:id", async (req, reply) => {
2303
+ try {
2304
+ return await manager.getDetail(req.params.id);
2305
+ } catch (err) {
2306
+ reply.code(404);
2307
+ return { error: err.message };
2308
+ }
2309
+ });
2310
+ app.get(
2311
+ "/api/sessions/:id/transcript",
2312
+ async (req, reply) => {
2313
+ const offset = Number.parseInt(req.query.offset ?? "", 10);
2314
+ const limit = Number.parseInt(req.query.limit ?? "", 10);
2315
+ if (!Number.isFinite(offset) || offset < 0 || !Number.isFinite(limit) || limit <= 0) {
2316
+ reply.code(400);
2317
+ return { error: "offset (>=0) and limit (>0) are required" };
2318
+ }
2319
+ try {
2320
+ return { items: await manager.getTranscriptPage(req.params.id, offset, limit) };
2321
+ } catch (err) {
2322
+ reply.code(404);
2323
+ return { error: err.message };
2324
+ }
2325
+ }
2326
+ );
2327
+ app.post(
2328
+ "/api/sessions/:id/prompt",
2329
+ async (req, reply) => {
2330
+ try {
2331
+ await manager.runPrompt(req.params.id, req.body.prompt);
2332
+ return { ok: true };
2333
+ } catch (err) {
2334
+ reply.code(400);
2335
+ return { error: err.message };
2336
+ }
2337
+ }
2338
+ );
2339
+ app.post("/api/sessions/:id/cancel", async (req) => {
2340
+ manager.cancel(req.params.id);
2341
+ return { ok: true };
2342
+ });
2343
+ app.post(
2344
+ "/api/sessions/:id/model",
2345
+ async (req) => {
2346
+ await manager.setModel(req.params.id, req.body.modelId);
2347
+ return { ok: true };
2348
+ }
2349
+ );
2350
+ app.post(
2351
+ "/api/sessions/:id/mode",
2352
+ async (req) => {
2353
+ await manager.setMode(req.params.id, req.body.modeId);
2354
+ return { ok: true };
2355
+ }
2356
+ );
2357
+ app.post(
2358
+ "/api/sessions/:id/rename",
2359
+ async (req) => {
2360
+ manager.renameSession(req.params.id, req.body.title);
2361
+ return { ok: true };
2362
+ }
2363
+ );
2364
+ app.post(
2365
+ "/api/sessions/:id/cwd",
2366
+ async (req, reply) => {
2367
+ const cwd = (req.body?.cwd ?? "").trim();
2368
+ if (!cwd) {
2369
+ reply.code(400);
2370
+ return { error: "cwd is required" };
2371
+ }
2372
+ try {
2373
+ return { ok: true, cwd: await manager.setSessionCwd(req.params.id, cwd) };
2374
+ } catch (err) {
2375
+ reply.code(400);
2376
+ return { error: err.message };
2377
+ }
2378
+ }
2379
+ );
2380
+ app.delete("/api/sessions/:id", async (req) => {
2381
+ await manager.deleteSession(req.params.id);
2382
+ return { ok: true };
2383
+ });
2384
+ }
2385
+ var init_sessions = __esm({
2386
+ "server/src/routes/sessions.ts"() {
2387
+ "use strict";
2388
+ }
2389
+ });
2390
+
2391
+ // server/src/util/filekind.ts
2392
+ import path11 from "node:path";
2393
+ function mimeForExt(ext) {
2394
+ return MIME_MAP[ext.toLowerCase()] ?? "application/octet-stream";
2395
+ }
2396
+ function looksBinary(buf) {
2397
+ if (buf.length === 0) return false;
2398
+ if (buf.length >= 2) {
2399
+ const b0 = buf[0];
2400
+ const b1 = buf[1];
2401
+ if (b0 === 255 && b1 === 254 || b0 === 254 && b1 === 255) return true;
2402
+ }
2403
+ let control = 0;
2404
+ for (let i = 0; i < buf.length; i++) {
2405
+ const b = buf[i];
2406
+ if (b === 0) return true;
2407
+ if (b < 32 && b !== 9 && b !== 10 && b !== 12 && b !== 13) control++;
2408
+ }
2409
+ return control / buf.length > 0.3;
2410
+ }
2411
+ function classifyKind(name) {
2412
+ const ext = path11.extname(name).toLowerCase();
2413
+ if (ext in IMAGE_EXTS2) return "image";
2414
+ if (TEXT_EXTS.has(ext)) return "text";
2415
+ return "binary";
2416
+ }
2417
+ var IMAGE_EXTS2, TEXT_EXTS, MIME_MAP;
2418
+ var init_filekind = __esm({
2419
+ "server/src/util/filekind.ts"() {
2420
+ "use strict";
2421
+ IMAGE_EXTS2 = {
2422
+ ".png": "image/png",
2423
+ ".jpg": "image/jpeg",
2424
+ ".jpeg": "image/jpeg",
2425
+ ".gif": "image/gif",
2426
+ ".webp": "image/webp",
2427
+ ".svg": "image/svg+xml",
2428
+ ".bmp": "image/bmp",
2429
+ ".ico": "image/x-icon",
2430
+ ".avif": "image/avif"
2431
+ };
2432
+ TEXT_EXTS = /* @__PURE__ */ new Set([
2433
+ ".txt",
2434
+ ".md",
2435
+ ".markdown",
2436
+ ".rst",
2437
+ ".log",
2438
+ ".csv",
2439
+ ".tsv",
2440
+ ".json",
2441
+ ".jsonl",
2442
+ ".yaml",
2443
+ ".yml",
2444
+ ".toml",
2445
+ ".ini",
2446
+ ".env",
2447
+ ".cfg",
2448
+ ".conf",
2449
+ ".xml",
2450
+ ".html",
2451
+ ".htm",
2452
+ ".css",
2453
+ ".scss",
2454
+ ".less",
2455
+ ".js",
2456
+ ".mjs",
2457
+ ".cjs",
2458
+ ".jsx",
2459
+ ".ts",
2460
+ ".tsx",
2461
+ ".vue",
2462
+ ".svelte",
2463
+ ".py",
2464
+ ".rb",
2465
+ ".go",
2466
+ ".rs",
2467
+ ".java",
2468
+ ".kt",
2469
+ ".c",
2470
+ ".h",
2471
+ ".cpp",
2472
+ ".hpp",
2473
+ ".cc",
2474
+ ".cs",
2475
+ ".php",
2476
+ ".swift",
2477
+ ".scala",
2478
+ ".sh",
2479
+ ".bash",
2480
+ ".zsh",
2481
+ ".fish",
2482
+ ".sql",
2483
+ ".graphql",
2484
+ ".proto",
2485
+ ".dockerfile",
2486
+ ".makefile",
2487
+ ".gradle",
2488
+ ".diff",
2489
+ ".patch",
2490
+ ".tex"
2491
+ ]);
2492
+ MIME_MAP = {
2493
+ ...IMAGE_EXTS2,
2494
+ ".txt": "text/plain",
2495
+ ".md": "text/markdown",
2496
+ ".csv": "text/csv",
2497
+ ".json": "application/json",
2498
+ ".xml": "application/xml",
2499
+ ".yaml": "text/yaml",
2500
+ ".yml": "text/yaml",
2501
+ ".html": "text/html",
2502
+ ".css": "text/css",
2503
+ ".js": "application/javascript",
2504
+ ".mjs": "application/javascript",
2505
+ ".ts": "text/typescript",
2506
+ ".tsx": "text/typescript",
2507
+ ".toml": "text/plain",
2508
+ ".pdf": "application/pdf",
2509
+ ".zip": "application/zip",
2510
+ ".tar": "application/x-tar",
2511
+ ".gz": "application/gzip",
2512
+ ".wasm": "application/wasm",
2513
+ ".exe": "application/vnd.microsoft.portable-executable",
2514
+ ".dll": "application/vnd.microsoft.portable-executable",
2515
+ ".so": "application/x-sharedlib",
2516
+ ".bin": "application/octet-stream"
2517
+ };
2518
+ }
2519
+ });
2520
+
2521
+ // server/src/routes/workspace.ts
2522
+ import fs10 from "node:fs/promises";
2523
+ import { createReadStream as createReadStream2 } from "node:fs";
2524
+ import path12 from "node:path";
2525
+ function tooLargeForPreview(size, maxBytes) {
2526
+ return {
2527
+ error: `File too large for preview (${(size / 1024 / 1024).toFixed(1)} MB, max ${maxBytes / 1024 / 1024} MB)`
2528
+ };
2529
+ }
2530
+ function hexdump(buf) {
2531
+ const lines = [];
2532
+ for (let off = 0; off < buf.length; off += 16) {
2533
+ const slice = buf.subarray(off, off + 16);
2534
+ const hex = [];
2535
+ let ascii = "";
2536
+ for (let i = 0; i < 16; i++) {
2537
+ if (i < slice.length) {
2538
+ const b = slice[i];
2539
+ hex.push(b.toString(16).padStart(2, "0"));
2540
+ ascii += b >= 32 && b <= 126 ? String.fromCharCode(b) : ".";
2541
+ } else {
2542
+ hex.push(" ");
2543
+ }
2544
+ if (i === 7) hex.push("");
2545
+ }
2546
+ lines.push(`${off.toString(16).padStart(8, "0")} ${hex.join(" ")} |${ascii}|`);
2547
+ }
2548
+ return lines.join("\n");
2549
+ }
2550
+ function registerWorkspaceRoutes(app, manager) {
2551
+ app.get(
2552
+ "/api/sessions/:id/tree",
2553
+ async (req, reply) => {
2554
+ let cwd;
2555
+ try {
2556
+ cwd = await manager.getSessionCwd(req.params.id);
2557
+ } catch {
2558
+ reply.code(404);
2559
+ return { error: "Session not found" };
2560
+ }
2561
+ const relative = (req.query.path ?? "").replace(/^\/+/, "");
2562
+ const target = confineToRoot(cwd, relative);
2563
+ if (!target) {
2564
+ reply.code(400);
2565
+ return { error: "Invalid path" };
2566
+ }
2567
+ const workspaceMissing = async () => {
2568
+ try {
2569
+ return !(await fs10.stat(cwd)).isDirectory();
2570
+ } catch {
2571
+ return true;
2572
+ }
2573
+ };
2574
+ const notFound = async () => {
2575
+ reply.code(404);
2576
+ return await workspaceMissing() ? { error: `Workspace folder no longer exists: ${cwd}` } : { error: "Directory not found" };
2577
+ };
2578
+ const realTarget = await realConfineToRoot(config.fileRoot, target);
2579
+ if (!realTarget) {
2580
+ return notFound();
2581
+ }
2582
+ let dirents;
2583
+ try {
2584
+ dirents = await fs10.readdir(realTarget, { withFileTypes: true, encoding: "utf8" });
2585
+ } catch {
2586
+ return notFound();
2587
+ }
2588
+ const entries = [];
2589
+ for (const d of dirents) {
2590
+ const name = d.name;
2591
+ const entryRelative = relative ? `${relative}/${name}` : name;
2592
+ const entryAbsolute = path12.join(realTarget, name);
2593
+ let kind = d.isDirectory() ? "directory" : d.isFile() ? "file" : null;
2594
+ let statTarget = entryAbsolute;
2595
+ if (kind === null && d.isSymbolicLink()) {
2596
+ const real = await realConfineToRoot(config.fileRoot, entryAbsolute);
2597
+ if (!real) continue;
2598
+ try {
2599
+ const st = await fs10.stat(real);
2600
+ kind = st.isDirectory() ? "directory" : st.isFile() ? "file" : null;
2601
+ statTarget = real;
2602
+ } catch {
2603
+ continue;
2604
+ }
2605
+ }
2606
+ if (kind === null) continue;
2607
+ if (kind === "directory") {
2608
+ entries.push({ name, path: entryRelative, type: "directory" });
2609
+ } else {
2610
+ try {
2611
+ const stat = await fs10.stat(statTarget);
2612
+ entries.push({
2613
+ name,
2614
+ path: entryRelative,
2615
+ type: "file",
2616
+ size: stat.size,
2617
+ modifiedAt: stat.mtime.toISOString()
2618
+ });
2619
+ } catch {
2620
+ }
2621
+ }
2622
+ }
2623
+ entries.sort((a, b) => {
2624
+ if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
2625
+ return a.name.localeCompare(b.name);
2626
+ });
2627
+ const response = { cwd, relativeTo: relative, entries };
2628
+ return response;
2629
+ }
2630
+ );
2631
+ app.get(
2632
+ "/api/sessions/:id/download",
2633
+ async (req, reply) => {
2634
+ let cwd;
2635
+ try {
2636
+ cwd = await manager.getSessionCwd(req.params.id);
2637
+ } catch {
2638
+ reply.code(404);
2639
+ return { error: "Session not found" };
2640
+ }
2641
+ const relative = (req.query.path ?? "").replace(/^\/+/, "");
2642
+ if (!relative) {
2643
+ reply.code(400);
2644
+ return { error: "path parameter is required" };
2645
+ }
2646
+ const target = confineToRoot(cwd, relative);
2647
+ if (!target) {
2648
+ reply.code(400);
2649
+ return { error: "Invalid path" };
2650
+ }
2651
+ const realTarget = await realConfineToRoot(config.fileRoot, target);
2652
+ if (!realTarget) {
2653
+ reply.code(404);
2654
+ return { error: "File not found" };
2655
+ }
2656
+ let stat;
2657
+ try {
2658
+ stat = await fs10.stat(realTarget);
2659
+ } catch {
2660
+ reply.code(404);
2661
+ return { error: "File not found" };
2662
+ }
2663
+ if (!stat.isFile()) {
2664
+ reply.code(400);
2665
+ return { error: "Path is not a file" };
2666
+ }
2667
+ if (stat.size > MAX_DOWNLOAD_BYTES) {
2668
+ reply.code(413);
2669
+ return { error: `File too large (${(stat.size / 1024 / 1024).toFixed(1)} MB, max 100 MB)` };
2670
+ }
2671
+ const ext = path12.extname(realTarget);
2672
+ const filename = path12.basename(realTarget);
2673
+ const asciiName = filename.replace(/[^\w.\-]/g, "_");
2674
+ reply.header("Content-Type", mimeForExt(ext));
2675
+ reply.header(
2676
+ "Content-Disposition",
2677
+ `attachment; filename="${asciiName}"; filename*=UTF-8''${encodeURIComponent(filename)}`
2678
+ );
2679
+ reply.header("Content-Length", stat.size);
2680
+ return reply.send(createReadStream2(realTarget));
2681
+ }
2682
+ );
2683
+ app.get(
2684
+ "/api/sessions/:id/preview",
2685
+ async (req, reply) => {
2686
+ let cwd;
2687
+ try {
2688
+ cwd = await manager.getSessionCwd(req.params.id);
2689
+ } catch {
2690
+ reply.code(404);
2691
+ return { error: "Session not found" };
2692
+ }
2693
+ const relative = (req.query.path ?? "").replace(/^\/+/, "");
2694
+ if (!relative) {
2695
+ reply.code(400);
2696
+ return { error: "path parameter is required" };
2697
+ }
2698
+ const target = confineToRoot(cwd, relative);
2699
+ if (!target) {
2700
+ reply.code(400);
2701
+ return { error: "Invalid path" };
2702
+ }
2703
+ const realTarget = await realConfineToRoot(config.fileRoot, target);
2704
+ if (!realTarget) {
2705
+ reply.code(404);
2706
+ return { error: "File not found" };
2707
+ }
2708
+ let stat;
2709
+ try {
2710
+ stat = await fs10.stat(realTarget);
2711
+ } catch {
2712
+ reply.code(404);
2713
+ return { error: "File not found" };
2714
+ }
2715
+ if (!stat.isFile()) {
2716
+ reply.code(400);
2717
+ return { error: "Path is not a file" };
2718
+ }
2719
+ const ext = path12.extname(realTarget).toLowerCase();
2720
+ const mime = mimeForExt(ext);
2721
+ const isImage = mime.startsWith("image/");
2722
+ const isPdf = mime === "application/pdf";
2723
+ const kind = classifyKind(realTarget);
2724
+ if (isImage || isPdf) {
2725
+ if (stat.size > MAX_IMAGE_PREVIEW_BYTES) {
2726
+ reply.code(413);
2727
+ return tooLargeForPreview(stat.size, MAX_IMAGE_PREVIEW_BYTES);
2728
+ }
2729
+ } else if (kind !== "binary") {
2730
+ if (stat.size > MAX_TEXT_PREVIEW_BYTES) {
2731
+ reply.code(413);
2732
+ return tooLargeForPreview(stat.size, MAX_TEXT_PREVIEW_BYTES);
2733
+ }
2734
+ }
2735
+ if (isImage || isPdf) {
2736
+ const etag = `W/"${stat.size}-${stat.mtimeMs}"`;
2737
+ reply.header("Cache-Control", "private, max-age=3600");
2738
+ reply.header("ETag", etag);
2739
+ reply.header("Last-Modified", stat.mtime.toUTCString());
2740
+ const ifNoneMatch = req.headers["if-none-match"];
2741
+ if (ifNoneMatch === etag) {
2742
+ reply.code(304);
2743
+ return reply.send();
2744
+ }
2745
+ reply.header("Content-Type", mime);
2746
+ reply.header("Content-Disposition", "inline");
2747
+ reply.header("Content-Length", stat.size);
2748
+ return reply.send(createReadStream2(realTarget));
2749
+ }
2750
+ if (kind === "binary") {
2751
+ let fh;
2752
+ try {
2753
+ fh = await fs10.open(realTarget, "r");
2754
+ const buf = Buffer.alloc(Math.min(HEXDUMP_BYTES, stat.size));
2755
+ const { bytesRead } = await fh.read(buf, 0, buf.length, 0);
2756
+ const head = buf.subarray(0, bytesRead);
2757
+ if (!looksBinary(head)) {
2758
+ if (stat.size > MAX_TEXT_PREVIEW_BYTES) {
2759
+ reply.code(413);
2760
+ return tooLargeForPreview(stat.size, MAX_TEXT_PREVIEW_BYTES);
2761
+ }
2762
+ reply.header("Content-Type", "text/plain; charset=utf-8");
2763
+ reply.header("Content-Disposition", "inline");
2764
+ reply.header("Content-Length", stat.size);
2765
+ return reply.send(createReadStream2(realTarget));
2766
+ }
2767
+ const dump = hexdump(head);
2768
+ const header = `Binary file - ${stat.size} bytes
2769
+ Showing first ${bytesRead} bytes as hexdump:
2770
+
2771
+ `;
2772
+ const body = header + dump + (stat.size > bytesRead ? "\n\n\u2026 (truncated)" : "");
2773
+ reply.header("Content-Type", "text/plain; charset=utf-8");
2774
+ reply.header("Content-Disposition", "inline");
2775
+ return reply.send(body);
2776
+ } finally {
2777
+ await fh?.close();
2778
+ }
2779
+ }
2780
+ reply.header("Content-Type", "text/plain; charset=utf-8");
2781
+ reply.header("Content-Disposition", "inline");
2782
+ reply.header("Content-Length", stat.size);
2783
+ return reply.send(createReadStream2(realTarget));
2784
+ }
2785
+ );
2786
+ }
2787
+ var MAX_DOWNLOAD_BYTES, HEXDUMP_BYTES, MAX_TEXT_PREVIEW_BYTES, MAX_IMAGE_PREVIEW_BYTES;
2788
+ var init_workspace = __esm({
2789
+ "server/src/routes/workspace.ts"() {
2790
+ "use strict";
2791
+ init_config();
2792
+ init_paths2();
2793
+ init_filekind();
2794
+ MAX_DOWNLOAD_BYTES = 100 * 1024 * 1024;
2795
+ HEXDUMP_BYTES = 4096;
2796
+ MAX_TEXT_PREVIEW_BYTES = 1024 * 1024;
2797
+ MAX_IMAGE_PREVIEW_BYTES = 20 * 1024 * 1024;
2798
+ }
2799
+ });
2800
+
2801
+ // server/src/routes/uploads.ts
2802
+ import { createReadStream as createReadStream3, createWriteStream } from "node:fs";
2803
+ import fs11 from "node:fs/promises";
2804
+ import path13 from "node:path";
2805
+ import crypto4 from "node:crypto";
2806
+ import { execFile as execFile3 } from "node:child_process";
2807
+ import { pipeline } from "node:stream/promises";
2808
+ function sanitizeName(raw) {
2809
+ const base = path13.basename(raw).replace(/[^\w.\- ]/g, "_").trim();
2810
+ if (!base || base === "." || base === "..") return `upload-${Date.now()}`;
2811
+ return base.replace(/^\.+/, "") || `upload-${Date.now()}`;
2812
+ }
2813
+ async function uniquePath(dir, name) {
2814
+ const ext = path13.extname(name);
2815
+ const stem = name.slice(0, name.length - ext.length);
2816
+ let candidate = name;
2817
+ let n = 1;
2818
+ while (true) {
2819
+ try {
2820
+ await fs11.access(path13.join(dir, candidate));
2821
+ candidate = `${stem} (${n++})${ext}`;
2822
+ } catch {
2823
+ return path13.join(dir, candidate);
2824
+ }
2825
+ }
2826
+ }
2827
+ function sha256File(absPath) {
2828
+ return new Promise((resolve, reject) => {
2829
+ const hash = crypto4.createHash("sha256");
2830
+ createReadStream3(absPath).on("data", (d) => hash.update(d)).on("end", () => resolve(hash.digest("hex"))).on("error", reject);
2831
+ });
2832
+ }
2833
+ function fileType(absPath) {
2834
+ return new Promise((resolve) => {
2835
+ execFile3("file", ["-b", absPath], { timeout: 5e3 }, (err, stdout) => {
2836
+ resolve(err ? "" : stdout.trim());
2837
+ });
2838
+ });
2839
+ }
2840
+ async function sampleStrings(absPath, limit = 40) {
2841
+ let fh;
2842
+ try {
2843
+ fh = await fs11.open(absPath, "r");
2844
+ const buf = Buffer.alloc(STRINGS_SCAN_BYTES);
2845
+ const { bytesRead } = await fh.read(buf, 0, STRINGS_SCAN_BYTES, 0);
2846
+ const out = [];
2847
+ let cur = "";
2848
+ for (let i = 0; i < bytesRead; i++) {
2849
+ const c = buf[i];
2850
+ if (c >= 32 && c <= 126) {
2851
+ cur += String.fromCharCode(c);
2852
+ } else {
2853
+ if (cur.length >= 6) out.push(cur);
2854
+ cur = "";
2855
+ if (out.length >= limit) break;
2856
+ }
2857
+ }
2858
+ if (cur.length >= 6 && out.length < limit) out.push(cur);
2859
+ return out;
2860
+ } catch {
2861
+ return [];
2862
+ } finally {
2863
+ await fh?.close();
2864
+ }
2865
+ }
2866
+ function registerUploadRoutes(app, manager) {
2867
+ app.post(
2868
+ "/api/sessions/:id/uploads",
2869
+ async (req, reply) => {
2870
+ let cwd;
2871
+ try {
2872
+ cwd = await manager.getSessionCwd(req.params.id);
2873
+ } catch {
2874
+ reply.code(404);
2875
+ return { error: "Session not found" };
2876
+ }
2877
+ const uploadDir = path13.join(cwd, UPLOAD_SUBDIR);
2878
+ await fs11.mkdir(uploadDir, { recursive: true });
2879
+ const results = [];
2880
+ const parts = req.files();
2881
+ for await (const part of parts) {
2882
+ const name = sanitizeName(part.filename ?? "upload");
2883
+ if (confineToRoot(uploadDir, name) === null) continue;
2884
+ const dest = await uniquePath(uploadDir, name);
2885
+ try {
2886
+ await pipeline(part.file, createWriteStream(dest));
2887
+ } catch {
2888
+ reply.code(500);
2889
+ return { error: `Failed to store ${name}` };
2890
+ }
2891
+ if (part.file.truncated) {
2892
+ await fs11.rm(dest, { force: true });
2893
+ reply.code(413);
2894
+ return {
2895
+ error: `${name} exceeds the max upload size (${Math.round(config.maxUploadBytes / 1024 / 1024)} MB)`
2896
+ };
2897
+ }
2898
+ const stat = await fs11.stat(dest);
2899
+ const ext = path13.extname(dest);
2900
+ const kind = classifyKind(dest);
2901
+ const relative = path13.relative(cwd, dest);
2902
+ const uploaded = {
2903
+ name: path13.basename(dest),
2904
+ path: relative,
2905
+ size: stat.size,
2906
+ mimeType: mimeForExt(ext),
2907
+ kind
2908
+ };
2909
+ if (kind === "binary") {
2910
+ const [type, sha2563, strings] = await Promise.all([
2911
+ fileType(dest),
2912
+ sha256File(dest),
2913
+ sampleStrings(dest)
2914
+ ]);
2915
+ uploaded.triage = {
2916
+ fileType: type || void 0,
2917
+ sha256: sha2563,
2918
+ strings: strings.length ? strings : void 0
2919
+ };
2920
+ }
2921
+ results.push(uploaded);
2922
+ }
2923
+ const response = { files: results };
2924
+ return response;
2925
+ }
2926
+ );
2927
+ }
2928
+ var UPLOAD_SUBDIR, STRINGS_SCAN_BYTES;
2929
+ var init_uploads = __esm({
2930
+ "server/src/routes/uploads.ts"() {
2931
+ "use strict";
2932
+ init_config();
2933
+ init_filekind();
2934
+ init_paths2();
2935
+ UPLOAD_SUBDIR = path13.join(".casper", "uploads");
2936
+ STRINGS_SCAN_BYTES = 256 * 1024;
2937
+ }
2938
+ });
2939
+
2940
+ // server/src/routes/health.ts
2941
+ function registerHealthRoute(app, manager, startedAt) {
2942
+ app.get("/api/health", async () => {
2943
+ return {
2944
+ status: "ok",
2945
+ kiroVersion: await kiroVersion(),
2946
+ liveSessions: manager.liveCount,
2947
+ uptimeMs: Date.now() - startedAt
2948
+ };
2949
+ });
2950
+ }
2951
+ var init_health = __esm({
2952
+ "server/src/routes/health.ts"() {
2953
+ "use strict";
2954
+ init_models();
2955
+ }
2956
+ });
2957
+
2958
+ // server/src/ws/dispatch.ts
2959
+ function send(socket, msg) {
2960
+ if (socket.readyState === socket.OPEN) socket.send(JSON.stringify(msg));
2961
+ }
2962
+ async function handleClientMessage(socket, manager, sessionId, msg) {
2963
+ switch (msg.type) {
2964
+ case "ping":
2965
+ send(socket, { type: "pong" });
2966
+ return;
2967
+ case "hello":
2968
+ return;
2969
+ case "prompt":
2970
+ try {
2971
+ await manager.runPrompt(sessionId, msg.content);
2972
+ send(socket, { type: "ack", action: "prompt", ok: true });
2973
+ } catch (err) {
2974
+ send(socket, {
2975
+ type: "ack",
2976
+ action: "prompt",
2977
+ ok: false,
2978
+ error: err.message
2979
+ });
2980
+ }
2981
+ return;
2982
+ case "cancel":
2983
+ try {
2984
+ manager.cancel(sessionId);
2985
+ send(socket, { type: "ack", action: "cancel", ok: true });
2986
+ } catch (err) {
2987
+ send(socket, {
2988
+ type: "ack",
2989
+ action: "cancel",
2990
+ ok: false,
2991
+ error: err.message
2992
+ });
2993
+ }
2994
+ return;
2995
+ case "set_mode":
2996
+ try {
2997
+ await manager.setMode(sessionId, msg.modeId);
2998
+ send(socket, { type: "ack", action: "set_mode", ok: true });
2999
+ } catch (err) {
3000
+ send(socket, {
3001
+ type: "ack",
3002
+ action: "set_mode",
3003
+ ok: false,
3004
+ error: err.message
3005
+ });
3006
+ }
3007
+ return;
3008
+ case "set_model":
3009
+ try {
3010
+ await manager.setModel(sessionId, msg.modelId);
3011
+ send(socket, { type: "ack", action: "set_model", ok: true });
3012
+ } catch (err) {
3013
+ send(socket, {
3014
+ type: "ack",
3015
+ action: "set_model",
3016
+ ok: false,
3017
+ error: err.message
3018
+ });
3019
+ }
3020
+ return;
3021
+ case "exec_command":
3022
+ try {
3023
+ await manager.execCommand(sessionId, msg.command, msg.args);
3024
+ send(socket, { type: "ack", action: "exec_command", ok: true });
3025
+ } catch (err) {
3026
+ send(socket, {
3027
+ type: "ack",
3028
+ action: "exec_command",
3029
+ ok: false,
3030
+ error: err.message
3031
+ });
3032
+ }
3033
+ return;
3034
+ default:
3035
+ send(socket, { type: "error", message: `Unknown message type` });
3036
+ }
3037
+ }
3038
+ var init_dispatch = __esm({
3039
+ "server/src/ws/dispatch.ts"() {
3040
+ "use strict";
3041
+ }
3042
+ });
3043
+
3044
+ // server/src/ws/gateway.ts
3045
+ function registerWsGateway(app, manager) {
3046
+ app.get("/ws", { websocket: true }, (socket, req) => {
3047
+ const query = req.query;
3048
+ if (!authDisabled() && !hasValidSession(req)) {
3049
+ send(socket, { type: "error", message: "Unauthorized" });
3050
+ socket.close(1008, "Unauthorized");
3051
+ return;
3052
+ }
3053
+ const sessionId = query.sessionId;
3054
+ if (!sessionId) {
3055
+ send(socket, { type: "error", message: "Missing sessionId" });
3056
+ socket.close(1008, "Missing sessionId");
3057
+ return;
3058
+ }
3059
+ let cursor = Number.parseInt(query.cursor ?? "0", 10) || 0;
3060
+ let unsubscribe = null;
3061
+ let alive = true;
3062
+ let ready = false;
3063
+ const forward = (event) => {
3064
+ if (event.seq <= cursor) return;
3065
+ cursor = event.seq;
3066
+ send(socket, { type: "event", event });
3067
+ };
3068
+ const attach = async () => {
3069
+ try {
3070
+ await manager.ensureOpen(sessionId);
3071
+ } catch (err) {
3072
+ send(socket, { type: "error", message: err.message });
3073
+ socket.close(1011, "open failed");
3074
+ return;
3075
+ }
3076
+ const store = manager.getStore(sessionId);
3077
+ if (!store) {
3078
+ send(socket, { type: "error", message: "Session store unavailable" });
3079
+ socket.close(1011, "no store");
3080
+ return;
3081
+ }
3082
+ const { events, gap } = store.getSince(cursor);
3083
+ if (gap) {
3084
+ send(socket, {
3085
+ type: "resync",
3086
+ reason: "cursor older than buffer; refetch full transcript"
3087
+ });
3088
+ cursor = store.head();
3089
+ } else {
3090
+ for (const e of events) forward(e);
3091
+ }
3092
+ send(socket, { type: "replay_complete", head: store.head() });
3093
+ unsubscribe = manager.onEvent(sessionId, forward);
3094
+ ready = true;
3095
+ };
3096
+ void attach();
3097
+ const heartbeat = setInterval(() => {
3098
+ if (!alive) {
3099
+ socket.terminate();
3100
+ return;
3101
+ }
3102
+ alive = false;
3103
+ try {
3104
+ socket.ping();
3105
+ } catch {
3106
+ }
3107
+ }, HEARTBEAT_MS);
3108
+ heartbeat.unref?.();
3109
+ socket.on("pong", () => {
3110
+ alive = true;
3111
+ });
3112
+ socket.on("message", (raw) => {
3113
+ alive = true;
3114
+ if (!ready) return;
3115
+ let msg;
3116
+ try {
3117
+ msg = JSON.parse(raw.toString());
3118
+ } catch {
3119
+ send(socket, { type: "error", message: "Invalid JSON" });
3120
+ return;
3121
+ }
3122
+ void handleClientMessage(socket, manager, sessionId, msg);
3123
+ });
3124
+ socket.on("close", () => {
3125
+ clearInterval(heartbeat);
3126
+ unsubscribe?.();
3127
+ });
3128
+ });
3129
+ }
3130
+ var HEARTBEAT_MS;
3131
+ var init_gateway = __esm({
3132
+ "server/src/ws/gateway.ts"() {
3133
+ "use strict";
3134
+ init_auth();
3135
+ init_dispatch();
3136
+ HEARTBEAT_MS = 2e4;
3137
+ }
3138
+ });
3139
+
3140
+ // server/src/app.ts
3141
+ import fs12 from "node:fs";
3142
+ import Fastify from "fastify";
3143
+ import cors from "@fastify/cors";
3144
+ import websocket from "@fastify/websocket";
3145
+ import fastifyStatic from "@fastify/static";
3146
+ import multipart from "@fastify/multipart";
3147
+ async function buildApp() {
3148
+ const isProd = process.env.NODE_ENV === "production";
3149
+ const app = Fastify({
3150
+ // Honor X-Forwarded-Proto so `secure: 'auto'` cookies detect HTTPS when the
3151
+ // server sits behind a TLS-terminating tunnel or reverse proxy.
3152
+ trustProxy: true,
3153
+ // In production, skip the two-lines-per-request access log; our own
3154
+ // logger.info/warn/error calls still fire. Dev keeps it for debugging.
3155
+ disableRequestLogging: isProd,
3156
+ logger: {
3157
+ level: process.env.LOG_LEVEL ?? "info",
3158
+ transport: isProd ? void 0 : { target: "pino/file", options: { destination: 2 } }
3159
+ }
3160
+ });
3161
+ const manager = new SessionManager(logger);
3162
+ const startedAt = Date.now();
3163
+ await app.register(cors, { origin: true });
3164
+ await app.register(websocket, {
3165
+ options: { maxPayload: 16 * 1024 * 1024 }
3166
+ });
3167
+ await app.register(multipart, {
3168
+ limits: { fileSize: config.maxUploadBytes, files: 20 }
3169
+ });
3170
+ await registerAuth(app);
3171
+ registerHealthRoute(app, manager, startedAt);
3172
+ registerModelRoutes(app);
3173
+ registerAgentRoutes(app);
3174
+ registerFsRoutes(app);
3175
+ registerSessionRoutes(app, manager);
3176
+ registerWorkspaceRoutes(app, manager);
3177
+ registerUploadRoutes(app, manager);
3178
+ registerWsGateway(app, manager);
3179
+ if (fs12.existsSync(config.webDist)) {
3180
+ await app.register(fastifyStatic, {
3181
+ root: config.webDist
3182
+ });
3183
+ app.setNotFoundHandler((req, reply) => {
3184
+ const path16 = req.url.split("?")[0] ?? "";
3185
+ const looksLikeAsset = path16.startsWith("/assets/") || /\.[a-zA-Z0-9]+$/.test(path16);
3186
+ if (path16.startsWith("/api/") || path16.startsWith("/ws") || looksLikeAsset) {
3187
+ reply.code(404).send({ error: "Not found" });
3188
+ return;
3189
+ }
3190
+ reply.sendFile("index.html");
3191
+ });
3192
+ }
3193
+ return { app, manager };
3194
+ }
3195
+ var init_app = __esm({
3196
+ "server/src/app.ts"() {
3197
+ "use strict";
3198
+ init_config();
3199
+ init_logger();
3200
+ init_SessionManager();
3201
+ init_auth();
3202
+ init_models2();
3203
+ init_agents2();
3204
+ init_fs();
3205
+ init_sessions();
3206
+ init_workspace();
3207
+ init_uploads();
3208
+ init_health();
3209
+ init_gateway();
3210
+ }
3211
+ });
3212
+
3213
+ // server/src/server.ts
3214
+ var server_exports = {};
3215
+ __export(server_exports, {
3216
+ serve: () => serve
3217
+ });
3218
+ async function serve() {
3219
+ if (!isWithinRoot(config.fileRoot, config.defaultCwd)) {
3220
+ logger.warn(
3221
+ { fileRoot: config.fileRoot, defaultCwd: config.defaultCwd },
3222
+ "DEFAULT_CWD is outside CASPER_FILE_ROOT; new sessions and the directory picker will be rejected. Set CASPER_FILE_ROOT to an ancestor of DEFAULT_CWD."
3223
+ );
3224
+ }
3225
+ const { app, manager } = await buildApp();
3226
+ await app.listen({ host: config.host, port: config.port });
3227
+ logger.info(
3228
+ { host: config.host, port: config.port, auth: config.token ? "on" : "OFF" },
3229
+ "Casper server listening"
3230
+ );
3231
+ if (!config.token) {
3232
+ logger.warn("CASPER_TOKEN is empty - auth is DISABLED. Set it before exposing on a network.");
3233
+ }
3234
+ const shutdown = async (signal) => {
3235
+ logger.info({ signal }, "shutting down; draining live sessions");
3236
+ manager.disposeAll();
3237
+ await app.close();
3238
+ process.exit(0);
3239
+ };
3240
+ process.on("SIGINT", () => void shutdown("SIGINT"));
3241
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
3242
+ }
3243
+ var init_server = __esm({
3244
+ "server/src/server.ts"() {
3245
+ "use strict";
3246
+ init_app();
3247
+ init_config();
3248
+ init_logger();
3249
+ init_paths2();
3250
+ }
3251
+ });
3252
+
3253
+ // server/src/cli/doctor.ts
3254
+ var doctor_exports = {};
3255
+ __export(doctor_exports, {
3256
+ doctor: () => doctor
3257
+ });
3258
+ import { execFileSync as execFileSync3 } from "node:child_process";
3259
+ import fs13 from "node:fs";
3260
+ import path14 from "node:path";
3261
+ function which(bin) {
3262
+ if (bin.includes("/")) return fs13.existsSync(bin) ? bin : void 0;
3263
+ for (const dir of (process.env.PATH ?? "").split(path14.delimiter)) {
3264
+ if (dir === "") continue;
3265
+ const candidate = path14.join(dir, bin);
3266
+ try {
3267
+ fs13.accessSync(candidate, fs13.constants.X_OK);
3268
+ return candidate;
3269
+ } catch {
3270
+ }
3271
+ }
3272
+ return void 0;
3273
+ }
3274
+ function checks() {
3275
+ const out = [];
3276
+ out.push({
3277
+ ok: true,
3278
+ name: "node",
3279
+ detail: `${process.version} at ${process.execPath}`
3280
+ });
3281
+ const kiro = which(config.kiroBin);
3282
+ if (kiro) {
3283
+ let version = "";
3284
+ try {
3285
+ version = execFileSync3(config.kiroBin, ["--version"], { encoding: "utf8" }).trim();
3286
+ } catch {
3287
+ version = "(version unavailable)";
3288
+ }
3289
+ out.push({ ok: true, name: "kiro-cli", detail: `${version} at ${kiro}` });
3290
+ } else {
3291
+ out.push({
3292
+ ok: false,
3293
+ fatal: true,
3294
+ name: "kiro-cli",
3295
+ detail: `not found on PATH (looked for "${config.kiroBin}"). Casper is a client for it, so nothing works without it.`
3296
+ });
3297
+ }
3298
+ const settings = readSettings(config.configFile);
3299
+ const hasToken = typeof settings.token === "string" && settings.token !== "";
3300
+ out.push(
3301
+ fs13.existsSync(config.configFile) ? {
3302
+ ok: hasToken,
3303
+ name: "settings",
3304
+ detail: hasToken ? `${config.configFile} (token set)` : `${config.configFile} exists but has no token - auth is DISABLED`
3305
+ } : {
3306
+ ok: false,
3307
+ name: "settings",
3308
+ detail: `${config.configFile} not found; a token is generated on first run`
3309
+ }
3310
+ );
3311
+ if (fs13.existsSync(config.configFile)) {
3312
+ const mode = fs13.statSync(config.configFile).mode & 511;
3313
+ out.push({
3314
+ ok: mode === 384,
3315
+ name: "settings mode",
3316
+ detail: mode === 384 ? "0600" : `${mode.toString(8).padStart(3, "0")} - it holds the token; chmod 600 it`
3317
+ });
3318
+ }
3319
+ try {
3320
+ fs13.mkdirSync(config.casperDataDir, { recursive: true });
3321
+ fs13.accessSync(config.casperDataDir, fs13.constants.W_OK);
3322
+ const db2 = path14.join(config.casperDataDir, "casper.db");
3323
+ const size = fs13.existsSync(db2) ? `, casper.db ${(fs13.statSync(db2).size / 1024).toFixed(0)} KB` : "";
3324
+ out.push({ ok: true, name: "data dir", detail: `${config.casperDataDir} writable${size}` });
3325
+ } catch (err) {
3326
+ out.push({
3327
+ ok: false,
3328
+ fatal: true,
3329
+ name: "data dir",
3330
+ detail: `${config.casperDataDir} is not writable: ${err.message}`
3331
+ });
3332
+ }
3333
+ out.push(
3334
+ fs13.existsSync(config.webDist) ? { ok: true, name: "web app", detail: config.webDist } : {
3335
+ ok: false,
3336
+ fatal: true,
3337
+ name: "web app",
3338
+ detail: `${config.webDist} not found - the package is incomplete or CASPER_WEB_DIST is wrong`
3339
+ }
3340
+ );
3341
+ const agent = path14.join(config.kiroSessionsDir, "..", "..", "agents", "casper.json");
3342
+ out.push({
3343
+ ok: fs13.existsSync(agent),
3344
+ name: "casper agent",
3345
+ detail: fs13.existsSync(agent) ? agent : `${agent} missing - it is installed on first run`
3346
+ });
3347
+ return out;
3348
+ }
3349
+ function doctor() {
3350
+ const results = checks();
3351
+ for (const c of results) {
3352
+ const mark = c.ok ? "\x1B[32m\u2713\x1B[0m" : c.fatal ? "\x1B[31m\u2717\x1B[0m" : "\x1B[33m!\x1B[0m";
3353
+ process.stdout.write(`${mark} ${c.name.padEnd(14)} ${c.detail}
3354
+ `);
3355
+ }
3356
+ const fatal = results.filter((c) => !c.ok && c.fatal);
3357
+ if (fatal.length > 0) {
3358
+ process.stdout.write(`
3359
+ ${fatal.length} problem(s) will stop Casper from working.
3360
+ `);
3361
+ return 1;
3362
+ }
3363
+ return 0;
3364
+ }
3365
+ var init_doctor = __esm({
3366
+ "server/src/cli/doctor.ts"() {
3367
+ "use strict";
3368
+ init_config();
3369
+ init_settings();
3370
+ }
3371
+ });
3372
+
3373
+ // server/src/cli/index.ts
3374
+ init_paths();
3375
+ init_settings();
3376
+ import fs14 from "node:fs";
3377
+ import os4 from "node:os";
3378
+ import path15 from "node:path";
3379
+ var VERSION = true ? "0.1.0" : "dev";
3380
+ var USAGE = `casper - web client for kiro-cli
3381
+
3382
+ Usage:
3383
+ casper run the server in the foreground
3384
+ casper token print the access token
3385
+ casper reset-token [value] set a new token and sign every device out
3386
+ casper doctor check kiro-cli, settings, data dir and web app
3387
+ casper service install run Casper as a systemd user service
3388
+ casper service uninstall remove the service
3389
+ casper service status show the service status
3390
+
3391
+ Settings live in ${configFilePath()}.
3392
+ Update with: npm install -g @joeyshi12/casper
3393
+ `;
3394
+ async function bootstrap() {
3395
+ const file = configFilePath();
3396
+ const settings = readSettings(file);
3397
+ if (typeof settings.token !== "string" || settings.token === "") {
3398
+ const token = generateToken();
3399
+ updateSettings(file, { token });
3400
+ printTokenBlock(token, "Your Casper access token (paste it at the login screen):");
3401
+ process.stdout.write(`Saved to ${file}. Print it again with: casper token
3402
+
3403
+ `);
3404
+ }
3405
+ const { installAgentFile: installAgentFile2 } = await Promise.resolve().then(() => (init_agentFile(), agentFile_exports));
3406
+ const agent = installAgentFile2(os4.homedir(), dataDirPath());
3407
+ if (agent.action === "installed" || agent.action === "updated") {
3408
+ process.stdout.write(`casper agent ${agent.action}: ${agent.target}
3409
+ `);
3410
+ } else if (agent.action === "kept-yours") {
3411
+ process.stdout.write(`keeping your edited agent file at ${agent.target}
3412
+ `);
3413
+ }
3414
+ }
3415
+ function printToken() {
3416
+ const file = configFilePath();
3417
+ const token = readSettings(file).token;
3418
+ if (typeof token !== "string" || token === "") {
3419
+ process.stderr.write(
3420
+ `casper: no token in ${file} - authentication is disabled.
3421
+ One is generated the first time you run \`casper\`.
3422
+ `
3423
+ );
3424
+ return 1;
3425
+ }
3426
+ process.stdout.write(`${token}
3427
+ `);
3428
+ return 0;
3429
+ }
3430
+ async function resetToken(value) {
3431
+ const token = value && value !== "" ? value : generateToken();
3432
+ const file = configFilePath();
3433
+ updateSettings(file, { token });
3434
+ process.stdout.write(`wrote a new token to ${file}
3435
+ `);
3436
+ const dbFile = path15.join(dataDirPath(), "casper.db");
3437
+ if (fs14.existsSync(dbFile)) {
3438
+ const { LoginStore: LoginStore2 } = await Promise.resolve().then(() => (init_logins(), logins_exports));
3439
+ new LoginStore2().revokeAll();
3440
+ process.stdout.write("revoked all device sessions\n");
3441
+ }
3442
+ if ((await Promise.resolve().then(() => (init_service(), service_exports))).serviceActive()) {
3443
+ const { spawnSync: spawnSync2 } = await import("node:child_process");
3444
+ spawnSync2("systemctl", ["--user", "restart", "casper.service"], { stdio: "ignore" });
3445
+ process.stdout.write("restarted the service\n");
3446
+ } else {
3447
+ process.stdout.write("restart Casper for the new token to take effect\n");
3448
+ }
3449
+ printTokenBlock(token, "New access token:");
3450
+ return 0;
3451
+ }
3452
+ async function main(argv) {
3453
+ const [cmd, ...rest] = argv;
3454
+ switch (cmd) {
3455
+ case void 0:
3456
+ case "start": {
3457
+ await bootstrap();
3458
+ const { serve: serve2 } = await Promise.resolve().then(() => (init_server(), server_exports));
3459
+ await serve2();
3460
+ return 0;
3461
+ }
3462
+ case "token":
3463
+ return printToken();
3464
+ case "reset-token":
3465
+ return resetToken(rest[0]);
3466
+ case "doctor":
3467
+ return (await Promise.resolve().then(() => (init_doctor(), doctor_exports))).doctor();
3468
+ case "service": {
3469
+ const svc = await Promise.resolve().then(() => (init_service(), service_exports));
3470
+ switch (rest[0]) {
3471
+ case "install":
3472
+ return svc.serviceInstall();
3473
+ case "uninstall":
3474
+ return svc.serviceUninstall();
3475
+ case "status":
3476
+ case void 0:
3477
+ return svc.serviceStatus();
3478
+ default:
3479
+ process.stderr.write(`casper: unknown service command "${rest[0]}"
3480
+ `);
3481
+ return 2;
3482
+ }
3483
+ }
3484
+ case "help":
3485
+ case "--help":
3486
+ case "-h":
3487
+ process.stdout.write(USAGE);
3488
+ return 0;
3489
+ case "version":
3490
+ case "--version":
3491
+ case "-v":
3492
+ process.stdout.write(`${VERSION}
3493
+ `);
3494
+ return 0;
3495
+ default:
3496
+ process.stderr.write(`casper: unknown command "${cmd}"
3497
+
3498
+ ${USAGE}`);
3499
+ return 2;
3500
+ }
3501
+ }
3502
+ var code = await main(process.argv.slice(2));
3503
+ if (code !== 0) process.exit(code);
3504
+ export {
3505
+ main
3506
+ };