@kkkiio/pi-web-ui 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 (681) hide show
  1. package/README.md +136 -0
  2. package/dist/assets/abap-BF8NL95m.js +1 -0
  3. package/dist/assets/abap-CLvhMVsD.js +1 -0
  4. package/dist/assets/actionscript-3--17pq3dv.js +1 -0
  5. package/dist/assets/actionscript-3-8HxPlehu.js +1 -0
  6. package/dist/assets/ada-Bw5CILjN.js +1 -0
  7. package/dist/assets/ada-C5qYipkI.js +1 -0
  8. package/dist/assets/andromeeda-WkhI09AS.js +1 -0
  9. package/dist/assets/andromeeda-vGVdxbeo.js +1 -0
  10. package/dist/assets/angular-html-DcVpqLl8.js +1 -0
  11. package/dist/assets/angular-html-agbA1wAx.js +1 -0
  12. package/dist/assets/angular-ts-Bq4wy26G.js +1 -0
  13. package/dist/assets/angular-ts-DvN0FkVU.js +1 -0
  14. package/dist/assets/apache-U0d_L8uA.js +1 -0
  15. package/dist/assets/apache-_o4xIZnD.js +1 -0
  16. package/dist/assets/apex-CGTLDQj6.js +1 -0
  17. package/dist/assets/apex-VAyPSnFM.js +1 -0
  18. package/dist/assets/apl-AFegjZcw.js +1 -0
  19. package/dist/assets/apl-BnB2j0rO.js +1 -0
  20. package/dist/assets/applescript-BeHGDHfZ.js +1 -0
  21. package/dist/assets/applescript-CCn79oCD.js +1 -0
  22. package/dist/assets/ara-4CJ0cIlV.js +1 -0
  23. package/dist/assets/ara-DO_5Wseq.js +1 -0
  24. package/dist/assets/arc-Czrm3tSr.js +1 -0
  25. package/dist/assets/architecture-7EHR7CIX-C8o3_3JT.js +1 -0
  26. package/dist/assets/architectureDiagram-3BPJPVTR-Z43oYPVJ.js +36 -0
  27. package/dist/assets/asciidoc-CibGaoXl.js +1 -0
  28. package/dist/assets/asciidoc-DE70LPWp.js +1 -0
  29. package/dist/assets/asm-Cmm7eHzH.js +1 -0
  30. package/dist/assets/asm-MXdQGneo.js +1 -0
  31. package/dist/assets/astro-B6HizED0.js +1 -0
  32. package/dist/assets/astro-CUn7k81t.js +1 -0
  33. package/dist/assets/aurora-x-C7CFqhAd.js +1 -0
  34. package/dist/assets/aurora-x-CDeNXAV0.js +1 -0
  35. package/dist/assets/awk-BWXHIvNe.js +1 -0
  36. package/dist/assets/awk-DnQEBQqr.js +1 -0
  37. package/dist/assets/ayu-dark-CRu61w6_.js +1 -0
  38. package/dist/assets/ayu-dark-DluEY0Gj.js +1 -0
  39. package/dist/assets/ayu-light-C3h-C4tm.js +1 -0
  40. package/dist/assets/ayu-light-Dn6O_aYR.js +1 -0
  41. package/dist/assets/ayu-mirage-Bqwy1Gya.js +1 -0
  42. package/dist/assets/ayu-mirage-CTjYAbs-.js +1 -0
  43. package/dist/assets/ballerina-B7ZEbQpA.js +1 -0
  44. package/dist/assets/ballerina-CFI0x2Aa.js +1 -0
  45. package/dist/assets/bat-Bo4NYOV-.js +1 -0
  46. package/dist/assets/bat-DC-yen0F.js +1 -0
  47. package/dist/assets/beancount-D-usSTwE.js +1 -0
  48. package/dist/assets/beancount-D_qoziCN.js +1 -0
  49. package/dist/assets/berry-BKmXRlIk.js +1 -0
  50. package/dist/assets/berry-DKpUyyne.js +1 -0
  51. package/dist/assets/bibtex-Ci_nEsc7.js +1 -0
  52. package/dist/assets/bibtex-DPT_ISlA.js +1 -0
  53. package/dist/assets/bicep-BSNFxtkx.js +1 -0
  54. package/dist/assets/bicep-CUHmPFLl.js +1 -0
  55. package/dist/assets/bird2-C2hNVINV.js +1 -0
  56. package/dist/assets/bird2-C6vDhewU.js +1 -0
  57. package/dist/assets/blade-ecD9BRYs.js +1 -0
  58. package/dist/assets/blade-mL5wbXoB.js +1 -0
  59. package/dist/assets/blockDiagram-GPEHLZMM-Deqgox_M.js +132 -0
  60. package/dist/assets/bsl-BkkzgIyY.js +1 -0
  61. package/dist/assets/bsl-DJOyloKD.js +1 -0
  62. package/dist/assets/c-CDpZqrm1.js +1 -0
  63. package/dist/assets/c-Cuwk2N9P.js +1 -0
  64. package/dist/assets/c3-BFHwR3_K.js +1 -0
  65. package/dist/assets/c3-CnJL0r0V.js +1 -0
  66. package/dist/assets/c4Diagram-AAUBKEIU-BPW-5VO7.js +10 -0
  67. package/dist/assets/cadence-CQ2zXKGN.js +1 -0
  68. package/dist/assets/cadence-Cfz1Ugii.js +1 -0
  69. package/dist/assets/cairo-CDc8os0h.js +1 -0
  70. package/dist/assets/cairo-DLTphjLi.js +1 -0
  71. package/dist/assets/catppuccin-frappe-3VR1Za6u.js +1 -0
  72. package/dist/assets/catppuccin-frappe-DFlFW5gh.js +1 -0
  73. package/dist/assets/catppuccin-latte-CpxdcXpJ.js +1 -0
  74. package/dist/assets/catppuccin-latte-DwIHMF0Q.js +1 -0
  75. package/dist/assets/catppuccin-macchiato-DIdIQ04K.js +1 -0
  76. package/dist/assets/catppuccin-macchiato-DYnBP6_5.js +1 -0
  77. package/dist/assets/catppuccin-mocha-Co3Xjl64.js +1 -0
  78. package/dist/assets/catppuccin-mocha-DYhrFGRu.js +1 -0
  79. package/dist/assets/channel-57IZAEuE.js +1 -0
  80. package/dist/assets/chunk-2J33WTMH-B4fhRNoJ.js +1 -0
  81. package/dist/assets/chunk-4BX2VUAB-Cx7adTfB.js +1 -0
  82. package/dist/assets/chunk-55IACEB6-CrM9gaVz.js +1 -0
  83. package/dist/assets/chunk-727SXJPM-zQV3bTq8.js +206 -0
  84. package/dist/assets/chunk-AQP2D5EJ-BAoLF3MP.js +231 -0
  85. package/dist/assets/chunk-FMBD7UC4-BQ0_2Rvj.js +15 -0
  86. package/dist/assets/chunk-ND2GUHAM-CJPtiRfG.js +1 -0
  87. package/dist/assets/chunk-QZHKN3VN-D1qpdCzO.js +1 -0
  88. package/dist/assets/clarity-CCl-fyB_.js +1 -0
  89. package/dist/assets/clarity-SemFz856.js +1 -0
  90. package/dist/assets/classDiagram-4FO5ZUOK-FXYqfOGy.js +1 -0
  91. package/dist/assets/classDiagram-v2-Q7XG4LA2-FXYqfOGy.js +1 -0
  92. package/dist/assets/clojure-D5q7Jl6u.js +1 -0
  93. package/dist/assets/clojure-DqKBuwfJ.js +1 -0
  94. package/dist/assets/cmake-Bj61d0ZC.js +1 -0
  95. package/dist/assets/cmake-CbIxKMg_.js +1 -0
  96. package/dist/assets/cobol-CQTgvzAu.js +1 -0
  97. package/dist/assets/cobol-Cv47UOc6.js +1 -0
  98. package/dist/assets/codeowners-BhqpE35O.js +1 -0
  99. package/dist/assets/codeowners-C8r90Shi.js +1 -0
  100. package/dist/assets/codeql-Bq651Y_e.js +1 -0
  101. package/dist/assets/codeql-oeQT6MSM.js +1 -0
  102. package/dist/assets/coffee-5sO4EViZ.js +1 -0
  103. package/dist/assets/coffee-OpnvsdMK.js +1 -0
  104. package/dist/assets/common-lisp-8vnVPjBz.js +1 -0
  105. package/dist/assets/common-lisp-Cv5bFMCO.js +1 -0
  106. package/dist/assets/coq-Bol-RJ7y.js +1 -0
  107. package/dist/assets/coq-BrsZFFmf.js +1 -0
  108. package/dist/assets/cose-bilkent-S5V4N54A-CeZVwLqu.js +1 -0
  109. package/dist/assets/cpp-CIYeuzjM.js +1 -0
  110. package/dist/assets/cpp-COicCpyD.js +1 -0
  111. package/dist/assets/crystal-BKABJHi3.js +1 -0
  112. package/dist/assets/crystal-DxtUkQe8.js +1 -0
  113. package/dist/assets/csharp-Ct8U2NOr.js +1 -0
  114. package/dist/assets/csharp-oqKa8noW.js +1 -0
  115. package/dist/assets/css-DmOTkotT.js +1 -0
  116. package/dist/assets/css-y06Cd4EX.js +1 -0
  117. package/dist/assets/csv-Dx-8-gkx.js +1 -0
  118. package/dist/assets/csv-aQe9eWwB.js +1 -0
  119. package/dist/assets/cue-CE9AQfxI.js +1 -0
  120. package/dist/assets/cue-Dw4Jatp1.js +1 -0
  121. package/dist/assets/cypher-Ce1ftazo.js +1 -0
  122. package/dist/assets/cypher-ClKdZ_lG.js +1 -0
  123. package/dist/assets/cytoscape.esm-FqbQrHcz.js +321 -0
  124. package/dist/assets/d-C5Cd7-kE.js +1 -0
  125. package/dist/assets/d-qD-0Kul2.js +1 -0
  126. package/dist/assets/dagre-BM42HDAG-JS67pd7a.js +4 -0
  127. package/dist/assets/dagre-Bx709z4p.js +1 -0
  128. package/dist/assets/dark-plus-Cs2F2srj.js +1 -0
  129. package/dist/assets/dark-plus-GAt2ZgX8.js +1 -0
  130. package/dist/assets/dart-CnvKMtbv.js +1 -0
  131. package/dist/assets/dart-DkHntEIa.js +1 -0
  132. package/dist/assets/dax-8Zbmoo6g.js +1 -0
  133. package/dist/assets/dax-BkyTk9wS.js +1 -0
  134. package/dist/assets/defaultLocale-C8Fc0cco.js +1 -0
  135. package/dist/assets/desktop-DfUumfaM.js +1 -0
  136. package/dist/assets/desktop-Dlh5hvp9.js +1 -0
  137. package/dist/assets/diagram-2AECGRRQ-Ck_AisNU.js +43 -0
  138. package/dist/assets/diagram-5GNKFQAL-CCU4VDPK.js +10 -0
  139. package/dist/assets/diagram-KO2AKTUF-DNWmJT5p.js +3 -0
  140. package/dist/assets/diagram-LMA3HP47-B2dy-7bE.js +24 -0
  141. package/dist/assets/diagram-OG6HWLK6-Bn7AmqLY.js +24 -0
  142. package/dist/assets/diff-COJGj3FI.js +1 -0
  143. package/dist/assets/diff-woXpYk--.js +1 -0
  144. package/dist/assets/docker-BRkDW2k5.js +1 -0
  145. package/dist/assets/docker-IyjqRm3v.js +1 -0
  146. package/dist/assets/dotenv-BCLQtltE.js +1 -0
  147. package/dist/assets/dotenv-_5a1GRtc.js +1 -0
  148. package/dist/assets/dracula-BHWKrbxM.js +1 -0
  149. package/dist/assets/dracula-C2uKkzgK.js +1 -0
  150. package/dist/assets/dracula-soft-5eyTD99u.js +1 -0
  151. package/dist/assets/dracula-soft-D1bVjxWZ.js +1 -0
  152. package/dist/assets/dream-maker-Bqk5m4Bz.js +1 -0
  153. package/dist/assets/dream-maker-DW3nJb8Q.js +1 -0
  154. package/dist/assets/edge-DGIjE7Mh.js +1 -0
  155. package/dist/assets/edge-DUdPlE9Y.js +1 -0
  156. package/dist/assets/elixir-CKKGjgry.js +1 -0
  157. package/dist/assets/elixir-DE3MaDR9.js +1 -0
  158. package/dist/assets/elm-B13-iZiI.js +1 -0
  159. package/dist/assets/elm-DYySh1Lo.js +1 -0
  160. package/dist/assets/emacs-lisp-B4R74twV.js +1 -0
  161. package/dist/assets/emacs-lisp-C9PiwqqW.js +1 -0
  162. package/dist/assets/erDiagram-TEJ5UH35-B04dRI_t.js +85 -0
  163. package/dist/assets/erb-6eZlR1MA.js +1 -0
  164. package/dist/assets/erb-CRUIEuSk.js +1 -0
  165. package/dist/assets/erlang-Cphh6RMH.js +1 -0
  166. package/dist/assets/erlang-Dej7b2OY.js +1 -0
  167. package/dist/assets/eventmodeling-FCH6USID-CSa_0bEO.js +1 -0
  168. package/dist/assets/everforest-dark-D-DlICQR.js +1 -0
  169. package/dist/assets/everforest-dark-sB-x3p7T.js +1 -0
  170. package/dist/assets/everforest-light-CeVdGHYG.js +1 -0
  171. package/dist/assets/everforest-light-Df2xbC6M.js +1 -0
  172. package/dist/assets/fennel-Bb9CuKP4.js +1 -0
  173. package/dist/assets/fennel-DQxkIbk2.js +1 -0
  174. package/dist/assets/fish-3TGl8j7I.js +1 -0
  175. package/dist/assets/fish-BJitypiv.js +1 -0
  176. package/dist/assets/flowDiagram-I6XJVG4X-DqxlEPIY.js +162 -0
  177. package/dist/assets/fluent-C03EYrpw.js +1 -0
  178. package/dist/assets/fluent-D94vzzSS.js +1 -0
  179. package/dist/assets/fortran-fixed-form-DEKoE2YW.js +1 -0
  180. package/dist/assets/fortran-fixed-form-RVbzjQ2q.js +1 -0
  181. package/dist/assets/fortran-free-form-CYNrtFtB.js +1 -0
  182. package/dist/assets/fortran-free-form-EhmKDVgM.js +1 -0
  183. package/dist/assets/fsharp-D13ZGOAj.js +1 -0
  184. package/dist/assets/fsharp-DKW21_Cq.js +1 -0
  185. package/dist/assets/ganttDiagram-6RSMTGT7-Daudkz2g.js +292 -0
  186. package/dist/assets/gdresource-C0sCabJj.js +1 -0
  187. package/dist/assets/gdresource-b7MixKdf.js +1 -0
  188. package/dist/assets/gdscript-Cp2uCuqX.js +1 -0
  189. package/dist/assets/gdscript-DbNIuFFJ.js +1 -0
  190. package/dist/assets/gdshader-C7BToOo8.js +1 -0
  191. package/dist/assets/gdshader-CBce3t8t.js +1 -0
  192. package/dist/assets/genie-CV2tkWYe.js +1 -0
  193. package/dist/assets/genie-Cxv98QxF.js +1 -0
  194. package/dist/assets/gherkin-CcdVVABv.js +1 -0
  195. package/dist/assets/gherkin-DExj1W_8.js +1 -0
  196. package/dist/assets/git-commit-BSykSTBG.js +1 -0
  197. package/dist/assets/git-commit-BU_RZ1WH.js +1 -0
  198. package/dist/assets/git-rebase-BJLu-DL5.js +1 -0
  199. package/dist/assets/git-rebase-BK279XeE.js +1 -0
  200. package/dist/assets/gitGraph-WXDBUCRP-BZRXV3nR.js +1 -0
  201. package/dist/assets/gitGraphDiagram-PVQCEYII-DnRkqvgL.js +106 -0
  202. package/dist/assets/github-dark-C-LZuMrd.js +1 -0
  203. package/dist/assets/github-dark-DwbbXv51.js +1 -0
  204. package/dist/assets/github-dark-default-CxjRwzl6.js +1 -0
  205. package/dist/assets/github-dark-default-DXG-b-1a.js +1 -0
  206. package/dist/assets/github-dark-dimmed-Bx1FflLF.js +1 -0
  207. package/dist/assets/github-dark-dimmed-Cr1oaoul.js +1 -0
  208. package/dist/assets/github-dark-high-contrast-B4qJZLZa.js +1 -0
  209. package/dist/assets/github-dark-high-contrast-B_tTalzw.js +1 -0
  210. package/dist/assets/github-light-CsVXhIlR.js +1 -0
  211. package/dist/assets/github-light-EUqPIrTm.js +1 -0
  212. package/dist/assets/github-light-default-BXViO-2h.js +1 -0
  213. package/dist/assets/github-light-default-DB_IwZa6.js +1 -0
  214. package/dist/assets/github-light-high-contrast-B68TUdTA.js +1 -0
  215. package/dist/assets/github-light-high-contrast-DLgVeSB-.js +1 -0
  216. package/dist/assets/gleam-B359wBlW.js +1 -0
  217. package/dist/assets/gleam-CSRkHgEL.js +1 -0
  218. package/dist/assets/glimmer-js-CFDrsclS.js +1 -0
  219. package/dist/assets/glimmer-js-TwCyT3hl.js +1 -0
  220. package/dist/assets/glimmer-ts-C1aJLW8U.js +1 -0
  221. package/dist/assets/glimmer-ts-bM0LWYGM.js +1 -0
  222. package/dist/assets/glsl-BwY33GHq.js +1 -0
  223. package/dist/assets/glsl-CoozvLnG.js +1 -0
  224. package/dist/assets/gn-BdcPb0jv.js +1 -0
  225. package/dist/assets/gn-ilITqXS6.js +1 -0
  226. package/dist/assets/gnuplot-7GGW24-e.js +1 -0
  227. package/dist/assets/gnuplot-DKk0glG7.js +1 -0
  228. package/dist/assets/go-BJwz_mda.js +1 -0
  229. package/dist/assets/go-rLFTqkRN.js +1 -0
  230. package/dist/assets/graphlib-B8gBHxth.js +1 -0
  231. package/dist/assets/graphql-BgVpbCnL.js +1 -0
  232. package/dist/assets/graphql-DAFj0eHZ.js +1 -0
  233. package/dist/assets/groovy-CacY0gHj.js +1 -0
  234. package/dist/assets/groovy-DyoOSCFW.js +1 -0
  235. package/dist/assets/gruvbox-dark-hard-C820rvS2.js +1 -0
  236. package/dist/assets/gruvbox-dark-hard-DXm60lX3.js +1 -0
  237. package/dist/assets/gruvbox-dark-medium-BPjhmG05.js +1 -0
  238. package/dist/assets/gruvbox-dark-medium-GE2thSmD.js +1 -0
  239. package/dist/assets/gruvbox-dark-soft-04Cj-4VZ.js +1 -0
  240. package/dist/assets/gruvbox-dark-soft-MrdJrrXF.js +1 -0
  241. package/dist/assets/gruvbox-light-hard-BC_s9l72.js +1 -0
  242. package/dist/assets/gruvbox-light-hard-K6wSn29D.js +1 -0
  243. package/dist/assets/gruvbox-light-medium-BAWPOn9u.js +1 -0
  244. package/dist/assets/gruvbox-light-medium-CVy3aSlc.js +1 -0
  245. package/dist/assets/gruvbox-light-soft-BSMLrYjP.js +1 -0
  246. package/dist/assets/gruvbox-light-soft-DNgJjaY0.js +1 -0
  247. package/dist/assets/hack-CoEN_EZp.js +1 -0
  248. package/dist/assets/hack-D20lxUwV.js +1 -0
  249. package/dist/assets/haml-D6bXtUcn.js +1 -0
  250. package/dist/assets/haml-rMxgQoLD.js +1 -0
  251. package/dist/assets/handlebars-Bu6ETRKh.js +1 -0
  252. package/dist/assets/handlebars-CMCwFoPW.js +1 -0
  253. package/dist/assets/haskell-D8IpX4py.js +1 -0
  254. package/dist/assets/haskell-Dsfw-pfZ.js +1 -0
  255. package/dist/assets/haxe-BZXihDV-.js +1 -0
  256. package/dist/assets/haxe-OTjmBuCE.js +1 -0
  257. package/dist/assets/hcl-Dh228itO.js +1 -0
  258. package/dist/assets/hcl-o1Ej_Blw.js +1 -0
  259. package/dist/assets/highlighted-body-OFNGDK62-CD4JRyfl.js +1 -0
  260. package/dist/assets/hjson-C0zQIMrr.js +1 -0
  261. package/dist/assets/hjson-CxZEssPk.js +1 -0
  262. package/dist/assets/hlsl-BxIm9IaD.js +1 -0
  263. package/dist/assets/hlsl-Cvrh5tZx.js +1 -0
  264. package/dist/assets/horizon-0-RkHfro.js +1 -0
  265. package/dist/assets/horizon-CE9ld1lL.js +1 -0
  266. package/dist/assets/horizon-bright-Br1oVSNq.js +1 -0
  267. package/dist/assets/horizon-bright-DSNQnXHK.js +1 -0
  268. package/dist/assets/houston-CsvMBhTu.js +1 -0
  269. package/dist/assets/houston-KyjWKfuY.js +1 -0
  270. package/dist/assets/html-BxT_wlCP.js +1 -0
  271. package/dist/assets/html-derivative-36-8F4zc.js +1 -0
  272. package/dist/assets/html-derivative-DdycQrdG.js +1 -0
  273. package/dist/assets/html-eg6bn0n_.js +1 -0
  274. package/dist/assets/http-CdKzEwNl.js +1 -0
  275. package/dist/assets/http-DjxKa19q.js +1 -0
  276. package/dist/assets/hurl-DbOdNNGx.js +1 -0
  277. package/dist/assets/hurl-DxpEECtM.js +1 -0
  278. package/dist/assets/hxml-B0Qn7Nwc.js +1 -0
  279. package/dist/assets/hxml-B_tSdhCX.js +1 -0
  280. package/dist/assets/hy-CPusJT2P.js +1 -0
  281. package/dist/assets/hy-CZbG8q4J.js +1 -0
  282. package/dist/assets/imba-BNhPHnyc.js +1 -0
  283. package/dist/assets/imba-DsUTQ-LC.js +1 -0
  284. package/dist/assets/index-Bb_TvYze.js +863 -0
  285. package/dist/assets/index-DL4-PDBO.css +2 -0
  286. package/dist/assets/info-J43DQDTF-B5mpewKR.js +1 -0
  287. package/dist/assets/infoDiagram-5YYISTIA-etKj4JuA.js +2 -0
  288. package/dist/assets/ini-B5eOa1yu.js +1 -0
  289. package/dist/assets/ini-BXoOzk1L.js +1 -0
  290. package/dist/assets/init-D6jRqBbL.js +1 -0
  291. package/dist/assets/ishikawaDiagram-YF4QCWOH-Bldw815g.js +70 -0
  292. package/dist/assets/java-DDl7ivwN.js +1 -0
  293. package/dist/assets/java-DI2dVZJn.js +1 -0
  294. package/dist/assets/javascript-DGd-xfcX.js +1 -0
  295. package/dist/assets/javascript-DRYVj2Fg.js +1 -0
  296. package/dist/assets/jinja-BOFcp5J1.js +1 -0
  297. package/dist/assets/jinja-BvwcDNka.js +1 -0
  298. package/dist/assets/jison-B1LOvVLD.js +1 -0
  299. package/dist/assets/jison-D2RT41vx.js +1 -0
  300. package/dist/assets/journeyDiagram-JHISSGLW-DOrm29SL.js +139 -0
  301. package/dist/assets/json-CwcobCqQ.js +1 -0
  302. package/dist/assets/json-DMAjn_S3.js +1 -0
  303. package/dist/assets/json5-BR5RXkoi.js +1 -0
  304. package/dist/assets/json5-BXepwZod.js +1 -0
  305. package/dist/assets/jsonc-CYpm1nAK.js +1 -0
  306. package/dist/assets/jsonc-jqjqAbF7.js +1 -0
  307. package/dist/assets/jsonl-CmCQp5Yx.js +1 -0
  308. package/dist/assets/jsonl-sJJpusUR.js +1 -0
  309. package/dist/assets/jsonnet-CJTPZ8u_.js +1 -0
  310. package/dist/assets/jsonnet-DKGWD1zU.js +1 -0
  311. package/dist/assets/jssm-D6JwwFbv.js +1 -0
  312. package/dist/assets/jssm-DXw9l8Rf.js +1 -0
  313. package/dist/assets/jsx-CYUj6TVo.js +1 -0
  314. package/dist/assets/jsx-cqwDl9B-.js +1 -0
  315. package/dist/assets/julia-B33WgAnz.js +1 -0
  316. package/dist/assets/julia-IFXxVp6t.js +1 -0
  317. package/dist/assets/just-BCuQtiyL.js +1 -0
  318. package/dist/assets/just-t6qxHVj_.js +1 -0
  319. package/dist/assets/kanagawa-dragon-7TqgsUPC.js +1 -0
  320. package/dist/assets/kanagawa-dragon-CXtmUGW6.js +1 -0
  321. package/dist/assets/kanagawa-lotus-BN08jTvb.js +1 -0
  322. package/dist/assets/kanagawa-lotus-CqxMQulh.js +1 -0
  323. package/dist/assets/kanagawa-wave-CTweb8Dz.js +1 -0
  324. package/dist/assets/kanagawa-wave-D8CNn6T_.js +1 -0
  325. package/dist/assets/kanban-definition-UN3LZRKU-D9EHr5eX.js +89 -0
  326. package/dist/assets/kdl-CsD5j6eV.js +1 -0
  327. package/dist/assets/kdl-HHaMlHFB.js +1 -0
  328. package/dist/assets/kotlin-Bvjw1jyK.js +1 -0
  329. package/dist/assets/kotlin-DhhofPvG.js +1 -0
  330. package/dist/assets/kusto-BUv0MjJC.js +1 -0
  331. package/dist/assets/kusto-C7mF5XQf.js +1 -0
  332. package/dist/assets/laserwave-CQkdhozh.js +1 -0
  333. package/dist/assets/laserwave-C_8bwKvT.js +1 -0
  334. package/dist/assets/latex-Dn7POiyn.js +1 -0
  335. package/dist/assets/latex-HtSKSxs6.js +1 -0
  336. package/dist/assets/lean-B2IJXP9Y.js +1 -0
  337. package/dist/assets/lean-CewbzKMR.js +1 -0
  338. package/dist/assets/less-BGJv5CZz.js +1 -0
  339. package/dist/assets/less-DVTAwKKz.js +1 -0
  340. package/dist/assets/light-plus-CNQ8_7Y0.js +1 -0
  341. package/dist/assets/light-plus-DVQuIRkW.js +1 -0
  342. package/dist/assets/linear-CH_4ryJu.js +1 -0
  343. package/dist/assets/liquid-BmJZZY64.js +1 -0
  344. package/dist/assets/liquid-C5Dc8Z9p.js +1 -0
  345. package/dist/assets/llvm-Cm23YOpf.js +1 -0
  346. package/dist/assets/llvm-DjdPfBRh.js +1 -0
  347. package/dist/assets/log-BNLmms1o.js +1 -0
  348. package/dist/assets/log-CS10erYd.js +1 -0
  349. package/dist/assets/logo-Cluzi2Zq.js +1 -0
  350. package/dist/assets/logo-CmXTaeCJ.js +1 -0
  351. package/dist/assets/lua-C6euQbiU.js +1 -0
  352. package/dist/assets/lua-DKMesj8b.js +1 -0
  353. package/dist/assets/luau-CNKltnaQ.js +1 -0
  354. package/dist/assets/luau-FMPmPwt6.js +1 -0
  355. package/dist/assets/make--KyFtISL.js +1 -0
  356. package/dist/assets/make-Dixweg8N.js +1 -0
  357. package/dist/assets/markdown-BYOwaDjH.js +1 -0
  358. package/dist/assets/markdown-Bk5jPFPK.js +1 -0
  359. package/dist/assets/marko-B1vUgmPS.js +1 -0
  360. package/dist/assets/marko-CYIvqJ7L.js +1 -0
  361. package/dist/assets/material-theme-Bm3Qr25_.js +1 -0
  362. package/dist/assets/material-theme-CAtLaHa3.js +1 -0
  363. package/dist/assets/material-theme-darker-2IIEA8gg.js +1 -0
  364. package/dist/assets/material-theme-darker-k-s5Bc6K.js +1 -0
  365. package/dist/assets/material-theme-lighter-CD6a-c4d.js +1 -0
  366. package/dist/assets/material-theme-lighter-uhdI0v04.js +1 -0
  367. package/dist/assets/material-theme-ocean-B3mraInd.js +1 -0
  368. package/dist/assets/material-theme-ocean-CHQ94UKr.js +1 -0
  369. package/dist/assets/material-theme-palenight-B5W6OYN7.js +1 -0
  370. package/dist/assets/material-theme-palenight-DEhAARHw.js +1 -0
  371. package/dist/assets/matlab-CXouX2WE.js +1 -0
  372. package/dist/assets/matlab-D7qyCx1q.js +1 -0
  373. package/dist/assets/mdc-BMOM55Wy.js +1 -0
  374. package/dist/assets/mdc-Bvv7AYF7.js +1 -0
  375. package/dist/assets/mdx-CT6rFF7S.js +1 -0
  376. package/dist/assets/mdx-DQZ5AkYe.js +1 -0
  377. package/dist/assets/mermaid-Bk4SNUv9.js +1 -0
  378. package/dist/assets/mermaid-CTmv7XD1.js +1 -0
  379. package/dist/assets/mermaid-GHXKKRXX-DAricd6j.js +1 -0
  380. package/dist/assets/mermaid-parser.core-DaUgASbP.js +161 -0
  381. package/dist/assets/min-dark-BFqTAwCr.js +1 -0
  382. package/dist/assets/min-dark-BSWPekZh.js +1 -0
  383. package/dist/assets/min-light-DDpmG2fV.js +1 -0
  384. package/dist/assets/min-light-O7UdaaxX.js +1 -0
  385. package/dist/assets/mindmap-definition-RKZ34NQL-GGlCOf2x.js +96 -0
  386. package/dist/assets/mipsasm-BMqwQI7S.js +1 -0
  387. package/dist/assets/mipsasm-Dl8GaYxY.js +1 -0
  388. package/dist/assets/mojo-BgCJLMeH.js +1 -0
  389. package/dist/assets/mojo-hMZsLDIK.js +1 -0
  390. package/dist/assets/monokai-C0p7Qecz.js +1 -0
  391. package/dist/assets/monokai-CdkpiU2Y.js +1 -0
  392. package/dist/assets/moonbit-BgUC1NvV.js +1 -0
  393. package/dist/assets/moonbit-CaWjb8XO.js +1 -0
  394. package/dist/assets/move-B1IS1UjX.js +1 -0
  395. package/dist/assets/move-u3t7wi4x.js +1 -0
  396. package/dist/assets/narrat-4tTAKi-J.js +1 -0
  397. package/dist/assets/narrat-_X_XdTYD.js +1 -0
  398. package/dist/assets/nextflow-BJtWHP5T.js +1 -0
  399. package/dist/assets/nextflow-Bbiyy34d.js +1 -0
  400. package/dist/assets/nextflow-groovy-DJMQeKeT.js +1 -0
  401. package/dist/assets/nextflow-groovy-Dc_ddanL.js +1 -0
  402. package/dist/assets/nginx-BJFY0Es3.js +1 -0
  403. package/dist/assets/nginx-IJ-NTbfq.js +1 -0
  404. package/dist/assets/night-owl-BsnCLAKc.js +1 -0
  405. package/dist/assets/night-owl-DhmEMT88.js +1 -0
  406. package/dist/assets/night-owl-light-BODRhcTU.js +1 -0
  407. package/dist/assets/night-owl-light-eJ-hLW7d.js +1 -0
  408. package/dist/assets/nim-COT5gKWD.js +1 -0
  409. package/dist/assets/nim-DZ8UavzN.js +1 -0
  410. package/dist/assets/nix-DGXJ-lch.js +1 -0
  411. package/dist/assets/nix-IvuFDN5E.js +1 -0
  412. package/dist/assets/nord-Cb4Vim4T.js +1 -0
  413. package/dist/assets/nord-Cd-F4txO.js +1 -0
  414. package/dist/assets/nushell-9zntkpRZ.js +1 -0
  415. package/dist/assets/nushell-DcLAeLz5.js +1 -0
  416. package/dist/assets/objective-c-BfQime6S.js +1 -0
  417. package/dist/assets/objective-c-D1A_Heim.js +1 -0
  418. package/dist/assets/objective-cpp-BbSyPuOs.js +1 -0
  419. package/dist/assets/objective-cpp-BsSzOQcm.js +1 -0
  420. package/dist/assets/ocaml--fvLg7Jk.js +1 -0
  421. package/dist/assets/ocaml-O90oeIOV.js +1 -0
  422. package/dist/assets/odin-B1RWQWA5.js +1 -0
  423. package/dist/assets/odin-B25GiMk5.js +1 -0
  424. package/dist/assets/one-dark-pro-CLwyXe_n.js +1 -0
  425. package/dist/assets/one-dark-pro-DUS_IGRK.js +1 -0
  426. package/dist/assets/one-light-CVUd7yD5.js +1 -0
  427. package/dist/assets/one-light-D7Lr4KcI.js +1 -0
  428. package/dist/assets/openscad-BUDT5pXO.js +1 -0
  429. package/dist/assets/openscad-Cc8OM7cV.js +1 -0
  430. package/dist/assets/ordinal-hYBb2elL.js +1 -0
  431. package/dist/assets/packet-YPE3B663-UGN3Kd1X.js +1 -0
  432. package/dist/assets/pascal-4ZHwLPI5.js +1 -0
  433. package/dist/assets/pascal-DRxrTJXf.js +1 -0
  434. package/dist/assets/perl-BKVR4McH.js +1 -0
  435. package/dist/assets/perl-BaQktjr3.js +1 -0
  436. package/dist/assets/php-06BrA8TC.js +1 -0
  437. package/dist/assets/php-D8OyC7c-.js +1 -0
  438. package/dist/assets/pie-LRSECV5Y-BdTkxwzm.js +1 -0
  439. package/dist/assets/pieDiagram-4H26LBE5-CswNzLKt.js +30 -0
  440. package/dist/assets/pkl-C3VTVe07.js +1 -0
  441. package/dist/assets/pkl-ot-7Btpt.js +1 -0
  442. package/dist/assets/plastic-CF8VZTnn.js +1 -0
  443. package/dist/assets/plastic-DQwYfKfQ.js +1 -0
  444. package/dist/assets/plsql-DGHpHOYJ.js +1 -0
  445. package/dist/assets/plsql-DKCJv1ec.js +1 -0
  446. package/dist/assets/po-BiJDBrnU.js +1 -0
  447. package/dist/assets/po-DLYw31Q8.js +1 -0
  448. package/dist/assets/poimandres-BX2TyY6j.js +1 -0
  449. package/dist/assets/poimandres-DRFjx7u4.js +1 -0
  450. package/dist/assets/polar-C7UOKdEL.js +1 -0
  451. package/dist/assets/polar-D_X-vjiJ.js +1 -0
  452. package/dist/assets/postcss-BXeXVLqQ.js +1 -0
  453. package/dist/assets/postcss-BunjpCQr.js +1 -0
  454. package/dist/assets/powerquery-B6M5zXQ4.js +1 -0
  455. package/dist/assets/powerquery-DNMTfnFr.js +1 -0
  456. package/dist/assets/powershell-DqZlBT-v.js +1 -0
  457. package/dist/assets/powershell-DshXNtvi.js +1 -0
  458. package/dist/assets/prisma-BsRQq5mF.js +1 -0
  459. package/dist/assets/prisma-VLinVlWN.js +1 -0
  460. package/dist/assets/prolog-C--lwqXe.js +1 -0
  461. package/dist/assets/prolog-iXnhIJG7.js +1 -0
  462. package/dist/assets/proto-DB4EqR-F.js +1 -0
  463. package/dist/assets/proto-DSd2dzpT.js +1 -0
  464. package/dist/assets/pug-BgRKZRpz.js +1 -0
  465. package/dist/assets/pug-DBEaSvAZ.js +1 -0
  466. package/dist/assets/puppet-CDv2pdJW.js +1 -0
  467. package/dist/assets/puppet-DDYKwRcl.js +1 -0
  468. package/dist/assets/purescript-9MfHhQsQ.js +1 -0
  469. package/dist/assets/purescript-C58zfXw7.js +1 -0
  470. package/dist/assets/python-DkGugaUl.js +1 -0
  471. package/dist/assets/python-gzcpVVnB.js +1 -0
  472. package/dist/assets/qml-DxOVRBYW.js +1 -0
  473. package/dist/assets/qml-IWh_Vkpx.js +1 -0
  474. package/dist/assets/qmldir-5WAoO9FS.js +1 -0
  475. package/dist/assets/qmldir-DCQb3MpD.js +1 -0
  476. package/dist/assets/qss-Fe1Jh2GI.js +1 -0
  477. package/dist/assets/qss-iwYQbQ5a.js +1 -0
  478. package/dist/assets/quadrantDiagram-W4KKPZXB-iFRRDUL_.js +7 -0
  479. package/dist/assets/r-tpCN-8ZP.js +1 -0
  480. package/dist/assets/r-wgrMyPi0.js +1 -0
  481. package/dist/assets/racket-ByduP6X2.js +1 -0
  482. package/dist/assets/racket-DcIDlBhZ.js +1 -0
  483. package/dist/assets/radar-GUYGQ44K-DrKPOUTN.js +1 -0
  484. package/dist/assets/raku-B3gFvitq.js +1 -0
  485. package/dist/assets/raku-CsvfXYUb.js +1 -0
  486. package/dist/assets/razor-BzussI8C.js +1 -0
  487. package/dist/assets/razor-f_4jxcSf.js +1 -0
  488. package/dist/assets/red-7ga_dcKu.js +1 -0
  489. package/dist/assets/red-CJ3rzSJv.js +1 -0
  490. package/dist/assets/reg-CRGYupPL.js +1 -0
  491. package/dist/assets/reg-efiUL9qS.js +1 -0
  492. package/dist/assets/regexp-CiT3pn3K.js +1 -0
  493. package/dist/assets/regexp-LQiMbZwu.js +1 -0
  494. package/dist/assets/rel-BFvj_u7U.js +1 -0
  495. package/dist/assets/rel-BtDbiS_P.js +1 -0
  496. package/dist/assets/requirementDiagram-4Y6WPE33-BaaILJZ-.js +84 -0
  497. package/dist/assets/riscv-CQLA6sFw.js +1 -0
  498. package/dist/assets/riscv-Ckw8ddFX.js +1 -0
  499. package/dist/assets/ron-UVnZQNEl.js +1 -0
  500. package/dist/assets/ron-VUp2lXgN.js +1 -0
  501. package/dist/assets/rose-pine-BthvhNj6.js +1 -0
  502. package/dist/assets/rose-pine-dawn-DSuRaP6u.js +1 -0
  503. package/dist/assets/rose-pine-dawn-Dg85fqjY.js +1 -0
  504. package/dist/assets/rose-pine-moon-DdHjCvFQ.js +1 -0
  505. package/dist/assets/rose-pine-moon-hon4tzzS.js +1 -0
  506. package/dist/assets/rose-pine-rh6ipraJ.js +1 -0
  507. package/dist/assets/rosmsg-CAekHB0j.js +1 -0
  508. package/dist/assets/rosmsg-Dwq6pEKS.js +1 -0
  509. package/dist/assets/rst-BxO2c92T.js +1 -0
  510. package/dist/assets/rst-DEdyTHH2.js +1 -0
  511. package/dist/assets/ruby-B8lm-vxk.js +1 -0
  512. package/dist/assets/ruby-us5DFUkT.js +1 -0
  513. package/dist/assets/rust-Cfkwpbl8.js +1 -0
  514. package/dist/assets/rust-DfhhoMHs.js +1 -0
  515. package/dist/assets/sankeyDiagram-5OEKKPKP-BpTfzrQf.js +40 -0
  516. package/dist/assets/sas-DX2Cgoeg.js +1 -0
  517. package/dist/assets/sas-nHSCsQ5v.js +1 -0
  518. package/dist/assets/sass-DXrisJhu.js +1 -0
  519. package/dist/assets/sass-DiF7DxUu.js +1 -0
  520. package/dist/assets/scala-DKOlJaKm.js +1 -0
  521. package/dist/assets/scala-F4M-xFqw.js +1 -0
  522. package/dist/assets/scheme-DQCgrYNe.js +1 -0
  523. package/dist/assets/scheme-yKF7wU_W.js +1 -0
  524. package/dist/assets/scss-MUwI6iVT.js +1 -0
  525. package/dist/assets/scss-Sz7VDCGh.js +1 -0
  526. package/dist/assets/sdbl-DzilZtvS.js +1 -0
  527. package/dist/assets/sdbl-bTVj8UrX.js +1 -0
  528. package/dist/assets/sequenceDiagram-3UESZ5HK-CzHEWA5F.js +162 -0
  529. package/dist/assets/shaderlab-QzRPXpa3.js +1 -0
  530. package/dist/assets/shaderlab-TOUzSsQk.js +1 -0
  531. package/dist/assets/shellscript-8fHBiosD.js +1 -0
  532. package/dist/assets/shellscript-i6xQSzFu.js +1 -0
  533. package/dist/assets/shellsession--X9H7mHk.js +1 -0
  534. package/dist/assets/shellsession-BwC8YCFZ.js +1 -0
  535. package/dist/assets/slack-dark-DYvUXyrP.js +1 -0
  536. package/dist/assets/slack-dark-DnToyrRv.js +1 -0
  537. package/dist/assets/slack-ochin-B2OO5cIa.js +1 -0
  538. package/dist/assets/slack-ochin-bqtMVDfH.js +1 -0
  539. package/dist/assets/smalltalk-B16xEiuN.js +1 -0
  540. package/dist/assets/smalltalk-Czn2qXp6.js +1 -0
  541. package/dist/assets/snazzy-light-4G7pJPwS.js +1 -0
  542. package/dist/assets/snazzy-light-D8dzFmq5.js +1 -0
  543. package/dist/assets/solarized-dark-BQwPDzxK.js +1 -0
  544. package/dist/assets/solarized-dark-DV17i1UV.js +1 -0
  545. package/dist/assets/solarized-light-BLaijb3X.js +1 -0
  546. package/dist/assets/solarized-light-DSh2HLQt.js +1 -0
  547. package/dist/assets/solidity-BOOD8PsF.js +1 -0
  548. package/dist/assets/solidity-CKzVLygQ.js +1 -0
  549. package/dist/assets/soy-ChTKvxA1.js +1 -0
  550. package/dist/assets/soy-D-9bKksu.js +1 -0
  551. package/dist/assets/sparql-BQ8pLLZr.js +1 -0
  552. package/dist/assets/sparql-D_iOobhT.js +1 -0
  553. package/dist/assets/splunk-BC2Px7Mm.js +1 -0
  554. package/dist/assets/splunk-Dy5hSDrK.js +1 -0
  555. package/dist/assets/sql-BND-P5sW.js +1 -0
  556. package/dist/assets/sql-DenxdSnS.js +1 -0
  557. package/dist/assets/ssh-config-BgfXC-Er.js +1 -0
  558. package/dist/assets/ssh-config-DlA0k24P.js +1 -0
  559. package/dist/assets/stata-CjzxkyO0.js +1 -0
  560. package/dist/assets/stata-CpuHvqC9.js +1 -0
  561. package/dist/assets/stateDiagram-AJRCARHV-BQTcwjRH.js +1 -0
  562. package/dist/assets/stateDiagram-v2-BHNVJYJU-BIClT_Tr.js +1 -0
  563. package/dist/assets/stylus-B6D30XZt.js +1 -0
  564. package/dist/assets/stylus-BrO8Mu5X.js +1 -0
  565. package/dist/assets/surrealql-CIFK726Y.js +1 -0
  566. package/dist/assets/surrealql-TDCvJyWl.js +1 -0
  567. package/dist/assets/svelte-BOuHBznH.js +1 -0
  568. package/dist/assets/svelte-DmsWqsUB.js +1 -0
  569. package/dist/assets/swift-DonLKvLd.js +1 -0
  570. package/dist/assets/swift-aU1DoeFE.js +1 -0
  571. package/dist/assets/synthwave-84-CIknw8y7.js +1 -0
  572. package/dist/assets/synthwave-84-nFMaYfgc.js +1 -0
  573. package/dist/assets/system-verilog-CTJOgDVF.js +1 -0
  574. package/dist/assets/system-verilog-DJ5XKQeo.js +1 -0
  575. package/dist/assets/systemd-BxMlprV5.js +1 -0
  576. package/dist/assets/systemd-vW0EBnEZ.js +1 -0
  577. package/dist/assets/talonscript-CohzipZa.js +1 -0
  578. package/dist/assets/talonscript-DOWETJMR.js +1 -0
  579. package/dist/assets/tasl-BCK2yre5.js +1 -0
  580. package/dist/assets/tasl-DMoTqEGO.js +1 -0
  581. package/dist/assets/tcl-CZd0xW_V.js +1 -0
  582. package/dist/assets/tcl-DpJlRc9L.js +1 -0
  583. package/dist/assets/templ-CTV6EO0d.js +1 -0
  584. package/dist/assets/templ-DFYOf8RG.js +1 -0
  585. package/dist/assets/terraform-BOmekhaT.js +1 -0
  586. package/dist/assets/terraform-DswuEJGm.js +1 -0
  587. package/dist/assets/tex-PpK3gmTO.js +1 -0
  588. package/dist/assets/tex-g578CdnF.js +1 -0
  589. package/dist/assets/timeline-definition-PNZ67QCA-DucKcgXL.js +120 -0
  590. package/dist/assets/tokyo-night-iIspnLcq.js +1 -0
  591. package/dist/assets/tokyo-night-oM2G3aXe.js +1 -0
  592. package/dist/assets/toml-CcmNWLt0.js +1 -0
  593. package/dist/assets/toml-DPDNdnh9.js +1 -0
  594. package/dist/assets/treeView-BLDUP644-CQLcMrBj.js +1 -0
  595. package/dist/assets/treemap-LRROVOQU-Cw2xscKE.js +1 -0
  596. package/dist/assets/ts-tags-BKj2Ru1s.js +1 -0
  597. package/dist/assets/ts-tags-Bz_MO_O-.js +1 -0
  598. package/dist/assets/tsv-BCypI0LB.js +1 -0
  599. package/dist/assets/tsv-sltzmVWM.js +1 -0
  600. package/dist/assets/tsx-BZ52agMW.js +1 -0
  601. package/dist/assets/tsx-DHnBvH6b.js +1 -0
  602. package/dist/assets/turtle-ByJddavk.js +1 -0
  603. package/dist/assets/turtle-CA3RhB-1.js +1 -0
  604. package/dist/assets/twig-BAGTH_EO.js +1 -0
  605. package/dist/assets/twig-pNPiMIRZ.js +1 -0
  606. package/dist/assets/typescript-DNfLp1GQ.js +1 -0
  607. package/dist/assets/typescript-OsK9n_v1.js +1 -0
  608. package/dist/assets/typespec-B88KGewJ.js +1 -0
  609. package/dist/assets/typespec-BRdr0IET.js +1 -0
  610. package/dist/assets/typst-DI99ib-x.js +1 -0
  611. package/dist/assets/typst-DR_hs4Gi.js +1 -0
  612. package/dist/assets/v-ByGVE3cP.js +1 -0
  613. package/dist/assets/v-DETTlOr0.js +1 -0
  614. package/dist/assets/vala-DUFyzj-T.js +1 -0
  615. package/dist/assets/vala-zf12oZj6.js +1 -0
  616. package/dist/assets/vb-Djn5o6TS.js +1 -0
  617. package/dist/assets/vb-DzJKgt2N.js +1 -0
  618. package/dist/assets/vennDiagram-CIIHVFJN-C-4HKq9M.js +34 -0
  619. package/dist/assets/verilog-C0HRahXC.js +1 -0
  620. package/dist/assets/verilog-CiiDBU1e.js +1 -0
  621. package/dist/assets/vesper-D5bVUKB1.js +1 -0
  622. package/dist/assets/vesper-DdrHHSXu.js +1 -0
  623. package/dist/assets/vhdl-BroJfC0k.js +1 -0
  624. package/dist/assets/vhdl-Dp1NtpV2.js +1 -0
  625. package/dist/assets/viml-BLGuOzJC.js +1 -0
  626. package/dist/assets/viml-DvXPmvsu.js +1 -0
  627. package/dist/assets/vitesse-black-D082JcaE.js +1 -0
  628. package/dist/assets/vitesse-black-fwtXNY1n.js +1 -0
  629. package/dist/assets/vitesse-dark-BZCL-v6S.js +1 -0
  630. package/dist/assets/vitesse-dark-z-nr_5O8.js +1 -0
  631. package/dist/assets/vitesse-light-VbXTXTou.js +1 -0
  632. package/dist/assets/vitesse-light-ojz42ZLy.js +1 -0
  633. package/dist/assets/vue-BZC3_NNh.js +1 -0
  634. package/dist/assets/vue-CPO5dRgN.js +1 -0
  635. package/dist/assets/vue-html-CROUL0mb.js +1 -0
  636. package/dist/assets/vue-html-DQaFnxAW.js +1 -0
  637. package/dist/assets/vue-vine-CI0jOQSg.js +1 -0
  638. package/dist/assets/vue-vine-CllluMwb.js +1 -0
  639. package/dist/assets/vyper-CgoNMtux.js +1 -0
  640. package/dist/assets/vyper-DyY9EjYM.js +1 -0
  641. package/dist/assets/wardley-L42UT6IY-CQQ8lBX5.js +1 -0
  642. package/dist/assets/wardleyDiagram-YWT4CUSO-BIAi-G68.js +78 -0
  643. package/dist/assets/wasm-BJZAGh5c.js +1 -0
  644. package/dist/assets/wasm-BnjxR4X6.js +1 -0
  645. package/dist/assets/wasm-ByWQv1Qj.js +1 -0
  646. package/dist/assets/wasm-DpeUkDXq.js +1 -0
  647. package/dist/assets/wenyan-C8pVoKbM.js +1 -0
  648. package/dist/assets/wenyan-kGy6j2rl.js +1 -0
  649. package/dist/assets/wgsl-BsKzXJz4.js +1 -0
  650. package/dist/assets/wgsl-CLosOKVB.js +1 -0
  651. package/dist/assets/wikitext-B00H4D6m.js +1 -0
  652. package/dist/assets/wikitext-ClFFjSW2.js +1 -0
  653. package/dist/assets/wit-DdvCle-K.js +1 -0
  654. package/dist/assets/wit-Ib9mjOBG.js +1 -0
  655. package/dist/assets/wolfram-DLL8P-h_.js +1 -0
  656. package/dist/assets/wolfram-ofSK-Pwc.js +1 -0
  657. package/dist/assets/xml-CB-djM40.js +1 -0
  658. package/dist/assets/xml-DCbgwso_.js +1 -0
  659. package/dist/assets/xsl-gc768h5Z.js +1 -0
  660. package/dist/assets/xsl-yyPrc0d0.js +1 -0
  661. package/dist/assets/xychartDiagram-2RQKCTM6-C6G1uiI-.js +7 -0
  662. package/dist/assets/yaml-DpLOLpJN.js +1 -0
  663. package/dist/assets/yaml-LMZZZ_NN.js +1 -0
  664. package/dist/assets/zenscript-BnlCZFoB.js +1 -0
  665. package/dist/assets/zenscript-WrGCG97x.js +1 -0
  666. package/dist/assets/zig-CMLA9XwU.js +1 -0
  667. package/dist/assets/zig-apmbPCW-.js +1 -0
  668. package/dist/icons/pi-web-ui-192.png +0 -0
  669. package/dist/icons/pi-web-ui-512.png +0 -0
  670. package/dist/icons/pi-web-ui-maskable-512.png +0 -0
  671. package/dist/index.html +16 -0
  672. package/dist/manifest.json +28 -0
  673. package/dist/sw.js +58 -0
  674. package/extensions/imessage-bridge.ts +440 -0
  675. package/extensions/mirror-server.ts +1823 -0
  676. package/package.json +81 -0
  677. package/public/icons/pi-web-ui-192.png +0 -0
  678. package/public/icons/pi-web-ui-512.png +0 -0
  679. package/public/icons/pi-web-ui-maskable-512.png +0 -0
  680. package/public/manifest.json +28 -0
  681. package/public/sw.js +58 -0
@@ -0,0 +1,1823 @@
1
+ /**
2
+ * pi-web-ui Mirror Server Extension
3
+ *
4
+ * Starts a WebSocket + HTTP server inside the running Pi process,
5
+ * allowing a browser to connect and mirror the TUI session in real-time.
6
+ *
7
+ * - Forwards all Pi events to connected browser clients
8
+ * - Accepts commands from the browser and executes them via the extension API
9
+ * - Serves static files for the pi-web-ui frontend
10
+ * - Sends full state snapshot on client connect (messages, model, etc.)
11
+ */
12
+
13
+ import * as fs from "node:fs";
14
+ import * as http from "node:http";
15
+ import * as path from "node:path";
16
+ import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
17
+ import { WebSocket, WebSocketServer } from "ws";
18
+
19
+ type SettingsData = {
20
+ port?: string | number;
21
+ host?: string;
22
+ disabled?: boolean;
23
+ };
24
+
25
+ type BrowserImage = {
26
+ data?: string;
27
+ mimeType?: string;
28
+ };
29
+
30
+ type BrowserCommand = {
31
+ id?: string;
32
+ type?: string;
33
+ message?: string;
34
+ images?: BrowserImage[];
35
+ streamingBehavior?: string;
36
+ provider?: string;
37
+ modelId?: string;
38
+ level?: "off" | "minimal" | "low" | "medium" | "high";
39
+ name?: string;
40
+ entryId?: string;
41
+ outputPath?: string;
42
+ enabled?: boolean;
43
+ };
44
+
45
+ type BrowserResponse = {
46
+ type: "response";
47
+ command: string;
48
+ success: boolean;
49
+ id?: string;
50
+ data?: unknown;
51
+ error?: string;
52
+ };
53
+
54
+ type TextContentBlock = {
55
+ type: "text";
56
+ text: string;
57
+ };
58
+
59
+ type ImageContentBlock = {
60
+ type: "image";
61
+ data: string;
62
+ mimeType: "image/png" | "image/jpeg" | "image/gif" | "image/webp";
63
+ };
64
+
65
+ type SessionEntry = {
66
+ type?: string;
67
+ id?: string;
68
+ timestamp?: string;
69
+ cwd?: string;
70
+ name?: string;
71
+ message?: {
72
+ role?: string;
73
+ content?: unknown;
74
+ };
75
+ };
76
+
77
+ type ParsedSession = {
78
+ id: string;
79
+ timestamp: string;
80
+ name: string | null;
81
+ firstMessage: string | null;
82
+ cwd: string | null;
83
+ };
84
+
85
+ type SessionListItem = ParsedSession & {
86
+ file: string;
87
+ filePath: string;
88
+ mtime: number;
89
+ tmux?: true;
90
+ };
91
+
92
+ type ProjectSessionGroup = {
93
+ path: string;
94
+ dirName: string;
95
+ sessions: SessionListItem[];
96
+ };
97
+
98
+ type FileListItem = {
99
+ name: string;
100
+ path: string;
101
+ isDirectory: boolean;
102
+ size: number | null;
103
+ mtime: number;
104
+ };
105
+
106
+ type SearchMatch = {
107
+ role: string;
108
+ snippet: string;
109
+ };
110
+
111
+ type SearchResult = {
112
+ filePath: string;
113
+ project: string;
114
+ sessionId: string;
115
+ sessionName: string;
116
+ sessionTimestamp: string;
117
+ firstMessage: string;
118
+ matches: SearchMatch[];
119
+ };
120
+
121
+ type ModelCandidate = {
122
+ provider?: string;
123
+ id?: string;
124
+ };
125
+
126
+ type ExtensionAPIWithEvents = ExtensionAPI & {
127
+ events?: {
128
+ on?: (eventType: string, listener: (payload: unknown) => void) => void;
129
+ };
130
+ };
131
+
132
+ type AliveWebSocket = WebSocket & {
133
+ isAlive?: boolean;
134
+ };
135
+
136
+ type WritableSocket = Pick<WebSocket, "readyState" | "send">;
137
+
138
+ type NodeError = Error & {
139
+ code?: string;
140
+ };
141
+
142
+ const AGENT_DIR = path.resolve(
143
+ (process.env.PI_CODING_AGENT_DIR || path.join(process.env.HOME || "~", ".pi/agent")).replace(
144
+ /^~(?=$|\/)/,
145
+ process.env.HOME || "~",
146
+ ),
147
+ );
148
+ const CUSTOM_SESSIONS_DIR = process.env.PI_CODING_AGENT_SESSION_DIR
149
+ ? path.resolve(process.env.PI_CODING_AGENT_SESSION_DIR.replace(/^~(?=$|\/)/, process.env.HOME || "~"))
150
+ : null;
151
+ const SESSIONS_DIR = CUSTOM_SESSIONS_DIR || path.join(AGENT_DIR, "sessions");
152
+ const CUSTOM_SESSIONS_GROUP = "__custom__";
153
+
154
+ // Load pi-web-ui settings from the Pi agent directory (falls back to env vars)
155
+ function loadSettings(): {
156
+ port: number;
157
+ host: string;
158
+ autoStart: boolean;
159
+ } {
160
+ let settings: SettingsData = {};
161
+ try {
162
+ const settingsPath = path.join(AGENT_DIR, "settings.json");
163
+ settings =
164
+ (
165
+ JSON.parse(fs.readFileSync(settingsPath, "utf8")) as {
166
+ "pi-web-ui"?: SettingsData;
167
+ }
168
+ )["pi-web-ui"] || {};
169
+ } catch {}
170
+ return {
171
+ port: parseInt(String(process.env.PI_WEB_UI_PORT || settings.port || "3001"), 10),
172
+ host: process.env.PI_WEB_UI_HOST || settings.host || "127.0.0.1",
173
+ autoStart: !(
174
+ process.env.PI_WEB_UI_DISABLED === "1" ||
175
+ process.env.PI_WEB_UI_DISABLED === "true" ||
176
+ settings.disabled === true
177
+ ),
178
+ };
179
+ }
180
+
181
+ const SETTINGS = loadSettings();
182
+ const PORT = SETTINGS.port;
183
+ const HOST = SETTINGS.host;
184
+ const AUTO_START = SETTINGS.autoStart;
185
+ // @ts-expect-error — __dirname is provided by jiti at runtime
186
+ const STATIC_DIR = process.env.PI_WEB_UI_STATIC_DIR || findStaticDir();
187
+
188
+ function findStaticDir(): string {
189
+ const candidates: string[] = [];
190
+ const seen = new Set<string>();
191
+ const addCandidate = (dir: string) => {
192
+ const normalized = path.resolve(dir);
193
+ if (seen.has(normalized)) return;
194
+ seen.add(normalized);
195
+ candidates.push(normalized);
196
+ };
197
+
198
+ // 1) Bundled React build output, then legacy public fallback.
199
+ addCandidate(path.resolve(__dirname, "dist"));
200
+ addCandidate(path.resolve(__dirname, "../dist"));
201
+ addCandidate(path.resolve(__dirname, "public"));
202
+ addCandidate(path.resolve(__dirname, "../public"));
203
+
204
+ // 2) Installed package path (for npm-installed extension execution)
205
+ try {
206
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
207
+ const pkgPath = require.resolve("@kkkiio/pi-web-ui/package.json");
208
+ const pkgDir = path.dirname(pkgPath);
209
+ addCandidate(path.join(pkgDir, "dist"));
210
+ addCandidate(path.join(pkgDir, "public"));
211
+ } catch {}
212
+
213
+ // 3) Development fallback from current working directory
214
+ addCandidate(path.resolve(process.cwd(), "dist"));
215
+ addCandidate(path.resolve(process.cwd(), "public"));
216
+ addCandidate(path.resolve(process.cwd(), "node_modules/@kkkiio/pi-web-ui/dist"));
217
+ addCandidate(path.resolve(process.cwd(), "node_modules/@kkkiio/pi-web-ui/public"));
218
+
219
+ for (const candidate of candidates) {
220
+ if (fs.existsSync(path.join(candidate, "index.html"))) return candidate;
221
+ }
222
+
223
+ return path.resolve(process.cwd(), "dist");
224
+ }
225
+
226
+ // MIME types for static file serving
227
+ const MIME_TYPES: Record<string, string> = {
228
+ ".html": "text/html",
229
+ ".css": "text/css",
230
+ ".js": "application/javascript",
231
+ ".json": "application/json",
232
+ ".map": "application/json",
233
+ ".webmanifest": "application/manifest+json",
234
+ ".png": "image/png",
235
+ ".jpg": "image/jpeg",
236
+ ".jpeg": "image/jpeg",
237
+ ".gif": "image/gif",
238
+ ".webp": "image/webp",
239
+ ".svg": "image/svg+xml",
240
+ ".ico": "image/x-icon",
241
+ ".woff": "font/woff",
242
+ ".woff2": "font/woff2",
243
+ };
244
+
245
+ export default function (pi: ExtensionAPI) {
246
+ let server: http.Server | null = null;
247
+ let wss: WebSocketServer | null = null;
248
+ let heartbeatTimer: NodeJS.Timeout | null = null;
249
+ const clients = new Set<WebSocket>();
250
+
251
+ // Store latest context reference for use in command handlers
252
+ let latestCtx: ExtensionContext | null = null;
253
+ let latestNavigateTree: ((targetId: string) => Promise<{ cancelled: boolean; editorText?: string }>) | null = null;
254
+
255
+ // ═══════════════════════════════════════
256
+ // Helper: send to one client
257
+ // ═══════════════════════════════════════
258
+ function sendTo(ws: WritableSocket, data: unknown) {
259
+ if (ws.readyState === WebSocket.OPEN) {
260
+ ws.send(JSON.stringify(data));
261
+ }
262
+ }
263
+
264
+ // ═══════════════════════════════════════
265
+ // Helper: broadcast to all clients
266
+ // ═══════════════════════════════════════
267
+ function broadcast(data: unknown) {
268
+ const json = JSON.stringify(data);
269
+ for (const client of clients) {
270
+ if (client.readyState === WebSocket.OPEN) {
271
+ client.send(json);
272
+ }
273
+ }
274
+ }
275
+
276
+ let mirrorUrl = "";
277
+ let mirrorStatusBase = "";
278
+
279
+ function updateMirrorStatus() {
280
+ if (!mirrorStatusBase) {
281
+ latestCtx?.ui.setStatus("webui", "");
282
+ return;
283
+ }
284
+ const clientCount = clients.size;
285
+ const clientText = clientCount > 0 ? ` • ${clientCount} web ${clientCount === 1 ? "client" : "clients"}` : "";
286
+ latestCtx?.ui.setStatus("webui", `${mirrorStatusBase}${clientText}`);
287
+ }
288
+
289
+ // ═══════════════════════════════════════
290
+ // Helper: stop the server
291
+ // ═══════════════════════════════════════
292
+ function stopServer() {
293
+ if (heartbeatTimer) {
294
+ clearInterval(heartbeatTimer);
295
+ heartbeatTimer = null;
296
+ }
297
+ if (wss) {
298
+ for (const client of clients) {
299
+ client.close();
300
+ }
301
+ clients.clear();
302
+ try {
303
+ wss.close();
304
+ } catch {}
305
+ wss = null;
306
+ }
307
+ if (server) {
308
+ try {
309
+ server.close();
310
+ } catch {}
311
+ server = null;
312
+ }
313
+ mirrorUrl = "";
314
+ mirrorStatusBase = "";
315
+ }
316
+
317
+ // ═══════════════════════════════════════
318
+ // /webui-stop and /webui-start commands
319
+ // ═══════════════════════════════════════
320
+ pi.registerCommand("webui-stop", {
321
+ description: "Stop the pi-web-ui server",
322
+ handler: async (_args, ctx) => {
323
+ if (!server) {
324
+ ctx.ui.notify("pi-web-ui is not running", "warning");
325
+ return;
326
+ }
327
+ stopServer();
328
+ ctx.ui.setStatus("webui", "");
329
+ ctx.ui.notify("pi-web-ui server stopped", "info");
330
+ },
331
+ });
332
+
333
+ pi.registerCommand("webui-start", {
334
+ description: "Start the pi-web-ui server",
335
+ handler: async (_args, ctx) => {
336
+ if (server) {
337
+ ctx.ui.notify(`pi-web-ui is already running at ${mirrorUrl}`, "warning");
338
+ return;
339
+ }
340
+ latestCtx = ctx;
341
+ startServer();
342
+ ctx.ui.notify("pi-web-ui server starting...", "info");
343
+ },
344
+ });
345
+
346
+ // ═══════════════════════════════════════
347
+ // /webui command — open Pi Web UI in browser
348
+ // ═══════════════════════════════════════
349
+ pi.registerCommand("webui", {
350
+ description: "Open Pi Web UI in browser",
351
+ handler: async (_args, ctx) => {
352
+ if (!mirrorUrl) {
353
+ ctx.ui.notify("pi-web-ui server not running yet", "warning");
354
+ return;
355
+ }
356
+ const { exec } = require("node:child_process");
357
+ exec(`open "${mirrorUrl}"`);
358
+ ctx.ui.notify(`Opened ${mirrorUrl}`, "info");
359
+ // Capture navigateTree for use by WebSocket handler (ADR 0003)
360
+ latestNavigateTree = (targetId) => ctx.navigateTree(targetId);
361
+ broadcast({ type: "state", advancedFeatures: true });
362
+ },
363
+ });
364
+
365
+ // ═══════════════════════════════════════
366
+ // Event forwarding — subscribe to all Pi events
367
+ // ═══════════════════════════════════════
368
+ const eventTypes = [
369
+ "agent_start",
370
+ "agent_end",
371
+ "turn_start",
372
+ "turn_end",
373
+ "message_start",
374
+ "message_update",
375
+ "message_end",
376
+ "tool_execution_start",
377
+ "tool_execution_update",
378
+ "tool_execution_end",
379
+ "auto_compaction_start",
380
+ "auto_compaction_end",
381
+ "auto_retry_start",
382
+ "auto_retry_end",
383
+ "model_select",
384
+ ] as const;
385
+
386
+ for (const eventType of eventTypes) {
387
+ pi.on(eventType as Parameters<ExtensionAPI["on"]>[0], async (event: unknown, ctx: ExtensionContext) => {
388
+ latestCtx = ctx;
389
+ const eventPayload = typeof event === "object" && event !== null ? (event as Record<string, unknown>) : {};
390
+
391
+ // Forward event to all connected browser clients
392
+ // Wrap in { type: "event", event: ... } to match the existing frontend protocol
393
+ broadcast({
394
+ type: "event",
395
+ event: { type: eventType, ...eventPayload },
396
+ });
397
+ });
398
+ }
399
+
400
+ const subagentEventTypes = [
401
+ "subagents:ready",
402
+ "subagents:created",
403
+ "subagents:started",
404
+ "subagents:completed",
405
+ "subagents:failed",
406
+ "subagents:steered",
407
+ "subagents:compacted",
408
+ "subagents:scheduled",
409
+ "subagents:scheduler_ready",
410
+ "subagents:settings_loaded",
411
+ "subagents:settings_changed",
412
+ ] as const;
413
+
414
+ for (const eventType of subagentEventTypes) {
415
+ (pi as ExtensionAPIWithEvents).events?.on?.(eventType, (payload: unknown) => {
416
+ broadcast({ type: "event", event: { type: eventType, payload } });
417
+ });
418
+ }
419
+
420
+ // Also capture context from session events
421
+ // Auto-title: collect user messages and generate a title after a few turns
422
+ let turnCount = 0;
423
+ let titleSet = false;
424
+ let userMessages: string[] = [];
425
+
426
+ pi.on("session_start", async (_event, ctx) => {
427
+ latestCtx = ctx;
428
+ latestNavigateTree = null;
429
+ broadcast({ type: "state", advancedFeatures: false });
430
+ turnCount = 0;
431
+ titleSet = false;
432
+ userMessages = [];
433
+ });
434
+
435
+ pi.on("turn_start", async (_event, _ctx) => {
436
+ turnCount++;
437
+ });
438
+
439
+ // Capture user messages for title generation via message_start
440
+ pi.on("message_start", async (event, _ctx) => {
441
+ if (titleSet) return;
442
+ const msg = event.message;
443
+ if (!msg || msg.role !== "user") return;
444
+ const content = msg.content;
445
+ let text = "";
446
+ if (typeof content === "string") text = content;
447
+ else if (Array.isArray(content)) {
448
+ const tb = content.find(
449
+ (b): b is TextContentBlock =>
450
+ typeof b === "object" &&
451
+ b !== null &&
452
+ (b as { type?: unknown }).type === "text" &&
453
+ typeof (b as { text?: unknown }).text === "string",
454
+ );
455
+ if (tb) text = tb.text;
456
+ }
457
+ if (text) userMessages.push(text.substring(0, 300));
458
+ });
459
+
460
+ pi.on("turn_end", async (_event, _ctx) => {
461
+ if (titleSet || turnCount < 2) return;
462
+
463
+ const sessionName = pi.getSessionName();
464
+ if (sessionName && sessionName !== "New Session" && sessionName !== "Untitled") {
465
+ titleSet = true;
466
+ return;
467
+ }
468
+
469
+ // Generate title from collected messages
470
+ const title = generateSessionTitle(userMessages);
471
+ if (title) {
472
+ pi.setSessionName(title);
473
+ titleSet = true;
474
+ // Broadcast to connected clients
475
+ broadcast({
476
+ type: "event",
477
+ event: { type: "session_name", name: title },
478
+ });
479
+ }
480
+ });
481
+
482
+ function generateSessionTitle(messages: string[]): string | null {
483
+ if (messages.length === 0) return null;
484
+
485
+ // Find first substantive message (skip greetings and memory instructions)
486
+ const greetings = /^(hey|hello|hi|morning|good morning|howdy|yo|sup)[\s!.:,]*$/i;
487
+ const memoryInstructions = /read (your |the )?(memory|seed|persona|working) files/i;
488
+
489
+ let bestMessage = "";
490
+ for (const msg of messages) {
491
+ const cleaned = msg.trim();
492
+ if (greetings.test(cleaned)) continue;
493
+ if (memoryInstructions.test(cleaned)) continue;
494
+ if (cleaned.length < 10) continue;
495
+ bestMessage = cleaned;
496
+ break;
497
+ }
498
+
499
+ if (!bestMessage) {
500
+ // Fall back to first message with any content
501
+ bestMessage = messages.find((m) => m.trim().length > 0) || "";
502
+ }
503
+
504
+ if (!bestMessage) return null;
505
+
506
+ // Extract a clean title: first sentence or clause, max ~60 chars
507
+ let title = bestMessage
508
+ .replace(/^(ok |okay |so |actually |hey |please |can you |could you |i want(ed)? to |i wanna |let'?s )/i, "")
509
+ .replace(/\n.*/s, "") // first line only
510
+ .trim();
511
+
512
+ // Take first sentence
513
+ const sentenceEnd = title.search(/[.!?]\s/);
514
+ if (sentenceEnd > 10 && sentenceEnd < 80) {
515
+ title = title.substring(0, sentenceEnd);
516
+ }
517
+
518
+ // Truncate cleanly
519
+ if (title.length > 60) {
520
+ const spaceIdx = title.lastIndexOf(" ", 57);
521
+ title = `${title.substring(0, spaceIdx > 20 ? spaceIdx : 57)}…`;
522
+ }
523
+
524
+ // Capitalize first letter
525
+ title = title.charAt(0).toUpperCase() + title.slice(1);
526
+
527
+ return title;
528
+ }
529
+
530
+ // ═══════════════════════════════════════
531
+ // Build state snapshot for new connections
532
+ // ═══════════════════════════════════════
533
+ async function buildStateSnapshot(ctx: ExtensionContext) {
534
+ // Get session entries for message history
535
+ const entries = ctx.sessionManager.getBranch();
536
+
537
+ // Get model info
538
+ const model = ctx.model;
539
+ const thinkingLevel = pi.getThinkingLevel();
540
+ const sessionName = pi.getSessionName();
541
+ const sessionFile = ctx.sessionManager.getSessionFile();
542
+
543
+ // Context usage
544
+ const contextUsage = ctx.getContextUsage();
545
+
546
+ return {
547
+ type: "mirror_sync",
548
+ entries,
549
+ model,
550
+ thinkingLevel,
551
+ sessionName,
552
+ sessionFile,
553
+ isStreaming: !ctx.isIdle(),
554
+ contextUsage,
555
+ };
556
+ }
557
+
558
+ // ═══════════════════════════════════════
559
+ // Handle commands from browser clients
560
+ // ═══════════════════════════════════════
561
+ async function handleCommand(ws: WritableSocket, command: BrowserCommand) {
562
+ const id = command.id;
563
+ const ctx = latestCtx;
564
+
565
+ const success = (cmd: string, data?: unknown) => {
566
+ const resp: BrowserResponse = {
567
+ type: "response",
568
+ command: cmd,
569
+ success: true,
570
+ id,
571
+ };
572
+ if (data !== undefined) resp.data = data;
573
+ return resp;
574
+ };
575
+
576
+ const error = (cmd: string, message: string) => {
577
+ return {
578
+ type: "response",
579
+ command: cmd,
580
+ success: false,
581
+ error: message,
582
+ id,
583
+ };
584
+ };
585
+
586
+ try {
587
+ switch (command.type) {
588
+ // ─── Prompting ───
589
+ case "prompt": {
590
+ const message = command.message || "";
591
+ if (ctx && !ctx.isIdle()) {
592
+ const behavior = command.streamingBehavior || "steer";
593
+ if (behavior === "steer") {
594
+ pi.sendUserMessage(message, { deliverAs: "steer" });
595
+ } else {
596
+ pi.sendUserMessage(message, { deliverAs: "followUp" });
597
+ }
598
+ } else {
599
+ // Build content with optional images
600
+ const images = Array.isArray(command.images) ? command.images : [];
601
+ if (images.length) {
602
+ const validMimes = ["image/png", "image/jpeg", "image/gif", "image/webp"];
603
+ const content: Array<TextContentBlock | ImageContentBlock> = [
604
+ { type: "text", text: message || "(see attached image)" },
605
+ ];
606
+ for (const img of images) {
607
+ if (!img.data || typeof img.data !== "string") {
608
+ continue;
609
+ }
610
+ // Strip data URL prefix if accidentally included
611
+ const data = img.data.includes(",") ? img.data.split(",")[1] : img.data;
612
+ const mimeType = (validMimes.includes(img.mimeType) ? img.mimeType : "image/png") as
613
+ | "image/png"
614
+ | "image/jpeg"
615
+ | "image/gif"
616
+ | "image/webp";
617
+ const imageBlock = {
618
+ type: "image" as const,
619
+ data: data,
620
+ mimeType: mimeType,
621
+ };
622
+ if (!imageBlock.mimeType) {
623
+ imageBlock.mimeType = "image/png";
624
+ }
625
+ content.push(imageBlock);
626
+ }
627
+ // Only send content array if we actually have images, otherwise just text
628
+ const hasImages = content.some((c) => c.type === "image");
629
+ if (hasImages) {
630
+ pi.sendUserMessage(content);
631
+ } else {
632
+ pi.sendUserMessage(message);
633
+ }
634
+ } else {
635
+ pi.sendUserMessage(message);
636
+ }
637
+ }
638
+ sendTo(ws, success("prompt"));
639
+ break;
640
+ }
641
+
642
+ case "steer": {
643
+ pi.sendUserMessage(command.message || "", { deliverAs: "steer" });
644
+ sendTo(ws, success("steer"));
645
+ break;
646
+ }
647
+
648
+ case "follow_up": {
649
+ pi.sendUserMessage(command.message || "", { deliverAs: "followUp" });
650
+ sendTo(ws, success("follow_up"));
651
+ break;
652
+ }
653
+
654
+ case "abort": {
655
+ if (ctx) ctx.abort();
656
+ sendTo(ws, success("abort"));
657
+ break;
658
+ }
659
+
660
+ // ─── State ───
661
+ case "get_state": {
662
+ if (!ctx) {
663
+ sendTo(ws, error("get_state", "No context available"));
664
+ break;
665
+ }
666
+ const model = ctx.model;
667
+ const state = {
668
+ model,
669
+ thinkingLevel: pi.getThinkingLevel(),
670
+ isStreaming: !ctx.isIdle(),
671
+ sessionFile: ctx.sessionManager.getSessionFile(),
672
+ sessionName: pi.getSessionName(),
673
+ autoCompactionEnabled: true, // Extension can't easily check this
674
+ };
675
+ sendTo(ws, success("get_state", state));
676
+ break;
677
+ }
678
+
679
+ case "get_messages": {
680
+ if (!ctx) {
681
+ sendTo(ws, error("get_messages", "No context available"));
682
+ break;
683
+ }
684
+ const entries = ctx.sessionManager.getEntries();
685
+ sendTo(ws, success("get_messages", { entries }));
686
+ break;
687
+ }
688
+
689
+ // ─── Model ───
690
+ case "get_available_models": {
691
+ if (!ctx) {
692
+ sendTo(ws, error("get_available_models", "No context available"));
693
+ break;
694
+ }
695
+ const models = await ctx.modelRegistry.getAvailable();
696
+ sendTo(ws, success("get_available_models", { models }));
697
+ break;
698
+ }
699
+
700
+ case "set_model": {
701
+ if (!ctx) {
702
+ sendTo(ws, error("set_model", "No context available"));
703
+ break;
704
+ }
705
+ const models = await ctx.modelRegistry.getAvailable();
706
+ const model = (models as ModelCandidate[]).find(
707
+ (m) => m.provider === command.provider && m.id === command.modelId,
708
+ );
709
+ if (!model) {
710
+ sendTo(ws, error("set_model", `Model not found: ${command.provider}/${command.modelId}`));
711
+ break;
712
+ }
713
+ const ok = await pi.setModel(model);
714
+ if (!ok) {
715
+ sendTo(ws, error("set_model", "No API key for this model"));
716
+ break;
717
+ }
718
+ sendTo(ws, success("set_model", model));
719
+ break;
720
+ }
721
+
722
+ case "cycle_model": {
723
+ // Extension API doesn't have cycleModel directly
724
+ // Workaround: get available models, find current, pick next
725
+ if (!ctx) {
726
+ sendTo(ws, success("cycle_model", null));
727
+ break;
728
+ }
729
+ const availModels = await ctx.modelRegistry.getAvailable();
730
+ const currentModel = ctx.model;
731
+ if (!currentModel || availModels.length <= 1) {
732
+ sendTo(ws, success("cycle_model", null));
733
+ break;
734
+ }
735
+ const idx = (availModels as ModelCandidate[]).findIndex(
736
+ (m) => m.provider === currentModel.provider && m.id === currentModel.id,
737
+ );
738
+ const nextModel = availModels[(idx + 1) % availModels.length];
739
+ await pi.setModel(nextModel);
740
+ sendTo(
741
+ ws,
742
+ success("cycle_model", {
743
+ model: nextModel,
744
+ thinkingLevel: pi.getThinkingLevel(),
745
+ }),
746
+ );
747
+ break;
748
+ }
749
+
750
+ // ─── Thinking ───
751
+ case "cycle_thinking_level": {
752
+ const levels = ["off", "minimal", "low", "medium", "high"];
753
+ const current = pi.getThinkingLevel();
754
+ const idx = levels.indexOf(current);
755
+ const next = levels[(idx + 1) % levels.length];
756
+ pi.setThinkingLevel(next as BrowserCommand["level"]);
757
+ sendTo(ws, success("cycle_thinking_level", { level: next }));
758
+ break;
759
+ }
760
+
761
+ case "set_thinking_level": {
762
+ pi.setThinkingLevel(command.level);
763
+ sendTo(ws, success("set_thinking_level"));
764
+ break;
765
+ }
766
+
767
+ // ─── Session ───
768
+ case "get_session_stats": {
769
+ if (!ctx) {
770
+ sendTo(ws, error("get_session_stats", "No context available"));
771
+ break;
772
+ }
773
+ const usage = ctx.getContextUsage();
774
+ const entries = ctx.sessionManager.getEntries();
775
+ let userMessages = 0,
776
+ assistantMessages = 0,
777
+ toolCalls = 0;
778
+ for (const e of entries) {
779
+ if (e.type === "message") {
780
+ if (e.message?.role === "user") userMessages++;
781
+ else if (e.message?.role === "assistant") assistantMessages++;
782
+ else if (e.message?.role === "toolResult") toolCalls++;
783
+ }
784
+ }
785
+ sendTo(
786
+ ws,
787
+ success("get_session_stats", {
788
+ sessionFile: ctx.sessionManager.getSessionFile(),
789
+ userMessages,
790
+ assistantMessages,
791
+ toolCalls,
792
+ totalMessages: entries.length,
793
+ tokens: usage ? { input: usage.tokens, total: usage.tokens } : null,
794
+ }),
795
+ );
796
+ break;
797
+ }
798
+
799
+ case "set_session_name": {
800
+ const name = command.name?.trim();
801
+ if (!name) {
802
+ sendTo(ws, error("set_session_name", "Name cannot be empty"));
803
+ break;
804
+ }
805
+ pi.setSessionName(name);
806
+ sendTo(ws, success("set_session_name"));
807
+ break;
808
+ }
809
+
810
+ case "set_auto_compaction": {
811
+ // Extension can't easily toggle auto-compaction
812
+ // Just acknowledge
813
+ sendTo(ws, success("set_auto_compaction"));
814
+ break;
815
+ }
816
+
817
+ case "compact": {
818
+ if (ctx) {
819
+ // Broadcast compaction start to all clients
820
+ broadcast({
821
+ type: "event",
822
+ event: { type: "auto_compaction_start" },
823
+ });
824
+ ctx.compact({
825
+ customInstructions: command.customInstructions,
826
+ onComplete: (result: { summary?: unknown }) => {
827
+ broadcast({
828
+ type: "event",
829
+ event: {
830
+ type: "auto_compaction_end",
831
+ summary: result?.summary,
832
+ },
833
+ });
834
+ },
835
+ onError: (err: Error) => {
836
+ broadcast({
837
+ type: "event",
838
+ event: {
839
+ type: "auto_compaction_end",
840
+ summary: `Error: ${err.message}`,
841
+ },
842
+ });
843
+ },
844
+ });
845
+ }
846
+ sendTo(ws, success("compact"));
847
+ break;
848
+ }
849
+
850
+ case "export_html": {
851
+ if (!ctx) {
852
+ sendTo(ws, error("export_html", "No context available"));
853
+ break;
854
+ }
855
+ try {
856
+ const sessionFile = ctx.sessionManager.getSessionFile();
857
+ if (!sessionFile) throw new Error("No session file to export");
858
+ const { execSync } = require("node:child_process");
859
+ const args = command.outputPath ? `"${sessionFile}" "${command.outputPath}"` : `"${sessionFile}"`;
860
+ const output = execSync(`pi --export ${args}`, {
861
+ cwd: process.cwd(),
862
+ timeout: 30000,
863
+ encoding: "utf-8",
864
+ });
865
+ // pi prints the output path
866
+ const result = output.trim().split("\n").pop() || sessionFile.replace(".jsonl", ".html");
867
+ sendTo(ws, success("export_html", { path: result }));
868
+ } catch (e: unknown) {
869
+ const message = e instanceof Error ? e.message : String(e);
870
+ sendTo(ws, error("export_html", message));
871
+ }
872
+ break;
873
+ }
874
+
875
+ // ─── Commands & Files ───
876
+ // ─── Sync ───
877
+ case "mirror_sync_request": {
878
+ if (ctx) {
879
+ const snapshot = await buildStateSnapshot(ctx);
880
+ sendTo(ws, snapshot);
881
+ } else {
882
+ sendTo(ws, { type: "mirror_sync", entries: [], model: null });
883
+ }
884
+ break;
885
+ }
886
+
887
+ // ─── Auth ───
888
+ case "get_auth": {
889
+ sendTo(ws, success("get_auth", { configured: false, enabled: false }));
890
+ break;
891
+ }
892
+
893
+ case "set_auth": {
894
+ sendTo(
895
+ ws,
896
+ error(
897
+ "set_auth",
898
+ "Authentication is not supported. Use tailscale serve or a reverse proxy with TLS for remote access.",
899
+ ),
900
+ );
901
+ break;
902
+ }
903
+
904
+ case "navigate_tree": {
905
+ if (!latestNavigateTree) {
906
+ sendTo(ws, error("navigate_tree", "Run /webui first to enable editing"));
907
+ break;
908
+ }
909
+ if (!latestCtx?.isIdle()) {
910
+ sendTo(ws, error("navigate_tree", "Agent is busy"));
911
+ break;
912
+ }
913
+ const entry = latestCtx.sessionManager.getEntry(command.entryId);
914
+ if (!entry) {
915
+ sendTo(ws, error("navigate_tree", "Entry not found"));
916
+ break;
917
+ }
918
+ if (!latestCtx.model) {
919
+ sendTo(ws, error("navigate_tree", "No model selected"));
920
+ break;
921
+ }
922
+ const result = await latestNavigateTree(entry.id);
923
+ if (result.cancelled) {
924
+ sendTo(ws, error("navigate_tree", "Navigation cancelled"));
925
+ break;
926
+ }
927
+ // Send success before snapshot so RPC promise resolves correctly
928
+ sendTo(ws, success("navigate_tree"));
929
+ // Broadcast updated state to all connected clients
930
+ const snapshot = await buildStateSnapshot(latestCtx);
931
+ broadcast(snapshot);
932
+ break;
933
+ }
934
+
935
+ default: {
936
+ sendTo(ws, error(command.type, `Unknown command: ${command.type}`));
937
+ }
938
+ }
939
+ } catch (e: unknown) {
940
+ const message = e instanceof Error ? e.message : String(e);
941
+ sendTo(ws, error(command.type || "unknown", message));
942
+ }
943
+ }
944
+
945
+ // ═══════════════════════════════════════
946
+ // Static file server
947
+ // ═══════════════════════════════════════
948
+ function serveStaticFile(req: http.IncomingMessage, res: http.ServerResponse) {
949
+ let urlPath = req.url || "/";
950
+
951
+ // Handle API routes
952
+ if (urlPath.startsWith("/api/")) {
953
+ handleApiRoute(req, res, urlPath);
954
+ return;
955
+ }
956
+
957
+ // Strip query params
958
+ urlPath = urlPath.split("?")[0];
959
+
960
+ // Default to index.html
961
+ if (urlPath === "/") urlPath = "/index.html";
962
+
963
+ const filePath = path.join(STATIC_DIR, urlPath);
964
+
965
+ // Security: prevent directory traversal
966
+ if (!filePath.startsWith(STATIC_DIR)) {
967
+ res.writeHead(403);
968
+ res.end("Forbidden");
969
+ return;
970
+ }
971
+
972
+ // Check file exists
973
+ fs.stat(filePath, (err, stats) => {
974
+ if (err || !stats.isFile()) {
975
+ res.writeHead(404);
976
+ res.end("Not Found");
977
+ return;
978
+ }
979
+
980
+ const ext = path.extname(filePath).toLowerCase();
981
+ const contentType = MIME_TYPES[ext] || "application/octet-stream";
982
+
983
+ res.writeHead(200, { "Content-Type": contentType });
984
+ fs.createReadStream(filePath).pipe(res);
985
+ });
986
+ }
987
+
988
+ // ═══════════════════════════════════════
989
+ // API routes (sessions list, etc.)
990
+ // ═══════════════════════════════════════
991
+ function handleApiRoute(req: http.IncomingMessage, res: http.ServerResponse, urlPath: string) {
992
+ // CORS headers
993
+ res.setHeader("Access-Control-Allow-Origin", "*");
994
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
995
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
996
+
997
+ if (req.method === "OPTIONS") {
998
+ res.writeHead(200);
999
+ res.end();
1000
+ return;
1001
+ }
1002
+
1003
+ if (urlPath === "/api/health") {
1004
+ res.writeHead(200, { "Content-Type": "application/json" });
1005
+ res.end(
1006
+ JSON.stringify({
1007
+ status: "ok",
1008
+ mode: "webui",
1009
+ mirrorUrl,
1010
+ }),
1011
+ );
1012
+ return;
1013
+ }
1014
+
1015
+ if (urlPath === "/api/projects" && req.method === "GET") {
1016
+ serveProjectsList(res);
1017
+ return;
1018
+ }
1019
+
1020
+ if (urlPath === "/api/projects/launch" && req.method === "POST") {
1021
+ let body = "";
1022
+ req.on("data", (chunk: Buffer) => {
1023
+ body += chunk.toString();
1024
+ });
1025
+ req.on("end", () => {
1026
+ try {
1027
+ const { path: projectPath } = JSON.parse(body);
1028
+ if (!projectPath || typeof projectPath !== "string") {
1029
+ res.writeHead(400, { "Content-Type": "application/json" });
1030
+ res.end(JSON.stringify({ error: "path required" }));
1031
+ return;
1032
+ }
1033
+ // Resolve ~ in path
1034
+ const resolved = projectPath.startsWith("~")
1035
+ ? path.join(process.env.HOME || "", projectPath.slice(1))
1036
+ : projectPath;
1037
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
1038
+ res.writeHead(400, { "Content-Type": "application/json" });
1039
+ res.end(JSON.stringify({ error: "Directory not found" }));
1040
+ return;
1041
+ }
1042
+ const { execSync } = require("node:child_process");
1043
+ const escaped = resolved.replace(/'/g, "'\\''");
1044
+ execSync(
1045
+ `osascript -e 'tell app "iTerm2" to create window with default profile command "cd '"'"'${escaped}'"'"' && pi"'`,
1046
+ );
1047
+ res.writeHead(200, { "Content-Type": "application/json" });
1048
+ res.end(JSON.stringify({ ok: true }));
1049
+ } catch (e: unknown) {
1050
+ const message = e instanceof Error ? e.message : String(e);
1051
+ res.writeHead(500, { "Content-Type": "application/json" });
1052
+ res.end(JSON.stringify({ error: message }));
1053
+ }
1054
+ });
1055
+ return;
1056
+ }
1057
+
1058
+ if (urlPath === "/api/sessions" && req.method === "GET") {
1059
+ serveSessionsList(res);
1060
+ return;
1061
+ }
1062
+
1063
+ // Full-text search across sessions
1064
+ if (urlPath.startsWith("/api/search") && req.method === "GET") {
1065
+ const searchUrl = new URL(`http://localhost${req.url}`);
1066
+ const q = searchUrl.searchParams.get("q") || "";
1067
+ serveSearch(res, q);
1068
+ return;
1069
+ }
1070
+
1071
+ // File browser: list directory
1072
+ if (urlPath === "/api/files" || urlPath.startsWith("/api/files?")) {
1073
+ if (req.method !== "GET") {
1074
+ res.writeHead(405);
1075
+ res.end();
1076
+ return;
1077
+ }
1078
+ try {
1079
+ const filesUrl = new URL(`http://localhost${req.url}`);
1080
+ const explicitPath = filesUrl.searchParams.get("path");
1081
+ let dirPath = explicitPath || process.cwd();
1082
+ if (!explicitPath && latestCtx) {
1083
+ try {
1084
+ const entries = latestCtx.sessionManager.getEntries() as SessionEntry[];
1085
+ const sessionEntry = entries.find((e) => e.type === "session");
1086
+ if (typeof sessionEntry?.cwd === "string") dirPath = sessionEntry.cwd;
1087
+ } catch {}
1088
+ }
1089
+ serveFileList(res, dirPath);
1090
+ } catch (err: unknown) {
1091
+ const message = err instanceof Error ? err.message : String(err);
1092
+ res.writeHead(500, { "Content-Type": "application/json" });
1093
+ res.end(JSON.stringify({ error: message }));
1094
+ }
1095
+ return;
1096
+ }
1097
+
1098
+ // File browser: open file natively
1099
+ if (urlPath === "/api/open" && req.method === "POST") {
1100
+ let body = "";
1101
+ req.on("data", (chunk: Buffer) => {
1102
+ body += chunk.toString();
1103
+ });
1104
+ req.on("end", async () => {
1105
+ try {
1106
+ const { filePath: fp } = JSON.parse(body);
1107
+ if (!fp || typeof fp !== "string") {
1108
+ res.writeHead(400, { "Content-Type": "application/json" });
1109
+ res.end(JSON.stringify({ error: "filePath required" }));
1110
+ return;
1111
+ }
1112
+ const { execFile } = await import("node:child_process");
1113
+ execFile("open", [fp], (err) => {
1114
+ if (err) latestCtx?.ui.notify(`Failed to open file: ${err.message}`, "warning");
1115
+ });
1116
+ res.writeHead(200, { "Content-Type": "application/json" });
1117
+ res.end(JSON.stringify({ ok: true }));
1118
+ } catch (err: unknown) {
1119
+ const message = err instanceof Error ? err.message : String(err);
1120
+ res.writeHead(500, { "Content-Type": "application/json" });
1121
+ res.end(JSON.stringify({ error: message }));
1122
+ }
1123
+ });
1124
+ return;
1125
+ }
1126
+
1127
+ // Session file endpoint: /api/sessions/:dirName/:file
1128
+ const sessionMatch = urlPath.match(/^\/api\/sessions\/([^/]+)\/([^/]+)$/);
1129
+ if (sessionMatch && req.method === "GET") {
1130
+ serveSessionFile(res, sessionMatch[1], sessionMatch[2]);
1131
+ return;
1132
+ }
1133
+
1134
+ // RPC proxy — handle via WebSocket command handler
1135
+ if (urlPath === "/api/rpc" && req.method === "POST") {
1136
+ let body = "";
1137
+ req.on("data", (chunk: Buffer) => {
1138
+ body += chunk.toString();
1139
+ });
1140
+ req.on("end", async () => {
1141
+ try {
1142
+ const command = JSON.parse(body) as BrowserCommand;
1143
+ // Create a fake WebSocket-like object to capture the response
1144
+ const responsePromise = new Promise<unknown>((resolve) => {
1145
+ const fakeWs: WritableSocket = {
1146
+ readyState: WebSocket.OPEN,
1147
+ send: (data: string | Buffer | ArrayBuffer | Buffer[]) => resolve(JSON.parse(data.toString())),
1148
+ };
1149
+ handleCommand(fakeWs, command);
1150
+ });
1151
+ const response = await responsePromise;
1152
+ res.writeHead(200, { "Content-Type": "application/json" });
1153
+ res.end(JSON.stringify(response));
1154
+ } catch (e: unknown) {
1155
+ const message = e instanceof Error ? e.message : String(e);
1156
+ res.writeHead(400, { "Content-Type": "application/json" });
1157
+ res.end(JSON.stringify({ error: message }));
1158
+ }
1159
+ });
1160
+ return;
1161
+ }
1162
+
1163
+ // Session switch — in mirror mode, this is a no-op (session is controlled by TUI)
1164
+ if (urlPath === "/api/sessions/switch" && req.method === "POST") {
1165
+ res.writeHead(200, { "Content-Type": "application/json" });
1166
+ res.end(
1167
+ JSON.stringify({
1168
+ success: true,
1169
+ mirror: true,
1170
+ note: "Session switching is controlled by the TUI in mirror mode",
1171
+ }),
1172
+ );
1173
+ return;
1174
+ }
1175
+
1176
+ // Memoryd check
1177
+ res.writeHead(404, { "Content-Type": "application/json" });
1178
+ res.end(JSON.stringify({ error: "Not found" }));
1179
+ }
1180
+
1181
+ // ═══════════════════════════════════════
1182
+ // Sessions list endpoint
1183
+ // ═══════════════════════════════════════
1184
+ function getTmuxSessionFiles(): Set<string> {
1185
+ try {
1186
+ const { execSync } = require("node:child_process");
1187
+ // Get tmux pane PIDs
1188
+ const paneOutput = execSync("tmux list-panes -a -F '#{pane_pid}' 2>/dev/null", {
1189
+ encoding: "utf8",
1190
+ });
1191
+ const tmuxFiles = new Set<string>();
1192
+
1193
+ for (const shellPid of paneOutput.trim().split("\n").filter(Boolean)) {
1194
+ try {
1195
+ // Find Pi (node) processes that are children of tmux shells
1196
+ const children = execSync(`pgrep -P ${shellPid} 2>/dev/null`, {
1197
+ encoding: "utf8",
1198
+ });
1199
+ for (const pid of children.trim().split("\n").filter(Boolean)) {
1200
+ // Check what .jsonl files this process has open
1201
+ const lsofOut = execSync(`lsof -p ${pid} 2>/dev/null | grep '\\.jsonl'`, {
1202
+ encoding: "utf8",
1203
+ });
1204
+ for (const line of lsofOut.trim().split("\n").filter(Boolean)) {
1205
+ const match = line.match(/\/.+\.jsonl$/);
1206
+ if (match) tmuxFiles.add(match[0]);
1207
+ }
1208
+ }
1209
+ } catch {
1210
+ /* no match */
1211
+ }
1212
+ }
1213
+ return tmuxFiles;
1214
+ } catch {
1215
+ return new Set();
1216
+ }
1217
+ }
1218
+
1219
+ function serveProjectsList(res: http.ServerResponse) {
1220
+ const projectsDir = process.env.PI_WEB_UI_PROJECTS_DIR;
1221
+ if (!projectsDir) {
1222
+ res.writeHead(200, { "Content-Type": "application/json" });
1223
+ res.end(JSON.stringify({ projects: [] }));
1224
+ return;
1225
+ }
1226
+
1227
+ const resolved = projectsDir.startsWith("~")
1228
+ ? path.join(process.env.HOME || "", projectsDir.slice(1))
1229
+ : projectsDir;
1230
+
1231
+ if (!fs.existsSync(resolved)) {
1232
+ res.writeHead(200, { "Content-Type": "application/json" });
1233
+ res.end(JSON.stringify({ projects: [], error: "Directory not found" }));
1234
+ return;
1235
+ }
1236
+
1237
+ try {
1238
+ const entries = fs.readdirSync(resolved, { withFileTypes: true });
1239
+ // Build session count + recency map from session history
1240
+ const sessionInfo = new Map<string, { count: number; lastActive: number }>();
1241
+ if (fs.existsSync(SESSIONS_DIR)) {
1242
+ for (const dir of fs.readdirSync(SESSIONS_DIR, {
1243
+ withFileTypes: true,
1244
+ })) {
1245
+ if (!dir.isDirectory()) continue;
1246
+ const decodedPath = dir.name.replace(/^--/, "/").replace(/--$/, "").replace(/-/g, "/");
1247
+ // Check if this session dir maps to a subdirectory of the projects folder
1248
+ if (!decodedPath.startsWith(`${resolved}/`) && !decodedPath.startsWith(resolved)) continue;
1249
+
1250
+ const sessionDir = path.join(SESSIONS_DIR, dir.name);
1251
+ const files = fs.readdirSync(sessionDir).filter((f) => f.endsWith(".jsonl"));
1252
+ let lastMtime = 0;
1253
+ for (const f of files) {
1254
+ try {
1255
+ const stat = fs.statSync(path.join(sessionDir, f));
1256
+ if (stat.mtimeMs > lastMtime) lastMtime = stat.mtimeMs;
1257
+ } catch {}
1258
+ }
1259
+ sessionInfo.set(decodedPath, {
1260
+ count: files.length,
1261
+ lastActive: lastMtime,
1262
+ });
1263
+ }
1264
+ }
1265
+
1266
+ const projects = entries
1267
+ .filter((e) => e.isDirectory() && !e.name.startsWith("."))
1268
+ .map((e) => {
1269
+ const fullPath = path.join(resolved, e.name);
1270
+ const info = sessionInfo.get(fullPath) || { count: 0, lastActive: 0 };
1271
+ return {
1272
+ name: e.name,
1273
+ path: fullPath,
1274
+ sessionCount: info.count,
1275
+ lastActive: info.lastActive || null,
1276
+ };
1277
+ });
1278
+
1279
+ res.writeHead(200, { "Content-Type": "application/json" });
1280
+ res.end(JSON.stringify({ projects }));
1281
+ } catch (e: unknown) {
1282
+ const message = e instanceof Error ? e.message : String(e);
1283
+ res.writeHead(500, { "Content-Type": "application/json" });
1284
+ res.end(JSON.stringify({ error: message }));
1285
+ }
1286
+ }
1287
+
1288
+ async function serveSessionsList(res: http.ServerResponse) {
1289
+ try {
1290
+ if (!fs.existsSync(SESSIONS_DIR)) {
1291
+ res.writeHead(200, { "Content-Type": "application/json" });
1292
+ res.end(JSON.stringify({ projects: [] }));
1293
+ return;
1294
+ }
1295
+
1296
+ const tmuxFiles = getTmuxSessionFiles();
1297
+ const readline = await import("node:readline");
1298
+ const dirEntries = CUSTOM_SESSIONS_DIR
1299
+ ? [{ name: CUSTOM_SESSIONS_GROUP, isDirectory: () => true }]
1300
+ : fs.readdirSync(SESSIONS_DIR, { withFileTypes: true });
1301
+ const projects: ProjectSessionGroup[] = [];
1302
+
1303
+ for (const dir of dirEntries) {
1304
+ if (!dir.isDirectory()) continue;
1305
+
1306
+ const projectDir = CUSTOM_SESSIONS_DIR || path.join(SESSIONS_DIR, dir.name);
1307
+ const files = fs.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl"));
1308
+ const decodedPath = CUSTOM_SESSIONS_DIR
1309
+ ? CUSTOM_SESSIONS_DIR
1310
+ : dir.name.replace(/^--/, "/").replace(/--$/, "").replace(/-/g, "/");
1311
+
1312
+ const sessions: SessionListItem[] = [];
1313
+
1314
+ for (const file of files) {
1315
+ try {
1316
+ const filePath = path.join(projectDir, file);
1317
+ const parsed = await parseSessionFile(filePath, readline);
1318
+ if (parsed) {
1319
+ const stat = fs.statSync(filePath);
1320
+ const isTmux = tmuxFiles.has(filePath);
1321
+ sessions.push({
1322
+ ...parsed,
1323
+ file,
1324
+ filePath,
1325
+ projectPath: decodedPath,
1326
+ mtime: stat.mtimeMs,
1327
+ ...(isTmux && { tmux: true }),
1328
+ });
1329
+ }
1330
+ } catch {
1331
+ /* skip */
1332
+ }
1333
+ }
1334
+
1335
+ sessions.sort((a, b) => b.mtime - a.mtime);
1336
+
1337
+ if (sessions.length > 0) {
1338
+ projects.push({ path: decodedPath, dirName: dir.name, sessions });
1339
+ }
1340
+ }
1341
+
1342
+ projects.sort((a, b) => {
1343
+ const aTime = a.sessions[0]?.mtime || 0;
1344
+ const bTime = b.sessions[0]?.mtime || 0;
1345
+ return bTime - aTime;
1346
+ });
1347
+
1348
+ res.writeHead(200, { "Content-Type": "application/json" });
1349
+ res.end(JSON.stringify({ projects }));
1350
+ } catch (e: unknown) {
1351
+ const message = e instanceof Error ? e.message : String(e);
1352
+ res.writeHead(500, { "Content-Type": "application/json" });
1353
+ res.end(JSON.stringify({ error: message }));
1354
+ }
1355
+ }
1356
+
1357
+ // ═══════════════════════════════════════
1358
+ // Session file endpoint
1359
+ // ═══════════════════════════════════════
1360
+ function serveSessionFile(res: http.ServerResponse, dirName: string, file: string) {
1361
+ const filePath =
1362
+ CUSTOM_SESSIONS_DIR && dirName === CUSTOM_SESSIONS_GROUP
1363
+ ? path.join(CUSTOM_SESSIONS_DIR, file)
1364
+ : path.join(SESSIONS_DIR, dirName, file);
1365
+
1366
+ if (!fs.existsSync(filePath)) {
1367
+ res.writeHead(404, { "Content-Type": "application/json" });
1368
+ res.end(JSON.stringify({ error: "Session not found" }));
1369
+ return;
1370
+ }
1371
+
1372
+ const entries: unknown[] = [];
1373
+ const stream = fs.createReadStream(filePath, { encoding: "utf8" });
1374
+ let buffer = "";
1375
+
1376
+ stream.on("data", (chunk: string) => {
1377
+ buffer += chunk;
1378
+ const lines = buffer.split("\n");
1379
+ buffer = lines.pop() || "";
1380
+ for (const line of lines) {
1381
+ if (line.trim()) {
1382
+ try {
1383
+ entries.push(JSON.parse(line));
1384
+ } catch {
1385
+ /* skip */
1386
+ }
1387
+ }
1388
+ }
1389
+ });
1390
+
1391
+ stream.on("end", () => {
1392
+ if (buffer.trim()) {
1393
+ try {
1394
+ entries.push(JSON.parse(buffer));
1395
+ } catch {
1396
+ /* skip */
1397
+ }
1398
+ }
1399
+ res.writeHead(200, { "Content-Type": "application/json" });
1400
+ res.end(JSON.stringify({ entries }));
1401
+ });
1402
+
1403
+ stream.on("error", (e: Error) => {
1404
+ res.writeHead(500, { "Content-Type": "application/json" });
1405
+ res.end(JSON.stringify({ error: e.message }));
1406
+ });
1407
+ }
1408
+
1409
+ // ═══════════════════════════════════════
1410
+ // Parse session file header
1411
+ // ═══════════════════════════════════════
1412
+ async function parseSessionFile(
1413
+ filePath: string,
1414
+ readline: typeof import("node:readline"),
1415
+ ): Promise<ParsedSession | null> {
1416
+ const stream = fs.createReadStream(filePath, { encoding: "utf8" });
1417
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
1418
+
1419
+ let header: SessionEntry | null = null;
1420
+ let firstMessage: string | null = null;
1421
+ let sessionName: string | null = null;
1422
+ let userMessageCount = 0;
1423
+ let lineCount = 0;
1424
+
1425
+ for await (const line of rl) {
1426
+ if (!line.trim()) continue;
1427
+ lineCount++;
1428
+
1429
+ try {
1430
+ const entry = JSON.parse(line) as SessionEntry;
1431
+ if (entry.type === "session") header = entry;
1432
+ else if (entry.type === "session_info" && entry.name) sessionName = entry.name;
1433
+ else if (entry.type === "message" && entry.message?.role === "user") {
1434
+ userMessageCount++;
1435
+ if (!firstMessage) {
1436
+ const content = entry.message.content;
1437
+ if (typeof content === "string") firstMessage = content.substring(0, 120);
1438
+ else if (Array.isArray(content)) {
1439
+ const tb = content.find(
1440
+ (b): b is TextContentBlock =>
1441
+ typeof b === "object" &&
1442
+ b !== null &&
1443
+ (b as { type?: unknown }).type === "text" &&
1444
+ typeof (b as { text?: unknown }).text === "string",
1445
+ );
1446
+ if (tb) firstMessage = tb.text.substring(0, 120);
1447
+ }
1448
+ }
1449
+ }
1450
+ } catch {
1451
+ /* skip */
1452
+ }
1453
+
1454
+ if (lineCount > 50 && firstMessage) break;
1455
+ }
1456
+
1457
+ rl.close();
1458
+ stream.destroy();
1459
+
1460
+ if (!header?.id) return null;
1461
+ if (userMessageCount <= 1 && lineCount <= 8) return null; // pipe mode
1462
+
1463
+ return {
1464
+ id: header.id,
1465
+ timestamp: header.timestamp || "",
1466
+ name: sessionName,
1467
+ firstMessage,
1468
+ cwd: header.cwd || null,
1469
+ };
1470
+ }
1471
+
1472
+ // ═══════════════════════════════════════
1473
+ // File browser
1474
+ // ═══════════════════════════════════════
1475
+
1476
+ const IGNORED_NAMES = new Set([
1477
+ "node_modules",
1478
+ ".git",
1479
+ "__pycache__",
1480
+ ".DS_Store",
1481
+ ".Trash",
1482
+ ".next",
1483
+ ".nuxt",
1484
+ "dist",
1485
+ "build",
1486
+ ".cache",
1487
+ ".turbo",
1488
+ "venv",
1489
+ ".venv",
1490
+ "env",
1491
+ ".env.local",
1492
+ ".pi",
1493
+ "coverage",
1494
+ ".nyc_output",
1495
+ ".parcel-cache",
1496
+ ]);
1497
+
1498
+ function serveFileList(res: http.ServerResponse, dirPath: string) {
1499
+ try {
1500
+ if (!fs.existsSync(dirPath) || !fs.statSync(dirPath).isDirectory()) {
1501
+ res.writeHead(400, { "Content-Type": "application/json" });
1502
+ res.end(JSON.stringify({ error: "Not a directory" }));
1503
+ return;
1504
+ }
1505
+
1506
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
1507
+ const items: FileListItem[] = [];
1508
+
1509
+ for (const entry of entries) {
1510
+ if (entry.name.startsWith(".") && entry.name !== ".env") continue;
1511
+ if (IGNORED_NAMES.has(entry.name)) continue;
1512
+
1513
+ try {
1514
+ const fullPath = path.join(dirPath, entry.name);
1515
+ const stat = fs.statSync(fullPath);
1516
+
1517
+ items.push({
1518
+ name: entry.name,
1519
+ path: fullPath,
1520
+ isDirectory: entry.isDirectory(),
1521
+ size: entry.isDirectory() ? null : stat.size,
1522
+ mtime: stat.mtimeMs,
1523
+ });
1524
+ } catch {
1525
+ /* skip inaccessible */
1526
+ }
1527
+ }
1528
+
1529
+ // Directories first, then files, both alphabetical
1530
+ items.sort((a, b) => {
1531
+ if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;
1532
+ return a.name.localeCompare(b.name);
1533
+ });
1534
+
1535
+ res.writeHead(200, { "Content-Type": "application/json" });
1536
+ res.end(JSON.stringify({ path: dirPath, items }));
1537
+ } catch (err: unknown) {
1538
+ const message = err instanceof Error ? err.message : String(err);
1539
+ res.writeHead(500, { "Content-Type": "application/json" });
1540
+ res.end(JSON.stringify({ error: message }));
1541
+ }
1542
+ }
1543
+
1544
+ // ═══════════════════════════════════════
1545
+ // Full-text search
1546
+ // ═══════════════════════════════════════
1547
+
1548
+ async function serveSearch(res: http.ServerResponse, query: string) {
1549
+ try {
1550
+ if (!query || query.length < 2) {
1551
+ res.writeHead(200, { "Content-Type": "application/json" });
1552
+ res.end(JSON.stringify({ results: [] }));
1553
+ return;
1554
+ }
1555
+
1556
+ const q = query.toLowerCase();
1557
+ const readline = await import("node:readline");
1558
+ const results: SearchResult[] = [];
1559
+ const MAX_RESULTS = 30;
1560
+
1561
+ if (!fs.existsSync(SESSIONS_DIR)) {
1562
+ res.writeHead(200, { "Content-Type": "application/json" });
1563
+ res.end(JSON.stringify({ results: [] }));
1564
+ return;
1565
+ }
1566
+
1567
+ const dirEntries = CUSTOM_SESSIONS_DIR
1568
+ ? [{ name: CUSTOM_SESSIONS_GROUP, isDirectory: () => true }]
1569
+ : fs.readdirSync(SESSIONS_DIR, { withFileTypes: true });
1570
+
1571
+ for (const dir of dirEntries) {
1572
+ if (!dir.isDirectory()) continue;
1573
+ if (results.length >= MAX_RESULTS) break;
1574
+
1575
+ const projectDir = CUSTOM_SESSIONS_DIR || path.join(SESSIONS_DIR, dir.name);
1576
+ const decodedPath = CUSTOM_SESSIONS_DIR
1577
+ ? CUSTOM_SESSIONS_DIR
1578
+ : dir.name.replace(/^--/, "/").replace(/--$/, "").replace(/-/g, "/");
1579
+ const files = fs.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl"));
1580
+
1581
+ for (const file of files) {
1582
+ if (results.length >= MAX_RESULTS) break;
1583
+
1584
+ try {
1585
+ const filePath = path.join(projectDir, file);
1586
+ const stream = fs.createReadStream(filePath, { encoding: "utf8" });
1587
+ const rl = readline.createInterface({
1588
+ input: stream,
1589
+ crlfDelay: Infinity,
1590
+ });
1591
+
1592
+ let sessionId = "";
1593
+ let sessionName = "";
1594
+ let sessionTimestamp = "";
1595
+ let firstMessage = "";
1596
+ const matches: SearchMatch[] = [];
1597
+
1598
+ for await (const line of rl) {
1599
+ if (!line.trim()) continue;
1600
+ try {
1601
+ const entry = JSON.parse(line) as SessionEntry;
1602
+
1603
+ if (entry.type === "session") {
1604
+ sessionId = entry.id;
1605
+ sessionTimestamp = entry.timestamp || "";
1606
+ }
1607
+ if (entry.type === "session_info" && entry.name) {
1608
+ sessionName = entry.name;
1609
+ }
1610
+ if (entry.type === "message") {
1611
+ const content = entry.message?.content;
1612
+ let text = "";
1613
+ if (typeof content === "string") text = content;
1614
+ else if (Array.isArray(content)) {
1615
+ text = content
1616
+ .filter(
1617
+ (b): b is TextContentBlock =>
1618
+ typeof b === "object" &&
1619
+ b !== null &&
1620
+ (b as { type?: unknown }).type === "text" &&
1621
+ typeof (b as { text?: unknown }).text === "string",
1622
+ )
1623
+ .map((b) => b.text)
1624
+ .join(" ");
1625
+ }
1626
+
1627
+ if (!firstMessage && entry.message?.role === "user" && text) {
1628
+ firstMessage = text.substring(0, 120);
1629
+ }
1630
+
1631
+ if (text?.toLowerCase().includes(q)) {
1632
+ // Extract a snippet around the match
1633
+ const idx = text.toLowerCase().indexOf(q);
1634
+ const start = Math.max(0, idx - 60);
1635
+ const end = Math.min(text.length, idx + q.length + 60);
1636
+ const snippet =
1637
+ (start > 0 ? "…" : "") + text.substring(start, end) + (end < text.length ? "…" : "");
1638
+
1639
+ matches.push({
1640
+ role: entry.message?.role || "unknown",
1641
+ snippet: snippet.replace(/\n/g, " "),
1642
+ });
1643
+
1644
+ if (matches.length >= 3) break; // max 3 matches per session
1645
+ }
1646
+ }
1647
+ } catch {
1648
+ /* skip line */
1649
+ }
1650
+ }
1651
+
1652
+ rl.close();
1653
+ stream.destroy();
1654
+
1655
+ if (matches.length > 0) {
1656
+ results.push({
1657
+ filePath,
1658
+ project: decodedPath,
1659
+ sessionId,
1660
+ sessionName,
1661
+ sessionTimestamp,
1662
+ firstMessage,
1663
+ matches,
1664
+ });
1665
+ }
1666
+ } catch {
1667
+ /* skip file */
1668
+ }
1669
+ }
1670
+ }
1671
+
1672
+ res.writeHead(200, { "Content-Type": "application/json" });
1673
+ res.end(JSON.stringify({ results }));
1674
+ } catch (err: unknown) {
1675
+ const message = err instanceof Error ? err.message : String(err);
1676
+ res.writeHead(500, { "Content-Type": "application/json" });
1677
+ res.end(JSON.stringify({ error: message }));
1678
+ }
1679
+ }
1680
+
1681
+ // ═══════════════════════════════════════
1682
+ // Start server function (reusable)
1683
+ // ═══════════════════════════════════════
1684
+ function startServer() {
1685
+ if (server) return; // Already running
1686
+
1687
+ server = http.createServer(serveStaticFile);
1688
+ wss = new WebSocketServer({ noServer: true });
1689
+
1690
+ server.on("upgrade", (request, socket, head) => {
1691
+ if (request.url === "/ws") {
1692
+ const activeWss = wss;
1693
+ if (!activeWss) {
1694
+ socket.destroy();
1695
+ return;
1696
+ }
1697
+ activeWss.handleUpgrade(request, socket, head, (ws) => {
1698
+ activeWss.emit("connection", ws, request);
1699
+ });
1700
+ } else {
1701
+ socket.destroy();
1702
+ }
1703
+ });
1704
+
1705
+ wss.on("connection", (ws) => {
1706
+ clients.add(ws);
1707
+ updateMirrorStatus();
1708
+ const aliveWs = ws as AliveWebSocket;
1709
+ aliveWs.isAlive = true;
1710
+
1711
+ ws.on("pong", () => {
1712
+ aliveWs.isAlive = true;
1713
+ });
1714
+
1715
+ // Send initial state
1716
+ sendTo(ws, { type: "state", advancedFeatures: !!latestNavigateTree });
1717
+
1718
+ // Immediately send state snapshot
1719
+ if (latestCtx) {
1720
+ buildStateSnapshot(latestCtx).then((snapshot) => {
1721
+ sendTo(ws, snapshot);
1722
+ });
1723
+ }
1724
+
1725
+ ws.on("message", (data) => {
1726
+ try {
1727
+ const command = JSON.parse(data.toString());
1728
+ handleCommand(ws, command);
1729
+ } catch (_e) {
1730
+ sendTo(ws, { type: "error", message: "Invalid client message" });
1731
+ }
1732
+ });
1733
+
1734
+ ws.on("close", () => {
1735
+ clients.delete(ws);
1736
+ updateMirrorStatus();
1737
+ });
1738
+
1739
+ ws.on("error", () => {
1740
+ clients.delete(ws);
1741
+ updateMirrorStatus();
1742
+ });
1743
+ });
1744
+
1745
+ // Heartbeat keeps mobile/Tailscale sessions alive and removes stale clients.
1746
+ heartbeatTimer = setInterval(() => {
1747
+ let changed = false;
1748
+ for (const client of clients) {
1749
+ if (client.readyState !== WebSocket.OPEN) {
1750
+ clients.delete(client);
1751
+ changed = true;
1752
+ continue;
1753
+ }
1754
+
1755
+ const aliveClient = client as AliveWebSocket;
1756
+ if (!aliveClient.isAlive) {
1757
+ try {
1758
+ client.terminate();
1759
+ } catch {}
1760
+ clients.delete(client);
1761
+ changed = true;
1762
+ continue;
1763
+ }
1764
+
1765
+ aliveClient.isAlive = false;
1766
+ try {
1767
+ client.ping();
1768
+ } catch {}
1769
+ }
1770
+ if (changed) updateMirrorStatus();
1771
+ }, 20000);
1772
+
1773
+ const tryListen = (port: number, maxAttempts = 10) => {
1774
+ const activeServer = server;
1775
+ if (!activeServer) return;
1776
+ activeServer.listen(port, HOST, () => {
1777
+ onListening(port);
1778
+ });
1779
+ activeServer.once("error", (err: NodeError) => {
1780
+ if (err.code === "EADDRINUSE" && port < PORT + maxAttempts) {
1781
+ latestCtx?.ui.setStatus("webui", `pi-web-ui: trying port ${port + 1}`);
1782
+ activeServer.removeAllListeners("error");
1783
+ tryListen(port + 1, maxAttempts);
1784
+ } else {
1785
+ latestCtx?.ui.setStatus("webui", "");
1786
+ latestCtx?.ui.notify(`pi-web-ui failed to start: ${err.message}`, "error");
1787
+ stopServer();
1788
+ }
1789
+ });
1790
+ };
1791
+
1792
+ const onListening = (port: number) => {
1793
+ mirrorUrl = `http://${HOST}:${port}`;
1794
+ mirrorStatusBase = `pi-web-ui: ${HOST}:${port}`;
1795
+ updateMirrorStatus();
1796
+
1797
+ latestCtx?.ui.notify(`pi-web-ui: ${mirrorUrl}`, "info");
1798
+ };
1799
+
1800
+ tryListen(PORT);
1801
+ }
1802
+
1803
+ // ═══════════════════════════════════════
1804
+ // Auto-start on session begin
1805
+ // ═══════════════════════════════════════
1806
+ pi.on("session_start", async (_event, ctx) => {
1807
+ latestCtx = ctx;
1808
+
1809
+ if (!AUTO_START) {
1810
+ return;
1811
+ }
1812
+
1813
+ startServer();
1814
+ });
1815
+
1816
+ // ═══════════════════════════════════════
1817
+ // Cleanup on shutdown
1818
+ // ═══════════════════════════════════════
1819
+ pi.on("session_shutdown", async () => {
1820
+ latestCtx = null;
1821
+ stopServer();
1822
+ });
1823
+ }