mexus-cli 1.0.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 (361) hide show
  1. package/README.md +114 -0
  2. package/package.json +45 -0
  3. package/packages/server/dist/cli.mjs +3186 -0
  4. package/packages/server/dist/cli.mjs.map +1 -0
  5. package/packages/web/dist/assets/_basePickBy-B0zzcSaC.js +1 -0
  6. package/packages/web/dist/assets/_baseUniq-B-4_EE7n.js +1 -0
  7. package/packages/web/dist/assets/abap-BdImnpbu.js +1 -0
  8. package/packages/web/dist/assets/actionscript-3-CoDkCxhg.js +1 -0
  9. package/packages/web/dist/assets/ada-bCR0ucgS.js +1 -0
  10. package/packages/web/dist/assets/andromeeda-C4gqWexZ.js +1 -0
  11. package/packages/web/dist/assets/angular-html-CU67Zn6k.js +1 -0
  12. package/packages/web/dist/assets/angular-ts-BwZT4LLn.js +1 -0
  13. package/packages/web/dist/assets/apache-Pmp26Uib.js +1 -0
  14. package/packages/web/dist/assets/apex-D8_7TLub.js +1 -0
  15. package/packages/web/dist/assets/apl-dKokRX4l.js +1 -0
  16. package/packages/web/dist/assets/applescript-Co6uUVPk.js +1 -0
  17. package/packages/web/dist/assets/ara-BRHolxvo.js +1 -0
  18. package/packages/web/dist/assets/arc-DFOgsfTK.js +1 -0
  19. package/packages/web/dist/assets/architectureDiagram-2XIMDMQ5-CNksmKJ-.js +36 -0
  20. package/packages/web/dist/assets/asciidoc-Ve4PFQV2.js +1 -0
  21. package/packages/web/dist/assets/asm-D_Q5rh1f.js +1 -0
  22. package/packages/web/dist/assets/astro-CbQHKStN.js +1 -0
  23. package/packages/web/dist/assets/aurora-x-D-2ljcwZ.js +1 -0
  24. package/packages/web/dist/assets/awk-DMzUqQB5.js +1 -0
  25. package/packages/web/dist/assets/ayu-dark-DYE7WIF3.js +1 -0
  26. package/packages/web/dist/assets/ayu-light-BA47KaF1.js +1 -0
  27. package/packages/web/dist/assets/ayu-mirage-32ctXXKs.js +1 -0
  28. package/packages/web/dist/assets/ballerina-BFfxhgS-.js +1 -0
  29. package/packages/web/dist/assets/bat-BkioyH1T.js +1 -0
  30. package/packages/web/dist/assets/beancount-k_qm7-4y.js +1 -0
  31. package/packages/web/dist/assets/berry-uYugtg8r.js +1 -0
  32. package/packages/web/dist/assets/bibtex-CHM0blh-.js +1 -0
  33. package/packages/web/dist/assets/bicep-Bmn6On1c.js +1 -0
  34. package/packages/web/dist/assets/bird2-D-8n_ahI.js +1 -0
  35. package/packages/web/dist/assets/blade-D4QpJJKB.js +1 -0
  36. package/packages/web/dist/assets/blockDiagram-WCTKOSBZ-B1Tw0xWQ.js +132 -0
  37. package/packages/web/dist/assets/bsl-BO_Y6i37.js +1 -0
  38. package/packages/web/dist/assets/c-BIGW1oBm.js +1 -0
  39. package/packages/web/dist/assets/c3-eo99z4R2.js +1 -0
  40. package/packages/web/dist/assets/c4Diagram-IC4MRINW-BC3Xs_CM.js +10 -0
  41. package/packages/web/dist/assets/cadence-Bv_4Rxtq.js +1 -0
  42. package/packages/web/dist/assets/cairo-KRGpt6FW.js +1 -0
  43. package/packages/web/dist/assets/catppuccin-frappe-DFWUc33u.js +1 -0
  44. package/packages/web/dist/assets/catppuccin-latte-C9dUb6Cb.js +1 -0
  45. package/packages/web/dist/assets/catppuccin-macchiato-DQyhUUbL.js +1 -0
  46. package/packages/web/dist/assets/catppuccin-mocha-D87Tk5Gz.js +1 -0
  47. package/packages/web/dist/assets/channel-BolzzYH1.js +1 -0
  48. package/packages/web/dist/assets/chunk-4BX2VUAB-Bc_dSZCa.js +1 -0
  49. package/packages/web/dist/assets/chunk-55IACEB6-BkpubxQa.js +1 -0
  50. package/packages/web/dist/assets/chunk-FMBD7UC4-DfsMHPhA.js +15 -0
  51. package/packages/web/dist/assets/chunk-JSJVCQXG-DQnDNPz7.js +1 -0
  52. package/packages/web/dist/assets/chunk-KX2RTZJC-BoujkdYL.js +1 -0
  53. package/packages/web/dist/assets/chunk-NQ4KR5QH-BI0GEUMc.js +220 -0
  54. package/packages/web/dist/assets/chunk-QZHKN3VN-Dq4w7tDq.js +1 -0
  55. package/packages/web/dist/assets/chunk-WL4C6EOR-DSQ5ZWfk.js +189 -0
  56. package/packages/web/dist/assets/clarity-D53aC0YG.js +1 -0
  57. package/packages/web/dist/assets/classDiagram-VBA2DB6C-DXgRkMh7.js +1 -0
  58. package/packages/web/dist/assets/classDiagram-v2-RAHNMMFH-DXgRkMh7.js +1 -0
  59. package/packages/web/dist/assets/clojure-P80f7IUj.js +1 -0
  60. package/packages/web/dist/assets/clone-BD_2Dvk1.js +1 -0
  61. package/packages/web/dist/assets/cmake-D1j8_8rp.js +1 -0
  62. package/packages/web/dist/assets/cobol-CELfDnG_.js +1 -0
  63. package/packages/web/dist/assets/codeowners-Bp6g37R7.js +1 -0
  64. package/packages/web/dist/assets/codeql-DsOJ9woJ.js +1 -0
  65. package/packages/web/dist/assets/coffee-Ch7k5sss.js +1 -0
  66. package/packages/web/dist/assets/common-lisp-Cg-RD9OK.js +1 -0
  67. package/packages/web/dist/assets/coq-DkFqJrB1.js +1 -0
  68. package/packages/web/dist/assets/cose-bilkent-S5V4N54A-Caq6VRnC.js +1 -0
  69. package/packages/web/dist/assets/cpp-CofmeUqb.js +1 -0
  70. package/packages/web/dist/assets/crystal-tKQVLTB8.js +1 -0
  71. package/packages/web/dist/assets/csharp-COcwbKMJ.js +1 -0
  72. package/packages/web/dist/assets/css-DPfMkruS.js +1 -0
  73. package/packages/web/dist/assets/csv-fuZLfV_i.js +1 -0
  74. package/packages/web/dist/assets/cue-D82EKSYY.js +1 -0
  75. package/packages/web/dist/assets/cypher-COkxafJQ.js +1 -0
  76. package/packages/web/dist/assets/cytoscape.esm-BQaXIfA_.js +331 -0
  77. package/packages/web/dist/assets/d-85-TOEBH.js +1 -0
  78. package/packages/web/dist/assets/dagre-KLK3FWXG-D1rt0jGa.js +4 -0
  79. package/packages/web/dist/assets/dark-plus-C3mMm8J8.js +1 -0
  80. package/packages/web/dist/assets/dart-CF10PKvl.js +1 -0
  81. package/packages/web/dist/assets/dax-CEL-wOlO.js +1 -0
  82. package/packages/web/dist/assets/defaultLocale-DX6XiGOO.js +1 -0
  83. package/packages/web/dist/assets/desktop-BmXAJ9_W.js +1 -0
  84. package/packages/web/dist/assets/diagram-E7M64L7V-BBrZhLH7.js +24 -0
  85. package/packages/web/dist/assets/diagram-IFDJBPK2-BFSSudzv.js +43 -0
  86. package/packages/web/dist/assets/diagram-P4PSJMXO-C3oU7Ool.js +24 -0
  87. package/packages/web/dist/assets/diff-D97Zzqfu.js +1 -0
  88. package/packages/web/dist/assets/docker-BcOcwvcX.js +1 -0
  89. package/packages/web/dist/assets/dotenv-Da5cRb03.js +1 -0
  90. package/packages/web/dist/assets/dracula-BzJJZx-M.js +1 -0
  91. package/packages/web/dist/assets/dracula-soft-BXkSAIEj.js +1 -0
  92. package/packages/web/dist/assets/dream-maker-BtqSS_iP.js +1 -0
  93. package/packages/web/dist/assets/edge-BkV0erSs.js +1 -0
  94. package/packages/web/dist/assets/elixir-CDX3lj18.js +1 -0
  95. package/packages/web/dist/assets/elm-DbKCFpqz.js +1 -0
  96. package/packages/web/dist/assets/emacs-lisp-C9XAeP06.js +1 -0
  97. package/packages/web/dist/assets/erDiagram-INFDFZHY-D0cx2_2Y.js +70 -0
  98. package/packages/web/dist/assets/erb-B12qg9BL.js +1 -0
  99. package/packages/web/dist/assets/erlang-DsQrWhSR.js +1 -0
  100. package/packages/web/dist/assets/everforest-dark-BgDCqdQA.js +1 -0
  101. package/packages/web/dist/assets/everforest-light-C8M2exoo.js +1 -0
  102. package/packages/web/dist/assets/fennel-BYunw83y.js +1 -0
  103. package/packages/web/dist/assets/fish-BvzEVeQv.js +1 -0
  104. package/packages/web/dist/assets/flowDiagram-PKNHOUZH-BXvZGiWz.js +162 -0
  105. package/packages/web/dist/assets/fluent-C4IJs8-o.js +1 -0
  106. package/packages/web/dist/assets/fortran-fixed-form-CkoXwp7k.js +1 -0
  107. package/packages/web/dist/assets/fortran-free-form-BxgE0vQu.js +1 -0
  108. package/packages/web/dist/assets/fsharp-CXgrBDvD.js +1 -0
  109. package/packages/web/dist/assets/ganttDiagram-A5KZAMGK-C9JOODgY.js +292 -0
  110. package/packages/web/dist/assets/gdresource-BOOCDP_w.js +1 -0
  111. package/packages/web/dist/assets/gdscript-C5YyOfLZ.js +1 -0
  112. package/packages/web/dist/assets/gdshader-DkwncUOv.js +1 -0
  113. package/packages/web/dist/assets/genie-D0YGMca9.js +1 -0
  114. package/packages/web/dist/assets/gherkin-DyxjwDmM.js +1 -0
  115. package/packages/web/dist/assets/git-commit-F4YmCXRG.js +1 -0
  116. package/packages/web/dist/assets/git-rebase-r7XF79zn.js +1 -0
  117. package/packages/web/dist/assets/gitGraphDiagram-K3NZZRJ6-BiIWik3M.js +65 -0
  118. package/packages/web/dist/assets/github-dark-DHJKELXO.js +1 -0
  119. package/packages/web/dist/assets/github-dark-default-Cuk6v7N8.js +1 -0
  120. package/packages/web/dist/assets/github-dark-dimmed-DH5Ifo-i.js +1 -0
  121. package/packages/web/dist/assets/github-dark-high-contrast-E3gJ1_iC.js +1 -0
  122. package/packages/web/dist/assets/github-light-DAi9KRSo.js +1 -0
  123. package/packages/web/dist/assets/github-light-default-D7oLnXFd.js +1 -0
  124. package/packages/web/dist/assets/github-light-high-contrast-BfjtVDDH.js +1 -0
  125. package/packages/web/dist/assets/gleam-BspZqrRM.js +1 -0
  126. package/packages/web/dist/assets/glimmer-js-Rg0-pVw9.js +1 -0
  127. package/packages/web/dist/assets/glimmer-ts-U6CK756n.js +1 -0
  128. package/packages/web/dist/assets/glsl-DplSGwfg.js +1 -0
  129. package/packages/web/dist/assets/gn-n2N0HUVH.js +1 -0
  130. package/packages/web/dist/assets/gnuplot-DdkO51Og.js +1 -0
  131. package/packages/web/dist/assets/go-CxLEBnE3.js +1 -0
  132. package/packages/web/dist/assets/graph-KIQcHS1V.js +1 -0
  133. package/packages/web/dist/assets/graphql-ChdNCCLP.js +1 -0
  134. package/packages/web/dist/assets/groovy-gcz8RCvz.js +1 -0
  135. package/packages/web/dist/assets/gruvbox-dark-hard-CFHQjOhq.js +1 -0
  136. package/packages/web/dist/assets/gruvbox-dark-medium-GsRaNv29.js +1 -0
  137. package/packages/web/dist/assets/gruvbox-dark-soft-CVdnzihN.js +1 -0
  138. package/packages/web/dist/assets/gruvbox-light-hard-CH1njM8p.js +1 -0
  139. package/packages/web/dist/assets/gruvbox-light-medium-DRw_LuNl.js +1 -0
  140. package/packages/web/dist/assets/gruvbox-light-soft-hJgmCMqR.js +1 -0
  141. package/packages/web/dist/assets/hack-CaT9iCJl.js +1 -0
  142. package/packages/web/dist/assets/haml-B8DHNrY2.js +1 -0
  143. package/packages/web/dist/assets/handlebars-BL8al0AC.js +1 -0
  144. package/packages/web/dist/assets/haskell-Df6bDoY_.js +1 -0
  145. package/packages/web/dist/assets/haxe-CzTSHFRz.js +1 -0
  146. package/packages/web/dist/assets/hcl-BWvSN4gD.js +1 -0
  147. package/packages/web/dist/assets/hjson-D5-asLiD.js +1 -0
  148. package/packages/web/dist/assets/hlsl-D3lLCCz7.js +1 -0
  149. package/packages/web/dist/assets/horizon-BUw7H-hv.js +1 -0
  150. package/packages/web/dist/assets/horizon-bright-CUuTKBJd.js +1 -0
  151. package/packages/web/dist/assets/houston-DnULxvSX.js +1 -0
  152. package/packages/web/dist/assets/html-GMplVEZG.js +1 -0
  153. package/packages/web/dist/assets/html-derivative-BFtXZ54Q.js +1 -0
  154. package/packages/web/dist/assets/http-jrhK8wxY.js +1 -0
  155. package/packages/web/dist/assets/hurl-irOxFIW8.js +1 -0
  156. package/packages/web/dist/assets/hxml-Bvhsp5Yf.js +1 -0
  157. package/packages/web/dist/assets/hy-DFXneXwc.js +1 -0
  158. package/packages/web/dist/assets/imba-DGztddWO.js +1 -0
  159. package/packages/web/dist/assets/index-CMzYPOPG.js +850 -0
  160. package/packages/web/dist/assets/index-SBtQaWyn.css +32 -0
  161. package/packages/web/dist/assets/infoDiagram-LFFYTUFH-DV6kISOT.js +2 -0
  162. package/packages/web/dist/assets/ini-BEwlwnbL.js +1 -0
  163. package/packages/web/dist/assets/init-Gi6I4Gst.js +1 -0
  164. package/packages/web/dist/assets/ishikawaDiagram-PHBUUO56-Dko7qIR3.js +70 -0
  165. package/packages/web/dist/assets/java-CylS5w8V.js +1 -0
  166. package/packages/web/dist/assets/javascript-wDzz0qaB.js +1 -0
  167. package/packages/web/dist/assets/jinja-4LBKfQ-Z.js +1 -0
  168. package/packages/web/dist/assets/jison-wvAkD_A8.js +1 -0
  169. package/packages/web/dist/assets/journeyDiagram-4ABVD52K-_FLfsCtQ.js +139 -0
  170. package/packages/web/dist/assets/json-Cp-IABpG.js +1 -0
  171. package/packages/web/dist/assets/json5-C9tS-k6U.js +1 -0
  172. package/packages/web/dist/assets/jsonc-Des-eS-w.js +1 -0
  173. package/packages/web/dist/assets/jsonl-DcaNXYhu.js +1 -0
  174. package/packages/web/dist/assets/jsonnet-DFQXde-d.js +1 -0
  175. package/packages/web/dist/assets/jssm-C2t-YnRu.js +1 -0
  176. package/packages/web/dist/assets/jsx-g9-lgVsj.js +1 -0
  177. package/packages/web/dist/assets/julia-CxzCAyBv.js +1 -0
  178. package/packages/web/dist/assets/just-Cw27pwNe.js +1 -0
  179. package/packages/web/dist/assets/kanagawa-dragon-CkXjmgJE.js +1 -0
  180. package/packages/web/dist/assets/kanagawa-lotus-CfQXZHmo.js +1 -0
  181. package/packages/web/dist/assets/kanagawa-wave-DWedfzmr.js +1 -0
  182. package/packages/web/dist/assets/kanban-definition-K7BYSVSG-Bjf_Hkam.js +89 -0
  183. package/packages/web/dist/assets/katex-BkgLa5T_.js +261 -0
  184. package/packages/web/dist/assets/kdl-DV7GczEv.js +1 -0
  185. package/packages/web/dist/assets/kotlin-BdnUsdx6.js +1 -0
  186. package/packages/web/dist/assets/kusto-DZf3V79B.js +1 -0
  187. package/packages/web/dist/assets/laserwave-DUszq2jm.js +1 -0
  188. package/packages/web/dist/assets/latex-CWtU0Tv5.js +1 -0
  189. package/packages/web/dist/assets/layout-D09tLn9J.js +1 -0
  190. package/packages/web/dist/assets/lean-BZvkOJ9d.js +1 -0
  191. package/packages/web/dist/assets/less-B1dDrJ26.js +1 -0
  192. package/packages/web/dist/assets/light-plus-B7mTdjB0.js +1 -0
  193. package/packages/web/dist/assets/linear-BnF-DuHG.js +1 -0
  194. package/packages/web/dist/assets/liquid-DYVedYrR.js +1 -0
  195. package/packages/web/dist/assets/llvm-DjAJT7YJ.js +1 -0
  196. package/packages/web/dist/assets/log-2UxHyX5q.js +1 -0
  197. package/packages/web/dist/assets/logo-BtOb2qkB.js +1 -0
  198. package/packages/web/dist/assets/lua-BaeVxFsk.js +1 -0
  199. package/packages/web/dist/assets/luau-C-HG3fhB.js +1 -0
  200. package/packages/web/dist/assets/make-CHLpvVh8.js +1 -0
  201. package/packages/web/dist/assets/markdown-Cvjx9yec.js +1 -0
  202. package/packages/web/dist/assets/marko-CnJfTvn9.js +1 -0
  203. package/packages/web/dist/assets/material-theme-D5KoaKCx.js +1 -0
  204. package/packages/web/dist/assets/material-theme-darker-BfHTSMKl.js +1 -0
  205. package/packages/web/dist/assets/material-theme-lighter-B0m2ddpp.js +1 -0
  206. package/packages/web/dist/assets/material-theme-ocean-CyktbL80.js +1 -0
  207. package/packages/web/dist/assets/material-theme-palenight-Csfq5Kiy.js +1 -0
  208. package/packages/web/dist/assets/matlab-D7o27uSR.js +1 -0
  209. package/packages/web/dist/assets/mdc-BMNejdWA.js +1 -0
  210. package/packages/web/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  211. package/packages/web/dist/assets/mermaid-mWjccvbQ.js +1 -0
  212. package/packages/web/dist/assets/min-dark-CafNBF8u.js +1 -0
  213. package/packages/web/dist/assets/min-light-CTRr51gU.js +1 -0
  214. package/packages/web/dist/assets/mindmap-definition-YRQLILUH-C4ui5Uen.js +68 -0
  215. package/packages/web/dist/assets/mipsasm-CKIfxQSi.js +1 -0
  216. package/packages/web/dist/assets/mojo-rZm6bMo-.js +1 -0
  217. package/packages/web/dist/assets/monokai-D4h5O-jR.js +1 -0
  218. package/packages/web/dist/assets/moonbit-_H4v1dQx.js +1 -0
  219. package/packages/web/dist/assets/move-IF9eRakj.js +1 -0
  220. package/packages/web/dist/assets/narrat-DRg8JJMk.js +1 -0
  221. package/packages/web/dist/assets/nextflow-Zz6hmt5N.js +1 -0
  222. package/packages/web/dist/assets/nextflow-groovy-BeH2EWoN.js +1 -0
  223. package/packages/web/dist/assets/nginx-BpAMiNFr.js +1 -0
  224. package/packages/web/dist/assets/night-owl-C39BiMTA.js +1 -0
  225. package/packages/web/dist/assets/night-owl-light-CMTm3GFP.js +1 -0
  226. package/packages/web/dist/assets/nim-CVrawwO9.js +1 -0
  227. package/packages/web/dist/assets/nix-CwoSXNpI.js +1 -0
  228. package/packages/web/dist/assets/nord-Ddv68eIx.js +1 -0
  229. package/packages/web/dist/assets/nushell-Cz2AlsmD.js +1 -0
  230. package/packages/web/dist/assets/objective-c-DXmwc3jG.js +1 -0
  231. package/packages/web/dist/assets/objective-cpp-CLxacb5B.js +1 -0
  232. package/packages/web/dist/assets/ocaml-C0hk2d4L.js +1 -0
  233. package/packages/web/dist/assets/odin-BBf5iR-q.js +1 -0
  234. package/packages/web/dist/assets/one-dark-pro-DVMEJ2y_.js +1 -0
  235. package/packages/web/dist/assets/one-light-C3Wv6jpd.js +1 -0
  236. package/packages/web/dist/assets/openscad-C4EeE6gA.js +1 -0
  237. package/packages/web/dist/assets/ordinal-Cboi1Yqb.js +1 -0
  238. package/packages/web/dist/assets/pascal-D93ZcfNL.js +1 -0
  239. package/packages/web/dist/assets/perl-C0TMdlhV.js +1 -0
  240. package/packages/web/dist/assets/php-Dhbhpdrm.js +1 -0
  241. package/packages/web/dist/assets/pieDiagram-SKSYHLDU-Chjj2B6Z.js +30 -0
  242. package/packages/web/dist/assets/pkl-u5AG7uiY.js +1 -0
  243. package/packages/web/dist/assets/plastic-3e1v2bzS.js +1 -0
  244. package/packages/web/dist/assets/plsql-ChMvpjG-.js +1 -0
  245. package/packages/web/dist/assets/po-BTJTHyun.js +1 -0
  246. package/packages/web/dist/assets/poimandres-CS3Unz2-.js +1 -0
  247. package/packages/web/dist/assets/polar-C0HS_06l.js +1 -0
  248. package/packages/web/dist/assets/postcss-CXtECtnM.js +1 -0
  249. package/packages/web/dist/assets/powerquery-CEu0bR-o.js +1 -0
  250. package/packages/web/dist/assets/powershell-Dpen1YoG.js +1 -0
  251. package/packages/web/dist/assets/prisma-Dd19v3D-.js +1 -0
  252. package/packages/web/dist/assets/prolog-CbFg5uaA.js +1 -0
  253. package/packages/web/dist/assets/proto-C7zT0LnQ.js +1 -0
  254. package/packages/web/dist/assets/pug-CGlum2m_.js +1 -0
  255. package/packages/web/dist/assets/puppet-BMWR74SV.js +1 -0
  256. package/packages/web/dist/assets/purescript-CklMAg4u.js +1 -0
  257. package/packages/web/dist/assets/python-B6aJPvgy.js +1 -0
  258. package/packages/web/dist/assets/qml-3beO22l8.js +1 -0
  259. package/packages/web/dist/assets/qmldir-C8lEn-DE.js +1 -0
  260. package/packages/web/dist/assets/qss-IeuSbFQv.js +1 -0
  261. package/packages/web/dist/assets/quadrantDiagram-337W2JSQ-BgcnBVtA.js +7 -0
  262. package/packages/web/dist/assets/r-Dspwwk_N.js +1 -0
  263. package/packages/web/dist/assets/racket-BqYA7rlc.js +1 -0
  264. package/packages/web/dist/assets/raku-DXvB9xmW.js +1 -0
  265. package/packages/web/dist/assets/razor-Uh8Bk_45.js +1 -0
  266. package/packages/web/dist/assets/red-bN70gL4F.js +1 -0
  267. package/packages/web/dist/assets/reg-C-SQnVFl.js +1 -0
  268. package/packages/web/dist/assets/regexp-CDVJQ6XC.js +1 -0
  269. package/packages/web/dist/assets/rel-C3B-1QV4.js +1 -0
  270. package/packages/web/dist/assets/requirementDiagram-Z7DCOOCP-D8i7_HQl.js +73 -0
  271. package/packages/web/dist/assets/riscv-BM1_JUlF.js +1 -0
  272. package/packages/web/dist/assets/ron-D8l8udqQ.js +1 -0
  273. package/packages/web/dist/assets/rose-pine-dawn-DHQR4-dF.js +1 -0
  274. package/packages/web/dist/assets/rose-pine-moon-D4_iv3hh.js +1 -0
  275. package/packages/web/dist/assets/rose-pine-qdsjHGoJ.js +1 -0
  276. package/packages/web/dist/assets/rosmsg-BJDFO7_C.js +1 -0
  277. package/packages/web/dist/assets/rst-BrH8l1NY.js +1 -0
  278. package/packages/web/dist/assets/ruby-Dw2BHqvy.js +1 -0
  279. package/packages/web/dist/assets/rust-B1yitclQ.js +1 -0
  280. package/packages/web/dist/assets/sankeyDiagram-WA2Y5GQK-CE8F22pR.js +10 -0
  281. package/packages/web/dist/assets/sas-cz2c8ADy.js +1 -0
  282. package/packages/web/dist/assets/sass-Cj5Yp3dK.js +1 -0
  283. package/packages/web/dist/assets/scala-C151Ov-r.js +1 -0
  284. package/packages/web/dist/assets/scheme-C98Dy4si.js +1 -0
  285. package/packages/web/dist/assets/scss-OYdSNvt2.js +1 -0
  286. package/packages/web/dist/assets/sdbl-DVxCFoDh.js +1 -0
  287. package/packages/web/dist/assets/sequenceDiagram-2WXFIKYE-BIpm8kwX.js +145 -0
  288. package/packages/web/dist/assets/shaderlab-Dg9Lc6iA.js +1 -0
  289. package/packages/web/dist/assets/shellscript-Yzrsuije.js +1 -0
  290. package/packages/web/dist/assets/shellsession-BADoaaVG.js +1 -0
  291. package/packages/web/dist/assets/slack-dark-BthQWCQV.js +1 -0
  292. package/packages/web/dist/assets/slack-ochin-DqwNpetd.js +1 -0
  293. package/packages/web/dist/assets/smalltalk-BERRCDM3.js +1 -0
  294. package/packages/web/dist/assets/snazzy-light-Bw305WKR.js +1 -0
  295. package/packages/web/dist/assets/solarized-dark-DXbdFlpD.js +1 -0
  296. package/packages/web/dist/assets/solarized-light-L9t79GZl.js +1 -0
  297. package/packages/web/dist/assets/solidity-rGO070M0.js +1 -0
  298. package/packages/web/dist/assets/soy-Brmx7dQM.js +1 -0
  299. package/packages/web/dist/assets/sparql-rVzFXLq3.js +1 -0
  300. package/packages/web/dist/assets/splunk-BtCnVYZw.js +1 -0
  301. package/packages/web/dist/assets/sql-BLtJtn59.js +1 -0
  302. package/packages/web/dist/assets/ssh-config-_ykCGR6B.js +1 -0
  303. package/packages/web/dist/assets/stata-BH5u7GGu.js +1 -0
  304. package/packages/web/dist/assets/stateDiagram-RAJIS63D-lboAMp3Q.js +1 -0
  305. package/packages/web/dist/assets/stateDiagram-v2-FVOUBMTO-BYDSvNCG.js +1 -0
  306. package/packages/web/dist/assets/stylus-BEDo0Tqx.js +1 -0
  307. package/packages/web/dist/assets/surrealql-Bq5Q-fJD.js +1 -0
  308. package/packages/web/dist/assets/svelte-C_ipcX3V.js +1 -0
  309. package/packages/web/dist/assets/swift-D82vCrfD.js +1 -0
  310. package/packages/web/dist/assets/synthwave-84-CbfX1IO0.js +1 -0
  311. package/packages/web/dist/assets/system-verilog-CnnmHF94.js +1 -0
  312. package/packages/web/dist/assets/systemd-4A_iFExJ.js +1 -0
  313. package/packages/web/dist/assets/talonscript-CkByrt1z.js +1 -0
  314. package/packages/web/dist/assets/tasl-QIJgUcNo.js +1 -0
  315. package/packages/web/dist/assets/tcl-dwOrl1Do.js +1 -0
  316. package/packages/web/dist/assets/templ-P3uqSqPl.js +1 -0
  317. package/packages/web/dist/assets/terraform-BETggiCN.js +1 -0
  318. package/packages/web/dist/assets/tex-idrVyKtj.js +1 -0
  319. package/packages/web/dist/assets/timeline-definition-YZTLITO2-Dh94GN9W.js +61 -0
  320. package/packages/web/dist/assets/tokyo-night-hegEt444.js +1 -0
  321. package/packages/web/dist/assets/toml-vGWfd6FD.js +1 -0
  322. package/packages/web/dist/assets/treemap-KZPCXAKY-Cnfp1I4Q.js +162 -0
  323. package/packages/web/dist/assets/ts-tags-eLBrqaVN.js +1 -0
  324. package/packages/web/dist/assets/tsv-B_m7g4N7.js +1 -0
  325. package/packages/web/dist/assets/tsx-COt5Ahok.js +1 -0
  326. package/packages/web/dist/assets/turtle-BsS91CYL.js +1 -0
  327. package/packages/web/dist/assets/twig-DNn4PbVi.js +1 -0
  328. package/packages/web/dist/assets/typescript-BPQ3VLAy.js +1 -0
  329. package/packages/web/dist/assets/typespec-BGHnOYBU.js +1 -0
  330. package/packages/web/dist/assets/typst-DHCkPAjA.js +1 -0
  331. package/packages/web/dist/assets/v-BcVCzyr7.js +1 -0
  332. package/packages/web/dist/assets/vala-CsfeWuGM.js +1 -0
  333. package/packages/web/dist/assets/vb-D17OF-Vu.js +1 -0
  334. package/packages/web/dist/assets/vennDiagram-LZ73GAT5-oM-IB92W.js +34 -0
  335. package/packages/web/dist/assets/verilog-BQ8w6xss.js +1 -0
  336. package/packages/web/dist/assets/vesper-DU1UobuO.js +1 -0
  337. package/packages/web/dist/assets/vhdl-CeAyd5Ju.js +1 -0
  338. package/packages/web/dist/assets/viml-CJc9bBzg.js +1 -0
  339. package/packages/web/dist/assets/vitesse-black-Bkuqu6BP.js +1 -0
  340. package/packages/web/dist/assets/vitesse-dark-D0r3Knsf.js +1 -0
  341. package/packages/web/dist/assets/vitesse-light-CVO1_9PV.js +1 -0
  342. package/packages/web/dist/assets/vue-DN_0RTcg.js +1 -0
  343. package/packages/web/dist/assets/vue-html-AaS7Mt5G.js +1 -0
  344. package/packages/web/dist/assets/vue-vine-CQOfvN7w.js +1 -0
  345. package/packages/web/dist/assets/vyper-CDx5xZoG.js +1 -0
  346. package/packages/web/dist/assets/wasm-CG6Dc4jp.js +1 -0
  347. package/packages/web/dist/assets/wasm-MzD3tlZU.js +1 -0
  348. package/packages/web/dist/assets/wenyan-BV7otONQ.js +1 -0
  349. package/packages/web/dist/assets/wgsl-Dx-B1_4e.js +1 -0
  350. package/packages/web/dist/assets/wikitext-BhOHFoWU.js +1 -0
  351. package/packages/web/dist/assets/wit-5i3qLPDT.js +1 -0
  352. package/packages/web/dist/assets/wolfram-lXgVvXCa.js +1 -0
  353. package/packages/web/dist/assets/xml-sdJ4AIDG.js +1 -0
  354. package/packages/web/dist/assets/xsl-CtQFsRM5.js +1 -0
  355. package/packages/web/dist/assets/xychartDiagram-JWTSCODW-rBsEDTWK.js +7 -0
  356. package/packages/web/dist/assets/yaml-Buea-lGh.js +1 -0
  357. package/packages/web/dist/assets/zenscript-DVFEvuxE.js +1 -0
  358. package/packages/web/dist/assets/zig-VOosw3JB.js +1 -0
  359. package/packages/web/dist/favicon.svg +35 -0
  360. package/packages/web/dist/index.html +14 -0
  361. package/scripts/postinstall.mjs +42 -0
@@ -0,0 +1,3186 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // src/workspace/ConfigManager.ts
13
+ var ConfigManager_exports = {};
14
+ __export(ConfigManager_exports, {
15
+ ConfigManager: () => ConfigManager
16
+ });
17
+ import fs from "fs";
18
+ import path from "path";
19
+ import os from "os";
20
+ import { execFile } from "child_process";
21
+ import { promisify } from "util";
22
+ import yaml from "js-yaml";
23
+ var execFileAsync, GLOBAL_DIR, GLOBAL_CONFIG_PATH, DEFAULT_GLOBAL_CONFIG, ConfigManager;
24
+ var init_ConfigManager = __esm({
25
+ "src/workspace/ConfigManager.ts"() {
26
+ "use strict";
27
+ execFileAsync = promisify(execFile);
28
+ GLOBAL_DIR = path.join(os.homedir(), ".nexus");
29
+ GLOBAL_CONFIG_PATH = path.join(GLOBAL_DIR, "config.yaml");
30
+ DEFAULT_GLOBAL_CONFIG = {
31
+ version: "1",
32
+ defaults: {
33
+ shell: process.env.SHELL || "/bin/bash",
34
+ scrollback_lines: 5e3,
35
+ grid_columns: 2,
36
+ history_retention_days: 30,
37
+ theme: "dark-ide"
38
+ },
39
+ agents: {
40
+ claudecode: {
41
+ bin: "claude",
42
+ continue_flag: "--continue",
43
+ resume_flag: "--resume",
44
+ yolo_flag: "--dangerously-skip-permissions",
45
+ statusline: true,
46
+ env: {}
47
+ },
48
+ opencode: {
49
+ bin: "opencode",
50
+ continue_flag: "--continue",
51
+ yolo_flag: "--yolo",
52
+ statusline: false,
53
+ env: {}
54
+ }
55
+ }
56
+ };
57
+ ConfigManager = class {
58
+ globalConfig = null;
59
+ workspaceConfig = null;
60
+ projectDir;
61
+ constructor(projectDir) {
62
+ this.projectDir = projectDir;
63
+ }
64
+ loadGlobalConfig() {
65
+ if (this.globalConfig) return this.globalConfig;
66
+ if (fs.existsSync(GLOBAL_CONFIG_PATH)) {
67
+ const content = fs.readFileSync(GLOBAL_CONFIG_PATH, "utf-8");
68
+ this.globalConfig = yaml.load(content);
69
+ let updated = false;
70
+ for (const [key, def] of Object.entries(DEFAULT_GLOBAL_CONFIG.agents)) {
71
+ if (!this.globalConfig.agents[key]) {
72
+ this.globalConfig.agents[key] = def;
73
+ updated = true;
74
+ } else {
75
+ const existing = this.globalConfig.agents[key];
76
+ for (const [field, value] of Object.entries(def)) {
77
+ if (!(field in existing)) {
78
+ existing[field] = value;
79
+ updated = true;
80
+ }
81
+ }
82
+ }
83
+ }
84
+ if (updated) {
85
+ this.saveGlobalConfig(this.globalConfig);
86
+ }
87
+ } else {
88
+ this.globalConfig = { ...DEFAULT_GLOBAL_CONFIG };
89
+ this.saveGlobalConfig(this.globalConfig);
90
+ this.detectAgentsAsync().then((detected) => {
91
+ if (Object.keys(detected).length > 0 && this.globalConfig) {
92
+ this.globalConfig.agents = { ...this.globalConfig.agents, ...detected };
93
+ this.saveGlobalConfig(this.globalConfig);
94
+ }
95
+ }).catch(() => {
96
+ });
97
+ }
98
+ return this.globalConfig;
99
+ }
100
+ saveGlobalConfig(config) {
101
+ fs.mkdirSync(GLOBAL_DIR, { recursive: true });
102
+ fs.writeFileSync(GLOBAL_CONFIG_PATH, yaml.dump(config, { lineWidth: -1 }));
103
+ }
104
+ loadWorkspaceConfig() {
105
+ if (this.workspaceConfig) return this.workspaceConfig;
106
+ const configPath = path.join(this.projectDir, ".nexus", "config.yaml");
107
+ if (fs.existsSync(configPath)) {
108
+ const content = fs.readFileSync(configPath, "utf-8");
109
+ const parsed = yaml.load(content);
110
+ if (parsed) {
111
+ if (!Array.isArray(parsed.panes)) parsed.panes = [];
112
+ this.workspaceConfig = parsed;
113
+ return this.workspaceConfig;
114
+ }
115
+ }
116
+ return null;
117
+ }
118
+ saveWorkspaceConfig(config) {
119
+ const nexusDir = path.join(this.projectDir, ".nexus");
120
+ fs.mkdirSync(nexusDir, { recursive: true });
121
+ const configPath = path.join(nexusDir, "config.yaml");
122
+ fs.writeFileSync(configPath, yaml.dump(config, { lineWidth: -1 }));
123
+ this.workspaceConfig = config;
124
+ }
125
+ initWorkspace() {
126
+ const existing = this.loadWorkspaceConfig();
127
+ if (existing) return existing;
128
+ const dirName = path.basename(this.projectDir);
129
+ const isGit = fs.existsSync(path.join(this.projectDir, ".git"));
130
+ const config = {
131
+ version: "1",
132
+ name: dirName,
133
+ description: "",
134
+ repository: {
135
+ path: ".",
136
+ git: isGit
137
+ },
138
+ panes: []
139
+ };
140
+ this.saveWorkspaceConfig(config);
141
+ return config;
142
+ }
143
+ async detectAgentsAsync() {
144
+ const agents = {};
145
+ const agentBins = [
146
+ { key: "claudecode", bin: "claude", flag: "--continue", statusline: true },
147
+ { key: "codex", bin: "codex", flag: "", statusline: false },
148
+ { key: "opencode", bin: "opencode", flag: "--continue", statusline: false },
149
+ { key: "kimi-cli", bin: "kimi", flag: "--continue", statusline: false },
150
+ { key: "qwencode", bin: "qwen-code", flag: "--continue", statusline: false }
151
+ ];
152
+ const results = await Promise.allSettled(
153
+ agentBins.map(async (agent) => {
154
+ await execFileAsync("which", [agent.bin], { timeout: 3e3 });
155
+ return agent;
156
+ })
157
+ );
158
+ for (const result of results) {
159
+ if (result.status === "fulfilled") {
160
+ const agent = result.value;
161
+ agents[agent.key] = {
162
+ bin: agent.bin,
163
+ continue_flag: agent.flag,
164
+ statusline: agent.statusline,
165
+ env: {}
166
+ };
167
+ }
168
+ }
169
+ return agents;
170
+ }
171
+ getAgentDefinition(agentType) {
172
+ const global = this.loadGlobalConfig();
173
+ return global.agents[agentType];
174
+ }
175
+ getShell() {
176
+ const global = this.loadGlobalConfig();
177
+ const configured = global.defaults.shell;
178
+ const candidates = [
179
+ "/opt/homebrew/bin/zsh",
180
+ // macOS Apple Silicon Homebrew
181
+ "/usr/local/bin/zsh",
182
+ // macOS Intel Homebrew
183
+ "/usr/bin/zsh",
184
+ // Linux
185
+ "/bin/zsh",
186
+ // macOS default / Linux
187
+ configured,
188
+ process.env.SHELL,
189
+ "/bin/bash",
190
+ "/bin/sh"
191
+ ];
192
+ for (const sh of candidates) {
193
+ if (!sh) continue;
194
+ try {
195
+ fs.accessSync(sh, fs.constants.X_OK);
196
+ return sh;
197
+ } catch {
198
+ }
199
+ }
200
+ return "/bin/sh";
201
+ }
202
+ /**
203
+ * Check which agents are available (installed) on the system.
204
+ * Returns a record of agentType → { installed, bin, installHint }
205
+ */
206
+ async checkAgentAvailability() {
207
+ const global = this.loadGlobalConfig();
208
+ const knownAgents = [
209
+ { key: "claudecode", bin: "claude", installHint: "npm install -g @anthropic-ai/claude-code" },
210
+ { key: "codex", bin: "codex", installHint: "npm install -g @openai/codex" },
211
+ { key: "opencode", bin: "opencode", installHint: "go install github.com/opencode-ai/opencode@latest" },
212
+ { key: "kimi-cli", bin: "kimi", installHint: "pip install kimi-cli" },
213
+ { key: "qwencode", bin: "qwen-code", installHint: "pip install qwen-code" }
214
+ ];
215
+ const checks = await Promise.allSettled(
216
+ knownAgents.map(async (agent) => {
217
+ const def = global.agents[agent.key];
218
+ const bin = def?.bin || agent.bin;
219
+ try {
220
+ await execFileAsync("which", [bin], { timeout: 3e3 });
221
+ return { ...agent, bin, installed: true };
222
+ } catch {
223
+ return { ...agent, bin, installed: false };
224
+ }
225
+ })
226
+ );
227
+ const result = {};
228
+ for (const check of checks) {
229
+ if (check.status === "fulfilled") {
230
+ const { key, bin, installHint, installed } = check.value;
231
+ result[key] = { installed, bin, installHint };
232
+ }
233
+ }
234
+ return result;
235
+ }
236
+ updateGlobalConfig(config) {
237
+ this.globalConfig = config;
238
+ this.saveGlobalConfig(config);
239
+ }
240
+ getProjectDir() {
241
+ return this.projectDir;
242
+ }
243
+ };
244
+ }
245
+ });
246
+
247
+ // src/cli.ts
248
+ import path10 from "path";
249
+ import fs9 from "fs";
250
+
251
+ // src/index.ts
252
+ init_ConfigManager();
253
+ import Fastify from "fastify";
254
+ import fastifyWebsocket from "@fastify/websocket";
255
+ import fastifyStatic from "@fastify/static";
256
+ import path9 from "path";
257
+ import fs8 from "fs";
258
+ import yaml3 from "js-yaml";
259
+ import { fileURLToPath } from "url";
260
+
261
+ // src/pty/PtyManager.ts
262
+ import * as pty from "node-pty";
263
+ import fs2 from "fs";
264
+ import os2 from "os";
265
+ import path2 from "path";
266
+
267
+ // src/comm/StatuslineParser.ts
268
+ var KNOWN_FIELDS = {
269
+ model: "string",
270
+ session_id: "string",
271
+ cost_usd: "number",
272
+ context_used_pct: "number",
273
+ cwd: "string",
274
+ tool_name: "string"
275
+ // Claude Code may add more fields — add them here as discovered
276
+ };
277
+ var MIN_KNOWN_FIELDS = 2;
278
+ var StatuslineParser = class {
279
+ buffer = "";
280
+ /**
281
+ * Process raw PTY output data.
282
+ * Returns { cleanData, meta } where cleanData has statusline JSON stripped
283
+ * and meta contains parsed metadata (if any was found).
284
+ */
285
+ parse(data) {
286
+ let meta = null;
287
+ if (!data.includes("\n")) {
288
+ this.buffer += data;
289
+ return { cleanData: data, meta: null };
290
+ }
291
+ const combined = this.buffer + data;
292
+ this.buffer = "";
293
+ const lines = combined.split("\n");
294
+ const trailing = lines.pop() || "";
295
+ if (trailing) {
296
+ this.buffer = trailing;
297
+ }
298
+ const cleanLines = [];
299
+ for (const line of lines) {
300
+ const trimmed = line.trim();
301
+ if (trimmed.length < 10 || trimmed.charCodeAt(0) !== 123) {
302
+ cleanLines.push(line);
303
+ continue;
304
+ }
305
+ if (trimmed.charCodeAt(trimmed.length - 1) !== 125) {
306
+ cleanLines.push(line);
307
+ continue;
308
+ }
309
+ let parsed;
310
+ try {
311
+ parsed = JSON.parse(trimmed);
312
+ } catch {
313
+ cleanLines.push(line);
314
+ continue;
315
+ }
316
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
317
+ cleanLines.push(line);
318
+ continue;
319
+ }
320
+ if (this.isStatuslineJson(parsed)) {
321
+ meta = this.extractMeta(parsed);
322
+ continue;
323
+ }
324
+ cleanLines.push(line);
325
+ }
326
+ let cleanData = cleanLines.join("\n");
327
+ if (cleanLines.length > 0) {
328
+ cleanData += "\n";
329
+ }
330
+ return { cleanData, meta };
331
+ }
332
+ /**
333
+ * Reset internal buffer state. Useful when restarting a pane.
334
+ */
335
+ reset() {
336
+ this.buffer = "";
337
+ }
338
+ isStatuslineJson(obj) {
339
+ let matchCount = 0;
340
+ for (const [field, expectedType] of Object.entries(KNOWN_FIELDS)) {
341
+ if (field in obj) {
342
+ if (typeof obj[field] === expectedType) {
343
+ matchCount++;
344
+ }
345
+ }
346
+ }
347
+ return matchCount >= MIN_KNOWN_FIELDS;
348
+ }
349
+ extractMeta(obj) {
350
+ const meta = {};
351
+ if (typeof obj.model === "string") {
352
+ meta.model = obj.model;
353
+ }
354
+ if (typeof obj.context_used_pct === "number") {
355
+ meta.contextUsedPct = obj.context_used_pct;
356
+ }
357
+ if (typeof obj.cost_usd === "number") {
358
+ meta.costUsd = obj.cost_usd;
359
+ }
360
+ if (typeof obj.session_id === "string") {
361
+ meta.sessionId = obj.session_id;
362
+ }
363
+ if (typeof obj.cwd === "string") {
364
+ meta.cwd = obj.cwd;
365
+ }
366
+ return meta;
367
+ }
368
+ };
369
+
370
+ // src/comm/ShellReadyDetector.ts
371
+ var FALLBACK_TIMEOUT_MS = 8e3;
372
+ var ShellReadyDetector = class {
373
+ constructor(paneId, options = {}) {
374
+ this.paneId = paneId;
375
+ this.options = options;
376
+ this.sentinel = `__NEXUS_RDY_${paneId}_${Date.now().toString(36)}__`;
377
+ this.startTime = Date.now();
378
+ this.promise = new Promise((resolve) => {
379
+ this.resolve = resolve;
380
+ });
381
+ }
382
+ sentinel;
383
+ resolve;
384
+ promise;
385
+ startTime;
386
+ timer = null;
387
+ disposed = false;
388
+ // Exposed for testing — accumulated raw output chunks
389
+ chunks = [];
390
+ /**
391
+ * Inject the sentinel into the shell and start watching.
392
+ * Call this immediately after pty.spawn().
393
+ *
394
+ * Returns a promise that resolves when the shell is ready.
395
+ * The caller should await this before sending any agent command.
396
+ */
397
+ start(term) {
398
+ const timeoutMs = this.options.timeoutMs ?? FALLBACK_TIMEOUT_MS;
399
+ term.write(`echo ${this.sentinel}\r`);
400
+ this.timer = setTimeout(() => {
401
+ if (!this.disposed) {
402
+ this.disposed = true;
403
+ this.resolve({
404
+ detected: false,
405
+ elapsedMs: Date.now() - this.startTime
406
+ });
407
+ }
408
+ }, timeoutMs);
409
+ return this.promise;
410
+ }
411
+ /**
412
+ * Feed PTY output data into the detector.
413
+ * Call this from the PTY onData handler.
414
+ *
415
+ * Returns the data with the sentinel line stripped if `stripSentinel` is true
416
+ * and the sentinel was found in this chunk. Otherwise returns the data as-is.
417
+ */
418
+ feed(data) {
419
+ if (this.disposed) return data;
420
+ this.chunks.push(data);
421
+ const recent = this.chunks.length <= 3 ? this.chunks.join("") : this.chunks.slice(-3).join("");
422
+ if (recent.includes(this.sentinel)) {
423
+ this.disposed = true;
424
+ if (this.timer) {
425
+ clearTimeout(this.timer);
426
+ this.timer = null;
427
+ }
428
+ this.resolve({
429
+ detected: true,
430
+ elapsedMs: Date.now() - this.startTime
431
+ });
432
+ this.chunks = [];
433
+ if (this.options.stripSentinel) {
434
+ return this.stripSentinelFromData(data);
435
+ }
436
+ }
437
+ return data;
438
+ }
439
+ /**
440
+ * Whether the detector has finished (either by detection or timeout).
441
+ */
442
+ get isDone() {
443
+ return this.disposed;
444
+ }
445
+ /**
446
+ * Clean up without resolving (e.g., if the PTY is killed before ready).
447
+ */
448
+ dispose() {
449
+ if (!this.disposed) {
450
+ this.disposed = true;
451
+ if (this.timer) {
452
+ clearTimeout(this.timer);
453
+ this.timer = null;
454
+ }
455
+ this.resolve({
456
+ detected: false,
457
+ elapsedMs: Date.now() - this.startTime
458
+ });
459
+ }
460
+ this.chunks = [];
461
+ }
462
+ stripSentinelFromData(data) {
463
+ const lines = data.split("\n");
464
+ const filtered = lines.filter((line) => !line.includes(this.sentinel));
465
+ return filtered.join("\n");
466
+ }
467
+ };
468
+
469
+ // src/comm/AgentReadyDetector.ts
470
+ var DEFAULT_QUIESCENCE_MS = 3e3;
471
+ var DEFAULT_HARD_TIMEOUT_MS = 15e3;
472
+ var PROMPT_PATTERNS = [
473
+ /❯\s*$/,
474
+ // Claude Code prompt
475
+ />\s*$/,
476
+ // Generic prompt
477
+ /\$\s*$/,
478
+ // Shell-style prompt (some agents)
479
+ /waiting for input/i
480
+ ];
481
+ var AgentReadyDetector = class {
482
+ constructor(options = {}) {
483
+ this.options = options;
484
+ this.startTime = Date.now();
485
+ this.quiescenceMs = options.quiescenceMs ?? DEFAULT_QUIESCENCE_MS;
486
+ this.promptPatterns = [
487
+ ...PROMPT_PATTERNS,
488
+ ...options.extraPromptPatterns || []
489
+ ];
490
+ this.promise = new Promise((resolve) => {
491
+ this.resolve = resolve;
492
+ });
493
+ }
494
+ resolve;
495
+ promise;
496
+ startTime;
497
+ disposed = false;
498
+ quiescenceTimer = null;
499
+ hardTimer = null;
500
+ quiescenceMs;
501
+ promptPatterns;
502
+ /**
503
+ * Start the detection timers. Returns a promise that resolves when the
504
+ * agent is deemed ready.
505
+ */
506
+ start() {
507
+ const hardTimeoutMs = this.options.hardTimeoutMs ?? DEFAULT_HARD_TIMEOUT_MS;
508
+ this.hardTimer = setTimeout(() => {
509
+ this.finish("timeout");
510
+ }, hardTimeoutMs);
511
+ this.resetQuiescence();
512
+ return this.promise;
513
+ }
514
+ /**
515
+ * Feed terminal output from the agent. Resets quiescence timer and
516
+ * checks for prompt patterns.
517
+ */
518
+ feed(data) {
519
+ if (this.disposed) return;
520
+ this.resetQuiescence();
521
+ const clean = stripAnsi(data);
522
+ const tail = clean.slice(-200);
523
+ for (const pattern of this.promptPatterns) {
524
+ if (pattern.test(tail)) {
525
+ this.finish("prompt");
526
+ return;
527
+ }
528
+ }
529
+ }
530
+ /**
531
+ * Called when a statusline meta event is received. If it contains a
532
+ * session_id, the agent is definitely ready.
533
+ */
534
+ onMeta(meta) {
535
+ if (this.disposed) return;
536
+ if (meta.sessionId) {
537
+ this.finish("statusline");
538
+ }
539
+ }
540
+ get isDone() {
541
+ return this.disposed;
542
+ }
543
+ dispose() {
544
+ if (!this.disposed) {
545
+ this.disposed = true;
546
+ this.clearTimers();
547
+ this.resolve({
548
+ reason: "timeout",
549
+ elapsedMs: Date.now() - this.startTime
550
+ });
551
+ }
552
+ }
553
+ finish(reason) {
554
+ if (this.disposed) return;
555
+ this.disposed = true;
556
+ this.clearTimers();
557
+ this.resolve({
558
+ reason,
559
+ elapsedMs: Date.now() - this.startTime
560
+ });
561
+ }
562
+ resetQuiescence() {
563
+ if (this.quiescenceTimer) {
564
+ clearTimeout(this.quiescenceTimer);
565
+ }
566
+ this.quiescenceTimer = setTimeout(() => {
567
+ this.finish("quiescence");
568
+ }, this.quiescenceMs);
569
+ }
570
+ clearTimers() {
571
+ if (this.quiescenceTimer) {
572
+ clearTimeout(this.quiescenceTimer);
573
+ this.quiescenceTimer = null;
574
+ }
575
+ if (this.hardTimer) {
576
+ clearTimeout(this.hardTimer);
577
+ this.hardTimer = null;
578
+ }
579
+ }
580
+ };
581
+ function stripAnsi(str) {
582
+ return str.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
583
+ }
584
+
585
+ // src/comm/OutputStateAnalyzer.ts
586
+ var DEFAULT_IDLE_THRESHOLD_MS = 5e3;
587
+ var OutputStateAnalyzer = class {
588
+ currentStatus = "running";
589
+ lastOutputTime = 0;
590
+ lastContextPct = null;
591
+ idleTimer = null;
592
+ extendedIdleTimer = null;
593
+ idleThresholdMs;
594
+ onStatusChange;
595
+ constructor(options = {}) {
596
+ this.idleThresholdMs = options.idleThresholdMs ?? DEFAULT_IDLE_THRESHOLD_MS;
597
+ this.onStatusChange = options.onStatusChange;
598
+ this.lastOutputTime = Date.now();
599
+ }
600
+ /**
601
+ * Call on every PTY output chunk. Must be fast — hot path.
602
+ */
603
+ onOutput() {
604
+ this.lastOutputTime = Date.now();
605
+ if (this.currentStatus === "waiting" || this.currentStatus === "idle") {
606
+ this.setStatus("running");
607
+ }
608
+ this.resetIdleTimers();
609
+ }
610
+ /**
611
+ * Call when a statusline meta event is received.
612
+ * Context usage changes confirm the agent is actively working.
613
+ */
614
+ onMeta(meta) {
615
+ if (meta.contextUsedPct !== void 0) {
616
+ if (this.lastContextPct !== null && meta.contextUsedPct > this.lastContextPct) {
617
+ if (this.currentStatus !== "running") {
618
+ this.setStatus("running");
619
+ }
620
+ this.resetIdleTimers();
621
+ }
622
+ this.lastContextPct = meta.contextUsedPct;
623
+ }
624
+ }
625
+ /**
626
+ * Call when the PTY process exits. Overrides any inferred state.
627
+ */
628
+ onExit(exitCode) {
629
+ this.clearTimers();
630
+ this.setStatus(exitCode === 0 ? "stopped" : "error");
631
+ }
632
+ /**
633
+ * Get the current inferred status.
634
+ */
635
+ getStatus() {
636
+ return this.currentStatus;
637
+ }
638
+ /**
639
+ * Milliseconds since last output.
640
+ */
641
+ getSilenceMs() {
642
+ return Date.now() - this.lastOutputTime;
643
+ }
644
+ /**
645
+ * Clean up timers.
646
+ */
647
+ dispose() {
648
+ this.clearTimers();
649
+ }
650
+ setStatus(status) {
651
+ if (status === this.currentStatus) return;
652
+ this.currentStatus = status;
653
+ this.onStatusChange?.(status);
654
+ }
655
+ resetIdleTimers() {
656
+ this.clearTimers();
657
+ this.idleTimer = setTimeout(() => {
658
+ if (this.currentStatus === "running") {
659
+ this.setStatus("waiting");
660
+ }
661
+ this.extendedIdleTimer = setTimeout(() => {
662
+ if (this.currentStatus === "waiting") {
663
+ this.setStatus("idle");
664
+ }
665
+ }, this.idleThresholdMs * 2);
666
+ }, this.idleThresholdMs);
667
+ }
668
+ clearTimers() {
669
+ if (this.idleTimer) {
670
+ clearTimeout(this.idleTimer);
671
+ this.idleTimer = null;
672
+ }
673
+ if (this.extendedIdleTimer) {
674
+ clearTimeout(this.extendedIdleTimer);
675
+ this.extendedIdleTimer = null;
676
+ }
677
+ }
678
+ };
679
+
680
+ // src/pty/ActivityParser.ts
681
+ function stripAnsi2(str) {
682
+ return str.replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, "").replace(/\x1b\[[0-9;]*[a-zA-Z]/g, "").replace(/\x1b[()][AB012]/g, "").replace(/\x1b[>=<]/g, "").replace(/\x0f|\x0e/g, "").replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, "");
683
+ }
684
+ var ActivityParser = class {
685
+ buffer = "";
686
+ lastFile = "";
687
+ lastTime = 0;
688
+ DEDUP_MS = 2e3;
689
+ // Ignore same file within 2s
690
+ parse(data) {
691
+ if (!data.includes("\n")) {
692
+ this.buffer += data;
693
+ return null;
694
+ }
695
+ const lines = (this.buffer + data).split("\n");
696
+ this.buffer = "";
697
+ const trailing = lines.pop() || "";
698
+ if (trailing) {
699
+ this.buffer = trailing;
700
+ }
701
+ for (const line of lines) {
702
+ const activity = this.parseLine(line);
703
+ if (activity) {
704
+ if (activity.file === this.lastFile && activity.timestamp - this.lastTime < this.DEDUP_MS) {
705
+ continue;
706
+ }
707
+ this.lastFile = activity.file;
708
+ this.lastTime = activity.timestamp;
709
+ return activity;
710
+ }
711
+ }
712
+ return null;
713
+ }
714
+ parseLine(line) {
715
+ const clean = stripAnsi2(line).trim();
716
+ if (!clean || clean.length < 5) return null;
717
+ const toolResultMatch = clean.match(
718
+ /^[⎿│├└┌┐┘┤┬┴┼╭╮╰╯─━]?\s*(Read|Edit|Write|MultiEdit|Create|Delete|Glob|Grep|Bash)\s+(.+)/i
719
+ );
720
+ if (toolResultMatch) {
721
+ return this.buildActivity(toolResultMatch[1], toolResultMatch[2]);
722
+ }
723
+ const toolStartMatch = clean.match(
724
+ /^(Read|Edit|Write|MultiEdit|Create|Delete)\s+([^\s(][^\s]*\.\w{1,10})\b/
725
+ );
726
+ if (toolStartMatch) {
727
+ return this.buildActivity(toolStartMatch[1], toolStartMatch[2]);
728
+ }
729
+ const parenMatch = clean.match(
730
+ /\b(Read|Edit|Write|MultiEdit|Create|Delete)\((?:file_path\s*[:=]\s*)?["']?([^"')]+\.\w{1,10})["']?\)/i
731
+ );
732
+ if (parenMatch) {
733
+ return this.buildActivity(parenMatch[1], parenMatch[2]);
734
+ }
735
+ const jsonFieldMatch = clean.match(
736
+ /["']?file_path["']?\s*[:=]\s*["']([^"']+\.\w{1,10})["']/i
737
+ );
738
+ if (jsonFieldMatch) {
739
+ const action = this.inferActionFromContext(clean);
740
+ return this.buildActivity(action, jsonFieldMatch[1]);
741
+ }
742
+ const colonMatch = clean.match(
743
+ /(?:─+\s*)?(Read|Edit|Write|MultiEdit|Create|Delete)\s*:\s*(.+?)(?:\s*─+)?$/i
744
+ );
745
+ if (colonMatch) {
746
+ return this.buildActivity(colonMatch[1], colonMatch[2]);
747
+ }
748
+ const spinnerMatch = clean.match(
749
+ /[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏⠿✓✗✔✘●◉▶▸►◆◇■□▪▫★☆○◎]\s*(Read|Edit|Write|MultiEdit|Create|Delete)\s+(.+)/i
750
+ );
751
+ if (spinnerMatch) {
752
+ return this.buildActivity(spinnerMatch[1], spinnerMatch[2]);
753
+ }
754
+ const indentedPathMatch = clean.match(
755
+ /^([a-zA-Z0-9_][a-zA-Z0-9_./\-]*\.\w{1,10})$/
756
+ );
757
+ if (indentedPathMatch && line.startsWith(" ")) {
758
+ return this.buildActivity("Read", indentedPathMatch[1]);
759
+ }
760
+ return null;
761
+ }
762
+ buildActivity(toolName, rawPath) {
763
+ const file = this.cleanPath(rawPath);
764
+ if (!file || !this.isValidPath(file)) return null;
765
+ const action = this.toolToAction(toolName);
766
+ return { file, action, timestamp: Date.now() };
767
+ }
768
+ toolToAction(tool) {
769
+ switch (tool.toLowerCase()) {
770
+ case "read":
771
+ case "glob":
772
+ case "grep":
773
+ return "read";
774
+ case "edit":
775
+ case "multiedit":
776
+ return "edit";
777
+ case "write":
778
+ return "write";
779
+ case "create":
780
+ return "create";
781
+ case "delete":
782
+ return "delete";
783
+ case "bash":
784
+ return "bash";
785
+ default:
786
+ return "read";
787
+ }
788
+ }
789
+ inferActionFromContext(line) {
790
+ const lower = line.toLowerCase();
791
+ if (lower.includes("edit") || lower.includes("old_string") || lower.includes("new_string")) return "Edit";
792
+ if (lower.includes("write") || lower.includes("content")) return "Write";
793
+ if (lower.includes("delete") || lower.includes("remove")) return "Delete";
794
+ return "Read";
795
+ }
796
+ cleanPath(raw) {
797
+ return raw.trim().replace(/^['"`]+|['"`]+$/g, "").replace(/^\.\//, "").replace(/\s+.*$/, "").replace(/[,;:)]+$/, "").replace(/^\(/, "");
798
+ }
799
+ isValidPath(file) {
800
+ if (!file || file.length < 3) return false;
801
+ if (!/\.\w{1,10}$/.test(file)) return false;
802
+ if (file.startsWith("/") || file.includes("://")) return false;
803
+ if (/[<>|&$`\\{}[\]]/.test(file)) return false;
804
+ const parts = file.split("/");
805
+ if (parts.length > 15) return false;
806
+ for (const part of parts) {
807
+ if (part.length > 100 || part.length === 0) return false;
808
+ }
809
+ return true;
810
+ }
811
+ };
812
+
813
+ // src/pty/PtyManager.ts
814
+ var MAX_SCROLLBACK_BYTES = 512 * 1024;
815
+ var PtyManager = class {
816
+ entries = /* @__PURE__ */ new Map();
817
+ configManager;
818
+ constructor(configManager) {
819
+ this.configManager = configManager;
820
+ }
821
+ spawn(paneId, config, cols = 80, rows = 24) {
822
+ if (this.entries.has(paneId)) {
823
+ this.kill(paneId);
824
+ }
825
+ const shell = this.configManager.getShell();
826
+ console.log(`[PTY] Using shell: ${shell} for pane ${paneId}`);
827
+ const projectDir = this.configManager.getProjectDir();
828
+ const basePath = config.isolation === "worktree" && config.worktreePath ? config.worktreePath : projectDir;
829
+ let cwd = config.workdir ? path2.resolve(basePath, config.workdir) : basePath;
830
+ if (!fs2.existsSync(cwd)) {
831
+ console.warn(`[PTY] cwd does not exist: ${cwd}, falling back to ${projectDir}`);
832
+ cwd = fs2.existsSync(projectDir) ? projectDir : os2.homedir();
833
+ }
834
+ let resolvedShell = shell;
835
+ if (!fs2.existsSync(resolvedShell)) {
836
+ const fallbacks = ["/bin/zsh", "/bin/bash", "/bin/sh"];
837
+ const found = fallbacks.find((s) => fs2.existsSync(s));
838
+ console.error(`[PTY] Shell binary not found: ${resolvedShell}, falling back to ${found || "/bin/sh"}`);
839
+ resolvedShell = found || "/bin/sh";
840
+ }
841
+ const agentDef = this.configManager.getAgentDefinition(config.agent);
842
+ const isAgent = agentDef && config.agent !== "__shell__";
843
+ const env = {};
844
+ for (const [key, value] of Object.entries(process.env)) {
845
+ if (value !== void 0 && !key.startsWith("CLAUDE") && key !== "CLAUDECODE") {
846
+ env[key] = value;
847
+ }
848
+ }
849
+ if (agentDef?.env) {
850
+ for (const [key, value] of Object.entries(agentDef.env)) {
851
+ const resolved = value.replace(/\$\{(\w+)\}/g, (_, varName) => {
852
+ return process.env[varName] || "";
853
+ });
854
+ if (resolved) env[key] = resolved;
855
+ }
856
+ }
857
+ if (!env.PATH) {
858
+ env.PATH = "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin";
859
+ if (process.platform === "darwin") {
860
+ env.PATH = "/opt/homebrew/bin:/opt/homebrew/sbin:" + env.PATH;
861
+ }
862
+ }
863
+ console.log(`[PTY] Spawning pane ${paneId}: shell=${resolvedShell}, cwd=${cwd}`);
864
+ const term = pty.spawn(resolvedShell, [], {
865
+ name: "xterm-256color",
866
+ cols,
867
+ rows,
868
+ cwd,
869
+ env
870
+ });
871
+ const shellDetector = isAgent ? new ShellReadyDetector(paneId, { stripSentinel: true }) : null;
872
+ const stateAnalyzer = new OutputStateAnalyzer({
873
+ idleThresholdMs: 5e3,
874
+ onStatusChange: (status) => {
875
+ const e = this.entries.get(paneId);
876
+ if (e) {
877
+ e.status = status;
878
+ for (const cb of e.onStatusCallbacks) {
879
+ cb(status);
880
+ }
881
+ }
882
+ }
883
+ });
884
+ const entry = {
885
+ pty: term,
886
+ config,
887
+ status: "running",
888
+ meta: {},
889
+ parser: new StatuslineParser(),
890
+ activityParser: new ActivityParser(),
891
+ stateAnalyzer,
892
+ shellDetector,
893
+ agentDetector: null,
894
+ // Created after shell is ready
895
+ scrollback: [],
896
+ scrollbackBytes: 0,
897
+ onDataCallbacks: [],
898
+ onStatusCallbacks: [],
899
+ onMetaCallbacks: [],
900
+ onActivityCallbacks: []
901
+ };
902
+ this.entries.set(paneId, entry);
903
+ term.onData((data) => {
904
+ let processedData = data;
905
+ if (entry.shellDetector && !entry.shellDetector.isDone) {
906
+ processedData = entry.shellDetector.feed(data);
907
+ }
908
+ if (entry.agentDetector && !entry.agentDetector.isDone) {
909
+ entry.agentDetector.feed(processedData);
910
+ }
911
+ const { cleanData, meta } = entry.parser.parse(processedData);
912
+ if (cleanData) {
913
+ entry.stateAnalyzer.onOutput();
914
+ entry.scrollback.push(cleanData);
915
+ entry.scrollbackBytes += cleanData.length;
916
+ while (entry.scrollbackBytes > MAX_SCROLLBACK_BYTES && entry.scrollback.length > 1) {
917
+ const removed = entry.scrollback.shift();
918
+ entry.scrollbackBytes -= removed.length;
919
+ }
920
+ for (const cb of entry.onDataCallbacks) {
921
+ cb(cleanData);
922
+ }
923
+ }
924
+ if (meta) {
925
+ entry.meta = { ...entry.meta, ...meta };
926
+ entry.stateAnalyzer.onMeta(meta);
927
+ if (entry.agentDetector && !entry.agentDetector.isDone) {
928
+ entry.agentDetector.onMeta(meta);
929
+ }
930
+ for (const cb of entry.onMetaCallbacks) {
931
+ cb(entry.meta);
932
+ }
933
+ }
934
+ if (config.agent !== "__shell__") {
935
+ const activity = entry.activityParser.parse(data);
936
+ if (activity) {
937
+ for (const cb of entry.onActivityCallbacks) {
938
+ cb(activity);
939
+ }
940
+ }
941
+ }
942
+ });
943
+ term.onExit(({ exitCode }) => {
944
+ const e = this.entries.get(paneId);
945
+ if (e) {
946
+ e.stateAnalyzer.onExit(exitCode);
947
+ e.shellDetector?.dispose();
948
+ e.agentDetector?.dispose();
949
+ }
950
+ });
951
+ if (isAgent && shellDetector) {
952
+ this.startAgentSequence(paneId, config, agentDef, shellDetector);
953
+ }
954
+ return term.pid;
955
+ }
956
+ /**
957
+ * Orchestrates the shell → agent → task startup sequence using
958
+ * event-driven detectors instead of hardcoded setTimeout delays.
959
+ */
960
+ async startAgentSequence(paneId, config, agentDef, shellDetector) {
961
+ const entry = this.entries.get(paneId);
962
+ if (!entry) return;
963
+ const shellResult = await shellDetector.start(entry.pty);
964
+ if (!this.entries.has(paneId)) return;
965
+ console.log(`[PTY] Shell ready for ${paneId}: detected=${shellResult.detected} (${shellResult.elapsedMs}ms)`);
966
+ this.sendAgentCommand(paneId, config, agentDef);
967
+ if (config.task && config.restore !== "manual") {
968
+ const agentDetector = new AgentReadyDetector({
969
+ quiescenceMs: 3e3,
970
+ hardTimeoutMs: 15e3
971
+ });
972
+ entry.agentDetector = agentDetector;
973
+ const agentResult = await agentDetector.start();
974
+ if (!this.entries.has(paneId)) return;
975
+ console.log(`[PTY] Agent ready for ${paneId}: reason=${agentResult.reason} (${agentResult.elapsedMs}ms)`);
976
+ entry.pty.write(config.task + "\r");
977
+ }
978
+ }
979
+ sendAgentCommand(paneId, config, agentDef) {
980
+ const entry = this.entries.get(paneId);
981
+ if (!entry) return;
982
+ let cmd = agentDef.bin;
983
+ if (config.restore === "resume" && config.sessionId && agentDef.resume_flag) {
984
+ cmd += ` ${agentDef.resume_flag} ${config.sessionId}`;
985
+ } else if (config.restore === "continue" && agentDef.continue_flag) {
986
+ cmd += ` ${agentDef.continue_flag}`;
987
+ }
988
+ if (config.yolo && agentDef.yolo_flag) {
989
+ cmd += ` ${agentDef.yolo_flag}`;
990
+ }
991
+ entry.pty.write(cmd + "\r");
992
+ }
993
+ write(paneId, data) {
994
+ const entry = this.entries.get(paneId);
995
+ if (entry) {
996
+ entry.pty.write(data);
997
+ }
998
+ }
999
+ resize(paneId, cols, rows) {
1000
+ const entry = this.entries.get(paneId);
1001
+ if (entry) {
1002
+ try {
1003
+ entry.pty.resize(cols, rows);
1004
+ } catch {
1005
+ }
1006
+ }
1007
+ }
1008
+ kill(paneId) {
1009
+ const entry = this.entries.get(paneId);
1010
+ if (entry) {
1011
+ entry.stateAnalyzer.dispose();
1012
+ entry.shellDetector?.dispose();
1013
+ entry.agentDetector?.dispose();
1014
+ entry.parser.reset();
1015
+ try {
1016
+ entry.pty.kill();
1017
+ } catch {
1018
+ }
1019
+ this.entries.delete(paneId);
1020
+ }
1021
+ }
1022
+ killAll() {
1023
+ for (const [paneId] of this.entries) {
1024
+ this.kill(paneId);
1025
+ }
1026
+ }
1027
+ getStatus(paneId) {
1028
+ return this.entries.get(paneId)?.status || "stopped";
1029
+ }
1030
+ getMeta(paneId) {
1031
+ return this.entries.get(paneId)?.meta || {};
1032
+ }
1033
+ getPid(paneId) {
1034
+ return this.entries.get(paneId)?.pty.pid;
1035
+ }
1036
+ onData(paneId, callback) {
1037
+ const entry = this.entries.get(paneId);
1038
+ if (entry) {
1039
+ entry.onDataCallbacks.push(callback);
1040
+ }
1041
+ }
1042
+ onStatus(paneId, callback) {
1043
+ const entry = this.entries.get(paneId);
1044
+ if (entry) {
1045
+ entry.onStatusCallbacks.push(callback);
1046
+ }
1047
+ }
1048
+ onMeta(paneId, callback) {
1049
+ const entry = this.entries.get(paneId);
1050
+ if (entry) {
1051
+ entry.onMetaCallbacks.push(callback);
1052
+ }
1053
+ }
1054
+ onActivity(paneId, callback) {
1055
+ const entry = this.entries.get(paneId);
1056
+ if (entry) {
1057
+ entry.onActivityCallbacks.push(callback);
1058
+ }
1059
+ }
1060
+ getScrollback(paneId) {
1061
+ const entry = this.entries.get(paneId);
1062
+ if (!entry) return "";
1063
+ return entry.scrollback.join("");
1064
+ }
1065
+ has(paneId) {
1066
+ return this.entries.has(paneId);
1067
+ }
1068
+ };
1069
+
1070
+ // src/git/WorktreeManager.ts
1071
+ import path3 from "path";
1072
+ import fs3 from "fs";
1073
+ import { simpleGit } from "simple-git";
1074
+ var WorktreeManager = class {
1075
+ projectDir;
1076
+ git;
1077
+ worktrees = /* @__PURE__ */ new Map();
1078
+ constructor(projectDir) {
1079
+ this.projectDir = projectDir;
1080
+ this.git = simpleGit(projectDir);
1081
+ }
1082
+ /**
1083
+ * Create a git worktree + branch for a pane.
1084
+ * Returns the worktree disk path and branch name.
1085
+ */
1086
+ async create(paneId, paneName) {
1087
+ const baseBranch = await this.getCurrentBranch();
1088
+ const slug = this.slugify(paneName);
1089
+ const branch = `nexus/${paneId}-${slug}`;
1090
+ const worktreePath = path3.join(this.projectDir, ".nexus", "worktrees", paneId);
1091
+ if (fs3.existsSync(worktreePath)) {
1092
+ await this.forceRemoveWorktree(worktreePath);
1093
+ }
1094
+ try {
1095
+ await this.git.raw(["branch", "-D", branch]);
1096
+ } catch {
1097
+ }
1098
+ fs3.mkdirSync(path3.dirname(worktreePath), { recursive: true });
1099
+ await this.git.raw(["worktree", "add", "-b", branch, worktreePath, baseBranch]);
1100
+ const entry = { path: worktreePath, branch, baseBranch };
1101
+ this.worktrees.set(paneId, entry);
1102
+ return { worktreePath, branch };
1103
+ }
1104
+ /**
1105
+ * Remove a worktree. Branch is kept for later merge/PR.
1106
+ */
1107
+ async remove(paneId) {
1108
+ const entry = this.worktrees.get(paneId);
1109
+ if (!entry) return;
1110
+ await this.forceRemoveWorktree(entry.path);
1111
+ this.worktrees.delete(paneId);
1112
+ }
1113
+ /**
1114
+ * Remove worktree and also delete the branch.
1115
+ */
1116
+ async removeWithBranch(paneId) {
1117
+ const entry = this.worktrees.get(paneId);
1118
+ if (!entry) return;
1119
+ await this.forceRemoveWorktree(entry.path);
1120
+ try {
1121
+ await this.git.raw(["branch", "-D", entry.branch]);
1122
+ } catch {
1123
+ }
1124
+ this.worktrees.delete(paneId);
1125
+ }
1126
+ /**
1127
+ * Get diffs for a worktree pane relative to its base branch.
1128
+ */
1129
+ async getDiffs(paneId) {
1130
+ const entry = this.worktrees.get(paneId);
1131
+ if (!entry) return [];
1132
+ const wtGit = simpleGit(entry.path);
1133
+ const diffs = [];
1134
+ try {
1135
+ const status = await wtGit.status();
1136
+ const committedDiff = await wtGit.diff([`${entry.baseBranch}...HEAD`]).catch(() => "");
1137
+ const uncommittedDiff = await wtGit.diff();
1138
+ const stagedDiff = await wtGit.diff(["--cached"]);
1139
+ const combinedDiff = [committedDiff, uncommittedDiff, stagedDiff].filter(Boolean).join("\n");
1140
+ const allFiles = /* @__PURE__ */ new Set();
1141
+ const fileStatuses = /* @__PURE__ */ new Map();
1142
+ if (committedDiff) {
1143
+ const parsed = this.parseFileDiffs(committedDiff);
1144
+ for (const [file] of parsed) {
1145
+ allFiles.add(file);
1146
+ fileStatuses.set(file, "modified");
1147
+ }
1148
+ }
1149
+ for (const file of status.created) {
1150
+ allFiles.add(file);
1151
+ fileStatuses.set(file, "added");
1152
+ }
1153
+ for (const file of status.modified) {
1154
+ allFiles.add(file);
1155
+ if (!fileStatuses.has(file)) fileStatuses.set(file, "modified");
1156
+ }
1157
+ for (const file of status.deleted) {
1158
+ allFiles.add(file);
1159
+ fileStatuses.set(file, "deleted");
1160
+ }
1161
+ for (const file of status.renamed) {
1162
+ allFiles.add(file.to);
1163
+ fileStatuses.set(file.to, "renamed");
1164
+ }
1165
+ for (const file of status.staged) {
1166
+ if (!allFiles.has(file)) {
1167
+ allFiles.add(file);
1168
+ fileStatuses.set(file, "modified");
1169
+ }
1170
+ }
1171
+ const hunkMap = combinedDiff ? this.parseFileDiffs(combinedDiff) : /* @__PURE__ */ new Map();
1172
+ for (const file of allFiles) {
1173
+ diffs.push({
1174
+ file,
1175
+ status: fileStatuses.get(file) || "modified",
1176
+ hunks: hunkMap.get(file) || ""
1177
+ });
1178
+ }
1179
+ } catch (err) {
1180
+ console.warn(`[WorktreeManager] getDiffs failed for ${paneId}:`, err.message);
1181
+ }
1182
+ return diffs;
1183
+ }
1184
+ getWorktreePath(paneId) {
1185
+ return this.worktrees.get(paneId)?.path;
1186
+ }
1187
+ getBranch(paneId) {
1188
+ return this.worktrees.get(paneId)?.branch;
1189
+ }
1190
+ getBaseBranch(paneId) {
1191
+ return this.worktrees.get(paneId)?.baseBranch;
1192
+ }
1193
+ has(paneId) {
1194
+ return this.worktrees.has(paneId);
1195
+ }
1196
+ /**
1197
+ * Clean up all worktrees on shutdown.
1198
+ */
1199
+ async removeAll() {
1200
+ for (const [paneId] of this.worktrees) {
1201
+ await this.remove(paneId);
1202
+ }
1203
+ }
1204
+ async getCurrentBranch() {
1205
+ try {
1206
+ const branch = await this.git.revparse(["--abbrev-ref", "HEAD"]);
1207
+ return branch.trim() || "HEAD";
1208
+ } catch {
1209
+ return "HEAD";
1210
+ }
1211
+ }
1212
+ slugify(name) {
1213
+ return name.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff]+/g, "-").replace(/^-|-$/g, "").slice(0, 30);
1214
+ }
1215
+ async forceRemoveWorktree(wtPath) {
1216
+ try {
1217
+ await this.git.raw(["worktree", "remove", "--force", wtPath]);
1218
+ } catch {
1219
+ try {
1220
+ fs3.rmSync(wtPath, { recursive: true, force: true });
1221
+ await this.git.raw(["worktree", "prune"]);
1222
+ } catch {
1223
+ }
1224
+ }
1225
+ }
1226
+ parseFileDiffs(diffText) {
1227
+ const result = /* @__PURE__ */ new Map();
1228
+ const fileSections = diffText.split(/^diff --git /m).filter(Boolean);
1229
+ for (const section of fileSections) {
1230
+ const headerMatch = section.match(/^a\/(.+?) b\/(.+)/);
1231
+ if (!headerMatch) continue;
1232
+ const filename = headerMatch[2];
1233
+ result.set(filename, `diff --git ${section}`);
1234
+ }
1235
+ return result;
1236
+ }
1237
+ };
1238
+
1239
+ // src/git/GitService.ts
1240
+ import path4 from "path";
1241
+ import { simpleGit as simpleGit2 } from "simple-git";
1242
+ import { watch } from "chokidar";
1243
+ var GitService = class {
1244
+ git;
1245
+ projectDir;
1246
+ gitWatcher = null;
1247
+ workWatcher = null;
1248
+ debounceTimer = null;
1249
+ workDebounceTimer = null;
1250
+ listeners = /* @__PURE__ */ new Set();
1251
+ currentResult = { unstaged: [], staged: [] };
1252
+ constructor(projectDir) {
1253
+ this.projectDir = projectDir;
1254
+ this.git = simpleGit2(projectDir);
1255
+ }
1256
+ async start() {
1257
+ const isRepo = await this.git.checkIsRepo();
1258
+ if (!isRepo) return;
1259
+ await this.refresh();
1260
+ const scheduleRefresh = () => {
1261
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
1262
+ this.debounceTimer = setTimeout(() => {
1263
+ this.refresh();
1264
+ }, 1e3);
1265
+ };
1266
+ const gitDir = path4.join(this.projectDir, ".git");
1267
+ this.gitWatcher = watch([
1268
+ path4.join(gitDir, "index"),
1269
+ path4.join(gitDir, "HEAD"),
1270
+ path4.join(gitDir, "refs")
1271
+ ], {
1272
+ persistent: true,
1273
+ ignoreInitial: true
1274
+ });
1275
+ this.gitWatcher.on("all", scheduleRefresh);
1276
+ const scheduleWorkRefresh = () => {
1277
+ if (this.workDebounceTimer) clearTimeout(this.workDebounceTimer);
1278
+ this.workDebounceTimer = setTimeout(() => {
1279
+ this.refresh();
1280
+ }, 3e3);
1281
+ };
1282
+ this.workWatcher = watch(this.projectDir, {
1283
+ ignored: (filePath) => {
1284
+ const basename = path4.basename(filePath);
1285
+ return basename === ".git" || basename === "node_modules" || basename === ".nexus" || basename === "dist";
1286
+ },
1287
+ persistent: true,
1288
+ ignoreInitial: true,
1289
+ depth: 5
1290
+ });
1291
+ this.workWatcher.on("all", scheduleWorkRefresh);
1292
+ }
1293
+ async refresh() {
1294
+ try {
1295
+ const result = await this.getDiffs();
1296
+ this.currentResult = result;
1297
+ this.notifyListeners();
1298
+ } catch {
1299
+ }
1300
+ }
1301
+ getCurrentDiffs() {
1302
+ return this.currentResult;
1303
+ }
1304
+ onDiffChange(callback) {
1305
+ this.listeners.add(callback);
1306
+ return () => this.listeners.delete(callback);
1307
+ }
1308
+ // ─── Stage / Unstage ─────────────────────────────────────
1309
+ async acceptFile(file) {
1310
+ await this.git.add(file);
1311
+ await this.refresh();
1312
+ }
1313
+ async acceptAll() {
1314
+ await this.git.add("-A");
1315
+ await this.refresh();
1316
+ }
1317
+ async unstageFile(file) {
1318
+ await this.git.reset(["HEAD", "--", file]);
1319
+ await this.refresh();
1320
+ }
1321
+ async unstageAll() {
1322
+ await this.git.reset(["HEAD"]);
1323
+ await this.refresh();
1324
+ }
1325
+ // ─── Discard ──────────────────────────────────────────────
1326
+ async discardFile(file) {
1327
+ const status = await this.git.status();
1328
+ const isUntracked = status.not_added.includes(file) || status.created.includes(file);
1329
+ if (isUntracked) {
1330
+ const fullPath = path4.join(this.projectDir, file);
1331
+ const fs10 = await import("fs");
1332
+ if (fs10.existsSync(fullPath)) {
1333
+ fs10.unlinkSync(fullPath);
1334
+ }
1335
+ } else {
1336
+ await this.git.checkout(["--", file]);
1337
+ try {
1338
+ await this.git.reset(["HEAD", "--", file]);
1339
+ } catch {
1340
+ }
1341
+ }
1342
+ await this.refresh();
1343
+ }
1344
+ async discardAll() {
1345
+ await this.git.checkout(["--", "."]);
1346
+ await this.git.clean("f", ["-d"]);
1347
+ await this.refresh();
1348
+ }
1349
+ // ─── Commit / Push ────────────────────────────────────────
1350
+ async commit(message) {
1351
+ const result = await this.git.commit(message);
1352
+ await this.refresh();
1353
+ const summary = result.summary;
1354
+ return `${summary.changes} file${summary.changes !== 1 ? "s" : ""}, +${summary.insertions} -${summary.deletions}`;
1355
+ }
1356
+ async push() {
1357
+ await this.git.push();
1358
+ await this.refresh();
1359
+ return "Pushed successfully";
1360
+ }
1361
+ async getBranchInfo() {
1362
+ const status = await this.git.status();
1363
+ return {
1364
+ branch: status.current || "HEAD",
1365
+ remote: status.tracking || void 0,
1366
+ ahead: status.ahead,
1367
+ behind: status.behind
1368
+ };
1369
+ }
1370
+ // ─── Cleanup ──────────────────────────────────────────────
1371
+ close() {
1372
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
1373
+ if (this.workDebounceTimer) clearTimeout(this.workDebounceTimer);
1374
+ this.gitWatcher?.close();
1375
+ this.gitWatcher = null;
1376
+ this.workWatcher?.close();
1377
+ this.workWatcher = null;
1378
+ }
1379
+ // ─── Internal ─────────────────────────────────────────────
1380
+ async getDiffs() {
1381
+ const status = await this.git.status();
1382
+ const unstaged = [];
1383
+ const staged = [];
1384
+ const [unstagedDiffText, stagedDiffText] = await Promise.all([
1385
+ this.git.diff(),
1386
+ this.git.diff(["--cached"])
1387
+ ]);
1388
+ const unstagedHunks = this.parseFileDiffs(unstagedDiffText);
1389
+ const stagedHunks = this.parseFileDiffs(stagedDiffText);
1390
+ const stagedFiles = new Set(status.staged);
1391
+ for (const file of status.not_added) {
1392
+ unstaged.push({ file, status: "added", hunks: "" });
1393
+ }
1394
+ for (const file of status.created) {
1395
+ if (!stagedFiles.has(file)) {
1396
+ unstaged.push({ file, status: "added", hunks: "" });
1397
+ }
1398
+ }
1399
+ for (const file of status.deleted) {
1400
+ if (!stagedFiles.has(file)) {
1401
+ unstaged.push({ file, status: "deleted", hunks: "" });
1402
+ }
1403
+ }
1404
+ for (const file of status.modified) {
1405
+ unstaged.push({ file, status: "modified", hunks: unstagedHunks.get(file) || "" });
1406
+ }
1407
+ for (const file of status.renamed) {
1408
+ if (!stagedFiles.has(file.to)) {
1409
+ unstaged.push({ file: file.to, status: "renamed", hunks: "" });
1410
+ }
1411
+ }
1412
+ for (const diff of unstaged) {
1413
+ if (!diff.hunks && unstagedHunks.has(diff.file)) {
1414
+ diff.hunks = unstagedHunks.get(diff.file);
1415
+ }
1416
+ }
1417
+ for (const diff of unstaged) {
1418
+ if (diff.status === "added" && !diff.hunks) {
1419
+ try {
1420
+ const content = await this.git.show([`:${diff.file}`]).catch(() => null);
1421
+ if (content) {
1422
+ diff.hunks = `--- /dev/null
1423
+ +++ b/${diff.file}
1424
+ @@ -0,0 +1 @@
1425
+ +${content}`;
1426
+ }
1427
+ } catch {
1428
+ }
1429
+ }
1430
+ }
1431
+ for (const fileResult of status.files) {
1432
+ const indexStatus = fileResult.index;
1433
+ if (!indexStatus || indexStatus === "?" || indexStatus === " ") continue;
1434
+ const file = fileResult.path;
1435
+ let diffStatus = "modified";
1436
+ if (indexStatus === "A") diffStatus = "added";
1437
+ else if (indexStatus === "D") diffStatus = "deleted";
1438
+ else if (indexStatus === "R") diffStatus = "renamed";
1439
+ staged.push({
1440
+ file,
1441
+ status: diffStatus,
1442
+ hunks: stagedHunks.get(file) || ""
1443
+ });
1444
+ }
1445
+ return { unstaged, staged };
1446
+ }
1447
+ parseFileDiffs(diffText) {
1448
+ const result = /* @__PURE__ */ new Map();
1449
+ if (!diffText) return result;
1450
+ const fileSections = diffText.split(/^diff --git /m).filter(Boolean);
1451
+ for (const section of fileSections) {
1452
+ const headerMatch = section.match(/^a\/(.+?) b\/(.+)/);
1453
+ if (!headerMatch) continue;
1454
+ const filename = headerMatch[2];
1455
+ result.set(filename, `diff --git ${section}`);
1456
+ }
1457
+ return result;
1458
+ }
1459
+ notifyListeners() {
1460
+ for (const listener of this.listeners) {
1461
+ listener(this.currentResult);
1462
+ }
1463
+ }
1464
+ };
1465
+
1466
+ // src/workspace/WorkspaceManager.ts
1467
+ var paneCounter = 0;
1468
+ function nextPaneId() {
1469
+ return `pane-${++paneCounter}`;
1470
+ }
1471
+ var WorkspaceManager = class {
1472
+ panes = /* @__PURE__ */ new Map();
1473
+ ptyManager;
1474
+ configManager;
1475
+ worktreeManager;
1476
+ perPaneGitServices = /* @__PURE__ */ new Map();
1477
+ wsName = "";
1478
+ wsDescription = "";
1479
+ // Multi-client event listener sets
1480
+ listeners = {
1481
+ onPaneAdded: /* @__PURE__ */ new Set(),
1482
+ onPaneRemoved: /* @__PURE__ */ new Set(),
1483
+ onPaneStatus: /* @__PURE__ */ new Set(),
1484
+ onPaneMeta: /* @__PURE__ */ new Set(),
1485
+ onTerminalData: /* @__PURE__ */ new Set(),
1486
+ onPaneActivity: /* @__PURE__ */ new Set(),
1487
+ onFileActivity: /* @__PURE__ */ new Set(),
1488
+ onFileTree: /* @__PURE__ */ new Set(),
1489
+ onGitDiff: /* @__PURE__ */ new Set(),
1490
+ onPaneDiff: /* @__PURE__ */ new Set()
1491
+ };
1492
+ constructor(configManager) {
1493
+ this.configManager = configManager;
1494
+ this.ptyManager = new PtyManager(configManager);
1495
+ this.worktreeManager = new WorktreeManager(configManager.getProjectDir());
1496
+ }
1497
+ init() {
1498
+ const wsConfig = this.configManager.initWorkspace();
1499
+ this.wsName = wsConfig.name;
1500
+ this.wsDescription = wsConfig.description || "";
1501
+ if (!Array.isArray(wsConfig.panes)) wsConfig.panes = [];
1502
+ for (const p of wsConfig.panes) {
1503
+ const match = p.id.match(/^pane-(\d+)$/);
1504
+ if (match) {
1505
+ const num = parseInt(match[1], 10);
1506
+ if (num >= paneCounter) paneCounter = num;
1507
+ }
1508
+ }
1509
+ let failCount = 0;
1510
+ for (const paneConfig of wsConfig.panes) {
1511
+ if (paneConfig.sessionId && paneConfig.agent !== "__shell__") {
1512
+ paneConfig.restore = "resume";
1513
+ }
1514
+ try {
1515
+ this.spawnPane(paneConfig);
1516
+ } catch (err) {
1517
+ console.warn(`Skipping stale pane ${paneConfig.id} (${paneConfig.name}):`, err.message);
1518
+ failCount++;
1519
+ }
1520
+ }
1521
+ if (failCount > 0) {
1522
+ wsConfig.panes = wsConfig.panes.filter((p) => this.panes.has(p.id));
1523
+ this.configManager.saveWorkspaceConfig(wsConfig);
1524
+ }
1525
+ }
1526
+ getState() {
1527
+ return {
1528
+ name: this.wsName,
1529
+ description: this.wsDescription,
1530
+ projectDir: this.configManager.getProjectDir(),
1531
+ panes: Array.from(this.panes.values())
1532
+ };
1533
+ }
1534
+ getPanes() {
1535
+ return Array.from(this.panes.values());
1536
+ }
1537
+ async createPane(createConfig) {
1538
+ const isShell = createConfig.agent === "__shell__";
1539
+ const id = isShell ? "__shell__" : nextPaneId();
1540
+ const isolation = createConfig.isolation || "shared";
1541
+ const { cols, rows, ...rest } = createConfig;
1542
+ const config = {
1543
+ id,
1544
+ ...rest,
1545
+ yolo: rest.yolo || false,
1546
+ isolation,
1547
+ sessionId: rest.sessionId
1548
+ };
1549
+ if (isolation === "worktree" && !isShell) {
1550
+ try {
1551
+ const { worktreePath, branch } = await this.worktreeManager.create(id, createConfig.name);
1552
+ config.worktreePath = worktreePath;
1553
+ config.branch = branch;
1554
+ } catch (err) {
1555
+ console.error(`Failed to create worktree for pane ${id}:`, err);
1556
+ throw err;
1557
+ }
1558
+ }
1559
+ try {
1560
+ const pane = this.spawnPane(config, cols, rows);
1561
+ if (isolation === "worktree" && config.worktreePath) {
1562
+ await this.startPaneGitService(id, config.worktreePath);
1563
+ }
1564
+ if (!isShell) {
1565
+ this.persistPaneConfig(config);
1566
+ }
1567
+ return pane;
1568
+ } catch (err) {
1569
+ if (isolation === "worktree") {
1570
+ await this.worktreeManager.removeWithBranch(id);
1571
+ }
1572
+ console.error(`Failed to create pane ${id}:`, err);
1573
+ throw err;
1574
+ }
1575
+ }
1576
+ async closePane(paneId) {
1577
+ const pane = this.panes.get(paneId);
1578
+ this.ptyManager.kill(paneId);
1579
+ if (pane?.isolation === "worktree") {
1580
+ this.stopPaneGitService(paneId);
1581
+ await this.worktreeManager.remove(paneId);
1582
+ }
1583
+ this.panes.delete(paneId);
1584
+ this.emit("onPaneRemoved", paneId);
1585
+ this.removePaneFromConfig(paneId);
1586
+ }
1587
+ restartPane(paneId, mode, sessionId) {
1588
+ const existingState = this.panes.get(paneId);
1589
+ if (!existingState) return;
1590
+ this.ptyManager.kill(paneId);
1591
+ const resolvedSessionId = mode === "resume" ? sessionId || existingState.sessionId || existingState.meta.sessionId : void 0;
1592
+ const config = {
1593
+ id: paneId,
1594
+ name: existingState.name,
1595
+ agent: existingState.agent,
1596
+ workdir: existingState.workdir,
1597
+ task: existingState.task,
1598
+ restore: mode,
1599
+ isolation: existingState.isolation,
1600
+ yolo: existingState.yolo || false,
1601
+ worktreePath: existingState.worktreePath,
1602
+ branch: existingState.branch,
1603
+ sessionId: resolvedSessionId
1604
+ };
1605
+ this.spawnPane(config);
1606
+ this.updatePaneConfigSessionId(paneId, resolvedSessionId);
1607
+ }
1608
+ writeToPane(paneId, data) {
1609
+ this.ptyManager.write(paneId, data);
1610
+ }
1611
+ resizePane(paneId, cols, rows) {
1612
+ this.ptyManager.resize(paneId, cols, rows);
1613
+ }
1614
+ getScrollback(paneId) {
1615
+ return this.ptyManager.getScrollback(paneId);
1616
+ }
1617
+ // ─── Event Registration (multi-client safe) ────────────────
1618
+ /**
1619
+ * Register event handlers for a client. Returns a cleanup function
1620
+ * that removes only this client's handlers.
1621
+ */
1622
+ onEvents(handlers) {
1623
+ const cleanups = [];
1624
+ for (const key of Object.keys(handlers)) {
1625
+ const handler = handlers[key];
1626
+ if (handler) {
1627
+ const set = this.listeners[key];
1628
+ set.add(handler);
1629
+ cleanups.push(() => set.delete(handler));
1630
+ }
1631
+ }
1632
+ return () => {
1633
+ for (const cleanup of cleanups) {
1634
+ cleanup();
1635
+ }
1636
+ };
1637
+ }
1638
+ // Broadcast events (called by services)
1639
+ emitFileTree(tree) {
1640
+ this.emit("onFileTree", tree);
1641
+ }
1642
+ emitGitDiff(result) {
1643
+ this.emit("onGitDiff", result);
1644
+ }
1645
+ emitFileActivity(activity) {
1646
+ this.emit("onFileActivity", activity);
1647
+ }
1648
+ async refreshPaneDiff(paneId) {
1649
+ const gitService = this.perPaneGitServices.get(paneId);
1650
+ if (gitService) {
1651
+ await gitService.refresh();
1652
+ }
1653
+ }
1654
+ /**
1655
+ * Get current per-pane diffs (for initial sync on WS connect).
1656
+ */
1657
+ getPaneDiffs() {
1658
+ const result = /* @__PURE__ */ new Map();
1659
+ for (const [paneId, gitService] of this.perPaneGitServices) {
1660
+ result.set(paneId, gitService.getCurrentDiffs().unstaged);
1661
+ }
1662
+ return result;
1663
+ }
1664
+ // ─── Internal ─────────────────────────────────────────────
1665
+ emit(key, ...args) {
1666
+ const set = this.listeners[key];
1667
+ for (const listener of set) {
1668
+ listener(...args);
1669
+ }
1670
+ }
1671
+ spawnPane(config, cols, rows) {
1672
+ const pid = this.ptyManager.spawn(config.id, config, cols || 80, rows || 24);
1673
+ const pane = {
1674
+ id: config.id,
1675
+ name: config.name,
1676
+ agent: config.agent,
1677
+ workdir: config.workdir,
1678
+ task: config.task,
1679
+ restore: config.restore,
1680
+ isolation: config.isolation || "shared",
1681
+ yolo: config.yolo || false,
1682
+ branch: config.branch,
1683
+ worktreePath: config.worktreePath,
1684
+ sessionId: config.sessionId,
1685
+ status: "running",
1686
+ pid,
1687
+ meta: {},
1688
+ startedAt: (/* @__PURE__ */ new Date()).toISOString()
1689
+ };
1690
+ this.panes.set(config.id, pane);
1691
+ this.emit("onPaneAdded", pane);
1692
+ this.ptyManager.onData(config.id, (data) => {
1693
+ this.emit("onTerminalData", config.id, data);
1694
+ });
1695
+ this.ptyManager.onStatus(config.id, (status) => {
1696
+ const p = this.panes.get(config.id);
1697
+ if (p) {
1698
+ p.status = status;
1699
+ this.emit("onPaneStatus", config.id, status);
1700
+ }
1701
+ });
1702
+ this.ptyManager.onMeta(config.id, (meta) => {
1703
+ const p = this.panes.get(config.id);
1704
+ if (p) {
1705
+ p.meta = meta;
1706
+ if (meta.sessionId && meta.sessionId !== p.sessionId) {
1707
+ p.sessionId = meta.sessionId;
1708
+ this.updatePaneConfigSessionId(config.id, meta.sessionId);
1709
+ }
1710
+ this.emit("onPaneMeta", config.id, meta);
1711
+ }
1712
+ });
1713
+ this.ptyManager.onActivity(config.id, (activity) => {
1714
+ this.emit("onPaneActivity", config.id, activity);
1715
+ });
1716
+ return pane;
1717
+ }
1718
+ async startPaneGitService(paneId, worktreePath) {
1719
+ const gitService = new GitService(worktreePath);
1720
+ gitService.onDiffChange((result) => {
1721
+ const tagged = result.unstaged.map((d) => ({ ...d, paneId }));
1722
+ this.emit("onPaneDiff", paneId, tagged);
1723
+ });
1724
+ await gitService.start();
1725
+ this.perPaneGitServices.set(paneId, gitService);
1726
+ }
1727
+ stopPaneGitService(paneId) {
1728
+ const gitService = this.perPaneGitServices.get(paneId);
1729
+ if (gitService) {
1730
+ gitService.close();
1731
+ this.perPaneGitServices.delete(paneId);
1732
+ }
1733
+ }
1734
+ persistPaneConfig(config) {
1735
+ const wsConfig = this.configManager.loadWorkspaceConfig();
1736
+ if (wsConfig) {
1737
+ wsConfig.panes.push(config);
1738
+ this.configManager.saveWorkspaceConfig(wsConfig);
1739
+ }
1740
+ }
1741
+ updatePaneConfigSessionId(paneId, sessionId) {
1742
+ const wsConfig = this.configManager.loadWorkspaceConfig();
1743
+ if (wsConfig) {
1744
+ const paneConfig = wsConfig.panes.find((p) => p.id === paneId);
1745
+ if (paneConfig) {
1746
+ paneConfig.sessionId = sessionId;
1747
+ this.configManager.saveWorkspaceConfig(wsConfig);
1748
+ }
1749
+ }
1750
+ }
1751
+ getSessionList(paneId) {
1752
+ const sessions = [];
1753
+ for (const pane of this.panes.values()) {
1754
+ if (pane.agent === "__shell__") continue;
1755
+ if (paneId && pane.id !== paneId) continue;
1756
+ if (pane.meta.sessionId || pane.sessionId) {
1757
+ sessions.push({
1758
+ sessionId: pane.meta.sessionId || pane.sessionId,
1759
+ paneId: pane.id,
1760
+ paneName: pane.name,
1761
+ agent: pane.agent,
1762
+ timestamp: pane.startedAt || (/* @__PURE__ */ new Date()).toISOString(),
1763
+ costUsd: pane.meta.costUsd,
1764
+ contextUsedPct: pane.meta.contextUsedPct,
1765
+ model: pane.meta.model
1766
+ });
1767
+ }
1768
+ }
1769
+ const wsConfig = this.configManager.loadWorkspaceConfig();
1770
+ if (wsConfig) {
1771
+ for (const pc of wsConfig.panes) {
1772
+ if (pc.sessionId && !sessions.find((s) => s.sessionId === pc.sessionId)) {
1773
+ if (paneId && pc.id !== paneId) continue;
1774
+ sessions.push({
1775
+ sessionId: pc.sessionId,
1776
+ paneId: pc.id,
1777
+ paneName: pc.name,
1778
+ agent: pc.agent,
1779
+ timestamp: ""
1780
+ });
1781
+ }
1782
+ }
1783
+ }
1784
+ return sessions;
1785
+ }
1786
+ removePaneFromConfig(paneId) {
1787
+ const wsConfig = this.configManager.loadWorkspaceConfig();
1788
+ if (wsConfig) {
1789
+ wsConfig.panes = wsConfig.panes.filter((p) => p.id !== paneId);
1790
+ this.configManager.saveWorkspaceConfig(wsConfig);
1791
+ }
1792
+ }
1793
+ async shutdown() {
1794
+ this.ptyManager.killAll();
1795
+ for (const [paneId] of this.perPaneGitServices) {
1796
+ this.stopPaneGitService(paneId);
1797
+ }
1798
+ await this.worktreeManager.removeAll();
1799
+ }
1800
+ };
1801
+
1802
+ // src/workspace/AgentsYamlWriter.ts
1803
+ import fs4 from "fs";
1804
+ import path5 from "path";
1805
+ import yaml2 from "js-yaml";
1806
+ var AgentsYamlWriter = class {
1807
+ projectDir;
1808
+ debounceTimer = null;
1809
+ constructor(projectDir) {
1810
+ this.projectDir = projectDir;
1811
+ }
1812
+ /**
1813
+ * Write agents.yaml with current pane states. Debounced to avoid excessive writes.
1814
+ */
1815
+ update(panes) {
1816
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
1817
+ this.debounceTimer = setTimeout(() => {
1818
+ this.writeFile(panes);
1819
+ }, 500);
1820
+ }
1821
+ /**
1822
+ * Write immediately without debouncing.
1823
+ */
1824
+ flush(panes) {
1825
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
1826
+ this.writeFile(panes);
1827
+ }
1828
+ writeFile(panes) {
1829
+ const nexusDir = path5.join(this.projectDir, ".nexus");
1830
+ fs4.mkdirSync(nexusDir, { recursive: true });
1831
+ const visible = panes.filter((p) => p.agent !== "__shell__");
1832
+ const data = {
1833
+ updated_at: (/* @__PURE__ */ new Date()).toISOString(),
1834
+ panes: visible.map((p) => {
1835
+ const basePath = p.isolation === "worktree" && p.worktreePath ? p.worktreePath : this.projectDir;
1836
+ return {
1837
+ id: p.id,
1838
+ name: p.name,
1839
+ agent: p.agent,
1840
+ pid: p.pid,
1841
+ status: p.status,
1842
+ isolation: p.isolation || "shared",
1843
+ branch: p.branch || void 0,
1844
+ workdir: p.workdir ? path5.resolve(basePath, p.workdir) : basePath,
1845
+ task: p.task || void 0,
1846
+ model: p.meta.model || void 0,
1847
+ context_used_pct: p.meta.contextUsedPct ?? void 0,
1848
+ cost_usd: p.meta.costUsd ?? void 0,
1849
+ session_id: p.meta.sessionId || void 0,
1850
+ started_at: p.startedAt || void 0
1851
+ };
1852
+ })
1853
+ };
1854
+ const filePath = path5.join(nexusDir, "agents.yaml");
1855
+ fs4.writeFileSync(filePath, yaml2.dump(data, { lineWidth: -1 }));
1856
+ }
1857
+ };
1858
+
1859
+ // src/fs/FsWatcher.ts
1860
+ import fs5 from "fs";
1861
+ import path6 from "path";
1862
+ import { watch as watch2 } from "chokidar";
1863
+ var IGNORED_DIRS = /* @__PURE__ */ new Set([
1864
+ "node_modules",
1865
+ ".git",
1866
+ "dist",
1867
+ ".cache",
1868
+ ".turbo",
1869
+ "__pycache__"
1870
+ ]);
1871
+ var IGNORED_FILES = /* @__PURE__ */ new Set([
1872
+ ".DS_Store",
1873
+ "Thumbs.db"
1874
+ ]);
1875
+ var FsWatcher = class {
1876
+ projectDir;
1877
+ watcher = null;
1878
+ tree = [];
1879
+ debounceTimer = null;
1880
+ listeners = /* @__PURE__ */ new Set();
1881
+ fileChangeListeners = /* @__PURE__ */ new Set();
1882
+ recentChanges = /* @__PURE__ */ new Map();
1883
+ // path → timestamp for dedup
1884
+ constructor(projectDir) {
1885
+ this.projectDir = projectDir;
1886
+ }
1887
+ start() {
1888
+ this.tree = this.buildTree(this.projectDir, 0);
1889
+ this.notifyListeners();
1890
+ this.watcher = watch2(this.projectDir, {
1891
+ ignored: (filePath) => {
1892
+ const basename = path6.basename(filePath);
1893
+ return IGNORED_DIRS.has(basename) || IGNORED_FILES.has(basename);
1894
+ },
1895
+ persistent: true,
1896
+ ignoreInitial: true,
1897
+ depth: 5
1898
+ });
1899
+ const scheduleRebuild = () => {
1900
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
1901
+ this.debounceTimer = setTimeout(() => {
1902
+ const newTree = this.buildTree(this.projectDir, 0);
1903
+ if (this.treeFingerprint(newTree) === this.treeFingerprint(this.tree)) return;
1904
+ this.tree = newTree;
1905
+ this.notifyListeners();
1906
+ }, 300);
1907
+ };
1908
+ const emitFileChange = (eventType, filePath) => {
1909
+ const relativePath = path6.relative(this.projectDir, filePath);
1910
+ if (!relativePath || relativePath.startsWith("..")) return;
1911
+ if (!/\.\w{1,10}$/.test(path6.basename(filePath))) return;
1912
+ const now = Date.now();
1913
+ const lastChange = this.recentChanges.get(relativePath);
1914
+ if (lastChange && now - lastChange < 1e3) return;
1915
+ this.recentChanges.set(relativePath, now);
1916
+ if (this.recentChanges.size > 200) {
1917
+ for (const [key, ts] of this.recentChanges) {
1918
+ if (now - ts > 1e4) this.recentChanges.delete(key);
1919
+ }
1920
+ }
1921
+ const actionMap = { add: "create", change: "edit", unlink: "delete" };
1922
+ const activity = {
1923
+ file: relativePath,
1924
+ action: actionMap[eventType],
1925
+ timestamp: now
1926
+ };
1927
+ for (const listener of this.fileChangeListeners) {
1928
+ listener(activity);
1929
+ }
1930
+ };
1931
+ this.watcher.on("add", (p) => {
1932
+ emitFileChange("add", p);
1933
+ scheduleRebuild();
1934
+ });
1935
+ this.watcher.on("change", (p) => {
1936
+ emitFileChange("change", p);
1937
+ });
1938
+ this.watcher.on("unlink", (p) => {
1939
+ emitFileChange("unlink", p);
1940
+ scheduleRebuild();
1941
+ });
1942
+ this.watcher.on("addDir", scheduleRebuild);
1943
+ this.watcher.on("unlinkDir", scheduleRebuild);
1944
+ this.watcher.on("error", () => {
1945
+ });
1946
+ }
1947
+ getTree() {
1948
+ return this.tree;
1949
+ }
1950
+ onTreeChange(callback) {
1951
+ this.listeners.add(callback);
1952
+ return () => this.listeners.delete(callback);
1953
+ }
1954
+ onFileChange(callback) {
1955
+ this.fileChangeListeners.add(callback);
1956
+ return () => this.fileChangeListeners.delete(callback);
1957
+ }
1958
+ close() {
1959
+ if (this.debounceTimer) clearTimeout(this.debounceTimer);
1960
+ this.watcher?.close();
1961
+ this.watcher = null;
1962
+ }
1963
+ notifyListeners() {
1964
+ for (const listener of this.listeners) {
1965
+ listener(this.tree);
1966
+ }
1967
+ }
1968
+ treeFingerprint(tree) {
1969
+ const parts = [];
1970
+ const walk = (nodes) => {
1971
+ for (const n of nodes) {
1972
+ parts.push(n.path);
1973
+ if (n.children) walk(n.children);
1974
+ }
1975
+ };
1976
+ walk(tree);
1977
+ return parts.join("\n");
1978
+ }
1979
+ buildTree(dirPath, depth) {
1980
+ if (depth > 8) return [];
1981
+ try {
1982
+ const entries = fs5.readdirSync(dirPath, { withFileTypes: true });
1983
+ const nodes = [];
1984
+ for (const entry of entries) {
1985
+ if (IGNORED_DIRS.has(entry.name) || IGNORED_FILES.has(entry.name)) continue;
1986
+ const fullPath = path6.join(dirPath, entry.name);
1987
+ const relativePath = path6.relative(this.projectDir, fullPath);
1988
+ if (entry.isDirectory()) {
1989
+ nodes.push({
1990
+ name: entry.name,
1991
+ path: relativePath,
1992
+ type: "directory",
1993
+ children: this.buildTree(fullPath, depth + 1)
1994
+ });
1995
+ } else if (entry.isFile()) {
1996
+ nodes.push({
1997
+ name: entry.name,
1998
+ path: relativePath,
1999
+ type: "file"
2000
+ });
2001
+ }
2002
+ }
2003
+ nodes.sort((a, b) => {
2004
+ if (a.type !== b.type) return a.type === "directory" ? -1 : 1;
2005
+ return a.name.localeCompare(b.name);
2006
+ });
2007
+ return nodes;
2008
+ } catch {
2009
+ return [];
2010
+ }
2011
+ }
2012
+ };
2013
+
2014
+ // src/ws/handlers.ts
2015
+ function setupWsHandlers(socket, workspaceManager, gitService) {
2016
+ const send = (event) => {
2017
+ if (socket.readyState === socket.OPEN) {
2018
+ socket.send(JSON.stringify(event));
2019
+ }
2020
+ };
2021
+ const state = workspaceManager.getState();
2022
+ send({
2023
+ type: "workspace.state",
2024
+ state
2025
+ });
2026
+ const SCROLLBACK_CHUNK_SIZE = 64 * 1024;
2027
+ for (const pane of state.panes) {
2028
+ const scrollback = workspaceManager.getScrollback(pane.id);
2029
+ if (scrollback) {
2030
+ if (scrollback.length <= SCROLLBACK_CHUNK_SIZE) {
2031
+ send({ type: "terminal.output", paneId: pane.id, data: scrollback });
2032
+ } else {
2033
+ for (let i = 0; i < scrollback.length; i += SCROLLBACK_CHUNK_SIZE) {
2034
+ send({ type: "terminal.output", paneId: pane.id, data: scrollback.slice(i, i + SCROLLBACK_CHUNK_SIZE) });
2035
+ }
2036
+ }
2037
+ }
2038
+ }
2039
+ const paneDiffs = workspaceManager.getPaneDiffs();
2040
+ for (const [paneId, diffs] of paneDiffs) {
2041
+ if (diffs.length > 0) {
2042
+ send({ type: "pane.diff", paneId, diffs });
2043
+ }
2044
+ }
2045
+ gitService?.getBranchInfo().then((info) => send({ type: "git.branchInfo", ...info })).catch(() => {
2046
+ });
2047
+ const cleanup = workspaceManager.onEvents({
2048
+ onTerminalData: (paneId, data) => {
2049
+ send({ type: "terminal.output", paneId, data });
2050
+ },
2051
+ onPaneStatus: (paneId, status) => {
2052
+ send({ type: "pane.status", paneId, status });
2053
+ },
2054
+ onPaneMeta: (paneId, meta) => {
2055
+ send({ type: "pane.meta", paneId, meta });
2056
+ },
2057
+ onPaneAdded: (pane) => {
2058
+ send({ type: "pane.added", pane });
2059
+ },
2060
+ onPaneRemoved: (paneId) => {
2061
+ send({ type: "pane.removed", paneId });
2062
+ },
2063
+ onPaneActivity: (paneId, activity) => {
2064
+ send({ type: "pane.activity", paneId, activity });
2065
+ },
2066
+ onFileActivity: (activity) => {
2067
+ send({ type: "file.activity", activity });
2068
+ },
2069
+ onPaneDiff: (paneId, diffs) => {
2070
+ send({ type: "pane.diff", paneId, diffs });
2071
+ },
2072
+ onFileTree: (tree) => {
2073
+ send({ type: "fs.tree", tree });
2074
+ },
2075
+ onGitDiff: (result) => {
2076
+ send({ type: "git.diff", unstaged: result.unstaged, staged: result.staged });
2077
+ }
2078
+ });
2079
+ socket.on("message", (raw) => {
2080
+ let event;
2081
+ try {
2082
+ event = JSON.parse(raw.toString());
2083
+ } catch {
2084
+ return;
2085
+ }
2086
+ switch (event.type) {
2087
+ case "terminal.input":
2088
+ workspaceManager.writeToPane(event.paneId, event.data);
2089
+ break;
2090
+ case "terminal.resize":
2091
+ workspaceManager.resizePane(event.paneId, event.cols, event.rows);
2092
+ break;
2093
+ case "pane.create":
2094
+ workspaceManager.createPane(event.config).catch((err) => {
2095
+ console.error("pane.create failed:", err);
2096
+ });
2097
+ break;
2098
+ case "pane.close":
2099
+ workspaceManager.closePane(event.paneId).catch((err) => {
2100
+ console.error("pane.close failed:", err);
2101
+ });
2102
+ break;
2103
+ case "pane.restart":
2104
+ workspaceManager.restartPane(event.paneId, event.mode, event.sessionId);
2105
+ break;
2106
+ case "session.list":
2107
+ send({ type: "session.list", paneId: event.paneId, sessions: workspaceManager.getSessionList(event.paneId) });
2108
+ break;
2109
+ case "git.refresh":
2110
+ gitService?.refresh();
2111
+ break;
2112
+ case "git.accept":
2113
+ gitService?.acceptFile(event.file).catch((err) => {
2114
+ console.error("git.accept failed:", err);
2115
+ });
2116
+ break;
2117
+ case "git.accept.all":
2118
+ gitService?.acceptAll().catch((err) => {
2119
+ console.error("git.accept.all failed:", err);
2120
+ });
2121
+ break;
2122
+ case "git.discard":
2123
+ gitService?.discardFile(event.file).catch((err) => {
2124
+ console.error("git.discard failed:", err);
2125
+ });
2126
+ break;
2127
+ case "git.discard.all":
2128
+ gitService?.discardAll().catch((err) => {
2129
+ console.error("git.discard.all failed:", err);
2130
+ });
2131
+ break;
2132
+ case "git.unstage":
2133
+ gitService?.unstageFile(event.file).catch((err) => {
2134
+ console.error("git.unstage failed:", err);
2135
+ });
2136
+ break;
2137
+ case "git.unstage.all":
2138
+ gitService?.unstageAll().catch((err) => {
2139
+ console.error("git.unstage.all failed:", err);
2140
+ });
2141
+ break;
2142
+ case "git.commit":
2143
+ if (gitService) {
2144
+ gitService.commit(event.message).then((summary) => {
2145
+ send({ type: "git.result", action: "commit", success: true, message: summary });
2146
+ return gitService.getBranchInfo();
2147
+ }).then((info) => {
2148
+ send({ type: "git.branchInfo", ...info });
2149
+ }).catch((err) => {
2150
+ send({ type: "git.result", action: "commit", success: false, message: String(err) });
2151
+ });
2152
+ }
2153
+ break;
2154
+ case "git.push":
2155
+ if (gitService) {
2156
+ gitService.push().then((summary) => {
2157
+ send({ type: "git.result", action: "push", success: true, message: summary });
2158
+ return gitService.getBranchInfo();
2159
+ }).then((info) => {
2160
+ send({ type: "git.branchInfo", ...info });
2161
+ }).catch((err) => {
2162
+ send({ type: "git.result", action: "push", success: false, message: String(err) });
2163
+ });
2164
+ }
2165
+ break;
2166
+ case "pane.diff.refresh":
2167
+ workspaceManager.refreshPaneDiff(event.paneId);
2168
+ break;
2169
+ case "workspace.save":
2170
+ break;
2171
+ case "review.comment": {
2172
+ const { paneId: targetPaneId, comment } = event;
2173
+ const targetPane = workspaceManager.getPanes().find((p) => p.id === targetPaneId);
2174
+ if (targetPane && comment.content.trim()) {
2175
+ const msg = [
2176
+ "",
2177
+ `[Review Comment] ${comment.file}:${comment.line}`,
2178
+ comment.content.trim(),
2179
+ ""
2180
+ ].join("\n");
2181
+ workspaceManager.writeToPane(targetPaneId, msg + "\n");
2182
+ }
2183
+ break;
2184
+ }
2185
+ case "broadcast.send":
2186
+ case "task.dispatch":
2187
+ break;
2188
+ }
2189
+ });
2190
+ socket.on("close", () => {
2191
+ cleanup();
2192
+ });
2193
+ }
2194
+
2195
+ // src/deps/DependencyAnalyzer.ts
2196
+ import fs6 from "fs";
2197
+ import path7 from "path";
2198
+ var IMPORT_FROM_RE = /import\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
2199
+ var EXPORT_FROM_RE = /export\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
2200
+ var REQUIRE_RE = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
2201
+ var DYNAMIC_IMPORT_RE = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
2202
+ var JS_TS_EXTENSIONS = /* @__PURE__ */ new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".mts", ".cjs", ".cts"]);
2203
+ var RESOLVE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".mts", "", "/index.ts", "/index.tsx", "/index.js", "/index.jsx"];
2204
+ var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".nuxt", "coverage", ".nexus", ".turbo", ".cache"]);
2205
+ var DependencyAnalyzer = class {
2206
+ projectDir;
2207
+ constructor(projectDir) {
2208
+ this.projectDir = projectDir;
2209
+ }
2210
+ /**
2211
+ * Scan the project directory and build a dependency graph.
2212
+ * Only analyzes JS/TS files with relative imports.
2213
+ */
2214
+ analyze() {
2215
+ const files = this.collectFiles(this.projectDir);
2216
+ const nodes = [];
2217
+ for (const absPath of files) {
2218
+ const relPath = path7.relative(this.projectDir, absPath);
2219
+ const imports = this.extractImports(absPath, relPath);
2220
+ nodes.push({ id: relPath, imports });
2221
+ }
2222
+ return { nodes, root: this.projectDir };
2223
+ }
2224
+ collectFiles(dir, depth = 0) {
2225
+ if (depth > 8) return [];
2226
+ const files = [];
2227
+ let entries;
2228
+ try {
2229
+ entries = fs6.readdirSync(dir, { withFileTypes: true });
2230
+ } catch {
2231
+ return files;
2232
+ }
2233
+ for (const entry of entries) {
2234
+ if (entry.name.startsWith(".") && entry.name !== ".") continue;
2235
+ const fullPath = path7.join(dir, entry.name);
2236
+ if (entry.isDirectory()) {
2237
+ if (SKIP_DIRS.has(entry.name)) continue;
2238
+ files.push(...this.collectFiles(fullPath, depth + 1));
2239
+ } else if (entry.isFile()) {
2240
+ const ext = path7.extname(entry.name);
2241
+ if (JS_TS_EXTENSIONS.has(ext)) {
2242
+ files.push(fullPath);
2243
+ }
2244
+ }
2245
+ }
2246
+ return files;
2247
+ }
2248
+ extractImports(absPath, relPath) {
2249
+ let content;
2250
+ try {
2251
+ content = fs6.readFileSync(absPath, "utf-8");
2252
+ } catch {
2253
+ return [];
2254
+ }
2255
+ const specifiers = /* @__PURE__ */ new Set();
2256
+ const patterns = [IMPORT_FROM_RE, EXPORT_FROM_RE, REQUIRE_RE, DYNAMIC_IMPORT_RE];
2257
+ for (const pattern of patterns) {
2258
+ pattern.lastIndex = 0;
2259
+ let match;
2260
+ while ((match = pattern.exec(content)) !== null) {
2261
+ const spec = match[1];
2262
+ if (spec.startsWith(".")) {
2263
+ specifiers.add(spec);
2264
+ }
2265
+ }
2266
+ }
2267
+ const imports = [];
2268
+ const fileDir = path7.dirname(absPath);
2269
+ for (const spec of specifiers) {
2270
+ const resolved = this.resolveSpecifier(fileDir, spec);
2271
+ if (resolved) {
2272
+ const resolvedRel = path7.relative(this.projectDir, resolved);
2273
+ imports.push(resolvedRel);
2274
+ }
2275
+ }
2276
+ return imports;
2277
+ }
2278
+ resolveSpecifier(fromDir, specifier) {
2279
+ const base = path7.resolve(fromDir, specifier);
2280
+ for (const ext of RESOLVE_EXTENSIONS) {
2281
+ const candidate = base + ext;
2282
+ try {
2283
+ if (fs6.statSync(candidate).isFile()) {
2284
+ return candidate;
2285
+ }
2286
+ } catch {
2287
+ }
2288
+ }
2289
+ return null;
2290
+ }
2291
+ };
2292
+
2293
+ // src/history/SessionRecorder.ts
2294
+ import fs7 from "fs";
2295
+ import path8 from "path";
2296
+ import { execFile as execFile2 } from "child_process";
2297
+ var HISTORY_DIR = ".nexus/history";
2298
+ var SESSIONS_INDEX = "sessions.json";
2299
+ var TERMINAL_BATCH_MS = 200;
2300
+ var MAX_TERMINAL_BYTES_PER_TURN = 256 * 1024;
2301
+ var MAX_SESSIONS = 50;
2302
+ var SessionRecorder = class _SessionRecorder {
2303
+ projectDir;
2304
+ session;
2305
+ sessionDir;
2306
+ activeTurns = /* @__PURE__ */ new Map();
2307
+ // paneId -> current turn
2308
+ turnCounter = 0;
2309
+ retentionDays;
2310
+ constructor(projectDir, projectName, retentionDays = 30) {
2311
+ this.projectDir = projectDir;
2312
+ const sessionId = `s-${Date.now()}`;
2313
+ this.sessionDir = path8.join(projectDir, HISTORY_DIR, sessionId);
2314
+ fs7.mkdirSync(this.sessionDir, { recursive: true });
2315
+ this.retentionDays = retentionDays;
2316
+ this.session = {
2317
+ id: sessionId,
2318
+ startedAt: Date.now(),
2319
+ endedAt: null,
2320
+ projectDir,
2321
+ projectName,
2322
+ panes: [],
2323
+ turns: []
2324
+ // lightweight refs only — full data in turn files
2325
+ };
2326
+ }
2327
+ // ─── Event Hooks (called by WorkspaceManager wiring) ─────
2328
+ onPaneAdded(pane) {
2329
+ if (pane.agent === "__shell__") return;
2330
+ const exists = this.session.panes.find((p) => p.id === pane.id);
2331
+ if (!exists) {
2332
+ this.session.panes.push({
2333
+ id: pane.id,
2334
+ name: pane.name,
2335
+ agent: pane.agent,
2336
+ task: pane.task
2337
+ });
2338
+ }
2339
+ if (pane.status === "running") {
2340
+ this.startTurn(pane.id, pane.name, pane.agent, pane.task);
2341
+ }
2342
+ }
2343
+ onPaneRemoved(paneId) {
2344
+ this.endTurn(paneId);
2345
+ }
2346
+ onPaneStatus(paneId, status, pane) {
2347
+ if (!pane || pane.agent === "__shell__") return;
2348
+ const active = this.activeTurns.get(paneId);
2349
+ if (status === "running" && !active) {
2350
+ this.startTurn(paneId, pane.name, pane.agent, pane.task);
2351
+ } else if ((status === "idle" || status === "stopped" || status === "error") && active) {
2352
+ this.recordStatusEvent(paneId, status);
2353
+ this.endTurn(paneId);
2354
+ } else if (active) {
2355
+ this.recordStatusEvent(paneId, status);
2356
+ }
2357
+ }
2358
+ onPaneMeta(paneId, meta) {
2359
+ const active = this.activeTurns.get(paneId);
2360
+ if (!active) return;
2361
+ const event = {
2362
+ t: Date.now() - active.turn.startedAt,
2363
+ type: "meta",
2364
+ paneId,
2365
+ meta
2366
+ };
2367
+ active.turn.events.push(event);
2368
+ }
2369
+ onTerminalData(paneId, data) {
2370
+ const active = this.activeTurns.get(paneId);
2371
+ if (!active) return;
2372
+ if (active.terminalBytes >= MAX_TERMINAL_BYTES_PER_TURN) return;
2373
+ active.terminalBuffer += data;
2374
+ active.terminalBytes += data.length;
2375
+ if (!active.terminalFlushTimer) {
2376
+ active.terminalFlushTimer = setTimeout(() => {
2377
+ this.flushTerminalBuffer(paneId);
2378
+ }, TERMINAL_BATCH_MS);
2379
+ }
2380
+ }
2381
+ onPaneActivity(paneId, activity) {
2382
+ const active = this.activeTurns.get(paneId);
2383
+ if (!active) return;
2384
+ const event = {
2385
+ t: Date.now() - active.turn.startedAt,
2386
+ type: "activity",
2387
+ paneId,
2388
+ activity: { ...activity }
2389
+ };
2390
+ active.turn.events.push(event);
2391
+ switch (activity.action) {
2392
+ case "read":
2393
+ active.filesRead.add(activity.file);
2394
+ break;
2395
+ case "edit":
2396
+ case "write":
2397
+ active.filesEdited.add(activity.file);
2398
+ break;
2399
+ case "create":
2400
+ active.filesCreated.add(activity.file);
2401
+ break;
2402
+ }
2403
+ if (activity.action === "edit" || activity.action === "write" || activity.action === "create") {
2404
+ this.captureDiff(activity.file).then((diff) => {
2405
+ if (diff) {
2406
+ event.activity = { ...event.activity, diff };
2407
+ }
2408
+ }).catch(() => {
2409
+ });
2410
+ }
2411
+ }
2412
+ /** Also accept file-level activity (from FsWatcher) attributed to the most recently active pane */
2413
+ onFileActivityForReplay(activity) {
2414
+ if (this.activeTurns.size === 0) return;
2415
+ if (this.activeTurns.size !== 1) return;
2416
+ const [paneId, active] = [...this.activeTurns.entries()][0];
2417
+ let recentActivity;
2418
+ for (let i = active.turn.events.length - 1; i >= 0; i--) {
2419
+ const e = active.turn.events[i];
2420
+ if (e.type === "activity" && e.activity?.file === activity.file) {
2421
+ recentActivity = e;
2422
+ break;
2423
+ }
2424
+ }
2425
+ if (recentActivity && Date.now() - active.turn.startedAt - recentActivity.t < 2e3) return;
2426
+ const event = {
2427
+ t: Date.now() - active.turn.startedAt,
2428
+ type: "activity",
2429
+ paneId,
2430
+ activity: { ...activity }
2431
+ };
2432
+ active.turn.events.push(event);
2433
+ if (activity.action === "edit" || activity.action === "create") {
2434
+ this.captureDiff(activity.file).then((diff) => {
2435
+ if (diff) {
2436
+ event.activity = { ...event.activity, diff };
2437
+ }
2438
+ }).catch(() => {
2439
+ });
2440
+ }
2441
+ }
2442
+ // ─── Session Lifecycle ────────────────────────────────────
2443
+ /** Flush all active turns and write session index. Called on shutdown. */
2444
+ flush() {
2445
+ for (const paneId of [...this.activeTurns.keys()]) {
2446
+ this.endTurn(paneId);
2447
+ }
2448
+ this.session.endedAt = Date.now();
2449
+ this.writeSessionFile();
2450
+ this.updateSessionsIndex();
2451
+ }
2452
+ // ─── Query API ────────────────────────────────────────────
2453
+ static listSessions(projectDir) {
2454
+ const indexPath = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2455
+ if (!fs7.existsSync(indexPath)) return [];
2456
+ try {
2457
+ const data = JSON.parse(fs7.readFileSync(indexPath, "utf-8"));
2458
+ return Array.isArray(data) ? data : [];
2459
+ } catch {
2460
+ return [];
2461
+ }
2462
+ }
2463
+ static getSession(projectDir, sessionId) {
2464
+ const sessionPath = path8.join(projectDir, HISTORY_DIR, sessionId, "session.json");
2465
+ if (!fs7.existsSync(sessionPath)) return null;
2466
+ try {
2467
+ return JSON.parse(fs7.readFileSync(sessionPath, "utf-8"));
2468
+ } catch {
2469
+ return null;
2470
+ }
2471
+ }
2472
+ static getTurn(projectDir, sessionId, turnId) {
2473
+ const turnPath = path8.join(projectDir, HISTORY_DIR, sessionId, `${turnId}.json`);
2474
+ if (!fs7.existsSync(turnPath)) return null;
2475
+ try {
2476
+ return JSON.parse(fs7.readFileSync(turnPath, "utf-8"));
2477
+ } catch {
2478
+ return null;
2479
+ }
2480
+ }
2481
+ /** Delete a single session and its directory. Returns true if deleted. */
2482
+ static deleteSession(projectDir, sessionId) {
2483
+ const sessionDir = path8.join(projectDir, HISTORY_DIR, sessionId);
2484
+ if (fs7.existsSync(sessionDir)) {
2485
+ fs7.rmSync(sessionDir, { recursive: true, force: true });
2486
+ }
2487
+ const indexPath = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2488
+ if (fs7.existsSync(indexPath)) {
2489
+ try {
2490
+ let sessions = JSON.parse(fs7.readFileSync(indexPath, "utf-8"));
2491
+ const before = sessions.length;
2492
+ sessions = sessions.filter((s) => s.id !== sessionId);
2493
+ if (sessions.length < before) {
2494
+ fs7.writeFileSync(indexPath, JSON.stringify(sessions, null, 2));
2495
+ return true;
2496
+ }
2497
+ } catch {
2498
+ }
2499
+ }
2500
+ return false;
2501
+ }
2502
+ /** Delete all sessions. Returns the number of sessions deleted. */
2503
+ static deleteAllSessions(projectDir) {
2504
+ const sessions = _SessionRecorder.listSessions(projectDir);
2505
+ let count = 0;
2506
+ for (const session of sessions) {
2507
+ const sessionDir = path8.join(projectDir, HISTORY_DIR, session.id);
2508
+ if (fs7.existsSync(sessionDir)) {
2509
+ fs7.rmSync(sessionDir, { recursive: true, force: true });
2510
+ count++;
2511
+ }
2512
+ }
2513
+ const indexPath = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2514
+ fs7.writeFileSync(indexPath, JSON.stringify([], null, 2));
2515
+ return count;
2516
+ }
2517
+ /**
2518
+ * Passive cleanup: prune sessions exceeding max count or older than retentionDays.
2519
+ * Called automatically when saving a new session.
2520
+ */
2521
+ static pruneOldSessions(projectDir, retentionDays) {
2522
+ const indexPath = path8.join(projectDir, HISTORY_DIR, SESSIONS_INDEX);
2523
+ if (!fs7.existsSync(indexPath)) return 0;
2524
+ let sessions;
2525
+ try {
2526
+ sessions = JSON.parse(fs7.readFileSync(indexPath, "utf-8"));
2527
+ } catch {
2528
+ return 0;
2529
+ }
2530
+ const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
2531
+ const keep = [];
2532
+ const remove = [];
2533
+ for (const s of sessions) {
2534
+ if (s.startedAt < cutoff || keep.length >= MAX_SESSIONS) {
2535
+ remove.push(s);
2536
+ } else {
2537
+ keep.push(s);
2538
+ }
2539
+ }
2540
+ if (remove.length === 0) return 0;
2541
+ for (const s of remove) {
2542
+ const sessionDir = path8.join(projectDir, HISTORY_DIR, s.id);
2543
+ if (fs7.existsSync(sessionDir)) {
2544
+ fs7.rmSync(sessionDir, { recursive: true, force: true });
2545
+ }
2546
+ }
2547
+ fs7.writeFileSync(indexPath, JSON.stringify(keep, null, 2));
2548
+ return remove.length;
2549
+ }
2550
+ // ─── Internal ─────────────────────────────────────────────
2551
+ startTurn(paneId, paneName, agent, task) {
2552
+ if (this.activeTurns.has(paneId)) {
2553
+ this.endTurn(paneId);
2554
+ }
2555
+ const turnId = `turn-${++this.turnCounter}`;
2556
+ const turn = {
2557
+ id: turnId,
2558
+ paneId,
2559
+ paneName,
2560
+ agent,
2561
+ startedAt: Date.now(),
2562
+ endedAt: null,
2563
+ task,
2564
+ events: [],
2565
+ summary: {
2566
+ filesRead: 0,
2567
+ filesEdited: 0,
2568
+ filesCreated: 0,
2569
+ terminalBytes: 0,
2570
+ durationMs: 0
2571
+ }
2572
+ };
2573
+ this.activeTurns.set(paneId, {
2574
+ turn,
2575
+ terminalBuffer: "",
2576
+ terminalFlushTimer: null,
2577
+ terminalBytes: 0,
2578
+ filesRead: /* @__PURE__ */ new Set(),
2579
+ filesEdited: /* @__PURE__ */ new Set(),
2580
+ filesCreated: /* @__PURE__ */ new Set()
2581
+ });
2582
+ }
2583
+ endTurn(paneId) {
2584
+ const active = this.activeTurns.get(paneId);
2585
+ if (!active) return;
2586
+ this.flushTerminalBuffer(paneId);
2587
+ const turn = active.turn;
2588
+ turn.endedAt = Date.now();
2589
+ turn.summary = {
2590
+ filesRead: active.filesRead.size,
2591
+ filesEdited: active.filesEdited.size,
2592
+ filesCreated: active.filesCreated.size,
2593
+ terminalBytes: active.terminalBytes,
2594
+ durationMs: turn.endedAt - turn.startedAt
2595
+ };
2596
+ if (turn.events.length > 0) {
2597
+ this.writeTurnFile(turn);
2598
+ this.session.turns.push({
2599
+ ...turn,
2600
+ events: []
2601
+ // don't duplicate events in session file
2602
+ });
2603
+ }
2604
+ this.activeTurns.delete(paneId);
2605
+ }
2606
+ recordStatusEvent(paneId, status) {
2607
+ const active = this.activeTurns.get(paneId);
2608
+ if (!active) return;
2609
+ active.turn.events.push({
2610
+ t: Date.now() - active.turn.startedAt,
2611
+ type: "status",
2612
+ paneId,
2613
+ status
2614
+ });
2615
+ }
2616
+ flushTerminalBuffer(paneId) {
2617
+ const active = this.activeTurns.get(paneId);
2618
+ if (!active || !active.terminalBuffer) return;
2619
+ if (active.terminalFlushTimer) {
2620
+ clearTimeout(active.terminalFlushTimer);
2621
+ active.terminalFlushTimer = null;
2622
+ }
2623
+ active.turn.events.push({
2624
+ t: Date.now() - active.turn.startedAt,
2625
+ type: "terminal",
2626
+ paneId,
2627
+ data: active.terminalBuffer
2628
+ });
2629
+ active.terminalBuffer = "";
2630
+ }
2631
+ /** Capture git diff for a single file. Returns unified diff string or null. */
2632
+ captureDiff(file) {
2633
+ return new Promise((resolve) => {
2634
+ execFile2("git", ["diff", "--no-color", "-U3", "--", file], {
2635
+ cwd: this.projectDir,
2636
+ timeout: 3e3,
2637
+ maxBuffer: 128 * 1024
2638
+ }, (err, stdout) => {
2639
+ if (stdout && stdout.trim()) {
2640
+ resolve(stdout.trim());
2641
+ return;
2642
+ }
2643
+ execFile2("git", ["diff", "--no-color", "--no-index", "/dev/null", file], {
2644
+ cwd: this.projectDir,
2645
+ timeout: 3e3,
2646
+ maxBuffer: 128 * 1024
2647
+ }, (_err2, stdout2) => {
2648
+ resolve(stdout2?.trim() || null);
2649
+ });
2650
+ });
2651
+ });
2652
+ }
2653
+ writeTurnFile(turn) {
2654
+ const turnPath = path8.join(this.sessionDir, `${turn.id}.json`);
2655
+ fs7.writeFileSync(turnPath, JSON.stringify(turn));
2656
+ }
2657
+ writeSessionFile() {
2658
+ const sessionPath = path8.join(this.sessionDir, "session.json");
2659
+ fs7.writeFileSync(sessionPath, JSON.stringify(this.session, null, 2));
2660
+ }
2661
+ updateSessionsIndex() {
2662
+ const indexPath = path8.join(this.projectDir, HISTORY_DIR, SESSIONS_INDEX);
2663
+ let sessions = [];
2664
+ if (fs7.existsSync(indexPath)) {
2665
+ try {
2666
+ sessions = JSON.parse(fs7.readFileSync(indexPath, "utf-8"));
2667
+ } catch {
2668
+ }
2669
+ }
2670
+ const totalDurationMs = this.session.endedAt ? this.session.endedAt - this.session.startedAt : 0;
2671
+ sessions.unshift({
2672
+ id: this.session.id,
2673
+ startedAt: this.session.startedAt,
2674
+ endedAt: this.session.endedAt,
2675
+ projectName: this.session.projectName,
2676
+ turnCount: this.session.turns.length,
2677
+ paneCount: this.session.panes.length,
2678
+ totalDurationMs
2679
+ });
2680
+ fs7.writeFileSync(indexPath, JSON.stringify(sessions, null, 2));
2681
+ const pruned = _SessionRecorder.pruneOldSessions(this.projectDir, this.retentionDays);
2682
+ if (pruned > 0) {
2683
+ console.log(`[SessionRecorder] Pruned ${pruned} old session(s)`);
2684
+ }
2685
+ }
2686
+ };
2687
+
2688
+ // src/workspace/SessionDiscovery.ts
2689
+ import { execFile as execFile3 } from "child_process";
2690
+ import { promisify as promisify2 } from "util";
2691
+ var execFileAsync2 = promisify2(execFile3);
2692
+ var CACHE_TTL = 3e4;
2693
+ var SessionDiscovery = class {
2694
+ configManager;
2695
+ cache = null;
2696
+ constructor(configManager) {
2697
+ this.configManager = configManager;
2698
+ }
2699
+ async listSessions(agentType = "claudecode") {
2700
+ const now = Date.now();
2701
+ if (this.cache && now - this.cache.ts < CACHE_TTL) {
2702
+ return this.cache.sessions;
2703
+ }
2704
+ const sessions = await this.fetchSessions(agentType);
2705
+ this.cache = { sessions, ts: now };
2706
+ return sessions;
2707
+ }
2708
+ async fetchSessions(agentType) {
2709
+ const agentDef = this.configManager.getAgentDefinition(agentType);
2710
+ if (!agentDef || agentDef.bin !== "claude") {
2711
+ return [];
2712
+ }
2713
+ try {
2714
+ const { stdout } = await execFileAsync2(agentDef.bin, ["sessions", "list", "--output", "json"], {
2715
+ timeout: 1e4,
2716
+ env: { ...process.env }
2717
+ });
2718
+ const parsed = JSON.parse(stdout.trim());
2719
+ const items = Array.isArray(parsed) ? parsed : parsed.sessions || [];
2720
+ return items.map((s) => ({
2721
+ sessionId: s.session_id,
2722
+ summary: s.summary,
2723
+ model: s.model,
2724
+ costUsd: s.cost_usd,
2725
+ numTurns: s.num_turns,
2726
+ createdAt: s.created_at,
2727
+ updatedAt: s.updated_at,
2728
+ projectPath: s.project_path,
2729
+ source: "external"
2730
+ }));
2731
+ } catch (err) {
2732
+ console.warn("[SessionDiscovery] Failed to list sessions:", err.message);
2733
+ return [];
2734
+ }
2735
+ }
2736
+ };
2737
+
2738
+ // src/index.ts
2739
+ var __dirname = path9.dirname(fileURLToPath(import.meta.url));
2740
+ async function startServer(port, projectDir) {
2741
+ const fastify = Fastify({ logger: false });
2742
+ const configManager = new ConfigManager(projectDir);
2743
+ configManager.loadGlobalConfig();
2744
+ const workspaceManager = new WorkspaceManager(configManager);
2745
+ workspaceManager.init();
2746
+ const agentsWriter = new AgentsYamlWriter(projectDir);
2747
+ workspaceManager.onEvents({
2748
+ onPaneAdded: () => agentsWriter.update(workspaceManager.getPanes()),
2749
+ onPaneRemoved: () => agentsWriter.update(workspaceManager.getPanes()),
2750
+ onPaneStatus: () => agentsWriter.update(workspaceManager.getPanes()),
2751
+ onPaneMeta: () => agentsWriter.update(workspaceManager.getPanes())
2752
+ });
2753
+ const wsConfig = configManager.loadWorkspaceConfig();
2754
+ const globalConfig = configManager.loadGlobalConfig();
2755
+ const recorder = new SessionRecorder(projectDir, wsConfig?.name || "Nexus", globalConfig.defaults.history_retention_days);
2756
+ workspaceManager.onEvents({
2757
+ onPaneAdded: (pane) => recorder.onPaneAdded(pane),
2758
+ onPaneRemoved: (paneId) => recorder.onPaneRemoved(paneId),
2759
+ onPaneStatus: (paneId, status) => {
2760
+ const panes = workspaceManager.getPanes();
2761
+ const pane = panes.find((p) => p.id === paneId);
2762
+ recorder.onPaneStatus(paneId, status, pane);
2763
+ },
2764
+ onPaneMeta: (paneId, meta) => recorder.onPaneMeta(paneId, meta),
2765
+ onTerminalData: (paneId, data) => recorder.onTerminalData(paneId, data),
2766
+ onPaneActivity: (paneId, activity) => recorder.onPaneActivity(paneId, activity)
2767
+ });
2768
+ const fsWatcher = new FsWatcher(projectDir);
2769
+ fsWatcher.onTreeChange((tree) => {
2770
+ workspaceManager.emitFileTree(tree);
2771
+ });
2772
+ fsWatcher.onFileChange((activity) => {
2773
+ workspaceManager.emitFileActivity(activity);
2774
+ recorder.onFileActivityForReplay(activity);
2775
+ });
2776
+ try {
2777
+ fsWatcher.start();
2778
+ } catch (err) {
2779
+ console.warn("[FsWatcher] Failed to start file watcher:", err.message);
2780
+ }
2781
+ const gitService = new GitService(projectDir);
2782
+ gitService.onDiffChange((result) => {
2783
+ workspaceManager.emitGitDiff(result);
2784
+ });
2785
+ try {
2786
+ await gitService.start();
2787
+ } catch (err) {
2788
+ console.warn("[GitService] Failed to start git service:", err.message);
2789
+ }
2790
+ await fastify.register(fastifyWebsocket);
2791
+ fastify.get("/nexus-ws", { websocket: true }, (socket, req) => {
2792
+ console.log("[WS] Upgrade request from", req.ip);
2793
+ try {
2794
+ setupWsHandlers(socket, workspaceManager, gitService);
2795
+ const tree = fsWatcher.getTree();
2796
+ if (tree.length > 0) {
2797
+ socket.send(JSON.stringify({ type: "fs.tree", tree }));
2798
+ }
2799
+ const { unstaged, staged } = gitService.getCurrentDiffs();
2800
+ if (unstaged.length > 0 || staged.length > 0) {
2801
+ socket.send(JSON.stringify({ type: "git.diff", unstaged, staged }));
2802
+ }
2803
+ console.log("[WS] Client connected");
2804
+ socket.on("close", (code, reason) => {
2805
+ console.log(`[WS] Client disconnected: code=${code} reason=${reason.toString()}`);
2806
+ });
2807
+ socket.on("error", (err) => {
2808
+ console.error("[WS] Socket error:", err);
2809
+ });
2810
+ } catch (err) {
2811
+ console.error("[WS] Error in connection handler:", err);
2812
+ }
2813
+ });
2814
+ fastify.get("/api/health", async () => {
2815
+ return { status: "ok" };
2816
+ });
2817
+ fastify.get("/api/agents", async () => {
2818
+ return await configManager.checkAgentAvailability();
2819
+ });
2820
+ fastify.get("/api/config", async () => {
2821
+ return configManager.loadGlobalConfig();
2822
+ });
2823
+ fastify.put("/api/config", async (request, reply) => {
2824
+ try {
2825
+ const config = request.body;
2826
+ configManager.updateGlobalConfig(config);
2827
+ return { success: true };
2828
+ } catch (err) {
2829
+ reply.code(400);
2830
+ return { error: "Invalid config" };
2831
+ }
2832
+ });
2833
+ const sessionDiscovery = new SessionDiscovery(configManager);
2834
+ fastify.get("/api/sessions", async (request) => {
2835
+ const { agent } = request.query;
2836
+ const external = await sessionDiscovery.listSessions(agent || "claudecode");
2837
+ const internal = workspaceManager.getSessionList();
2838
+ const seen = new Set(internal.map((s2) => s2.sessionId));
2839
+ const merged = [
2840
+ ...internal.map((s2) => ({
2841
+ sessionId: s2.sessionId,
2842
+ summary: s2.paneName,
2843
+ model: s2.model,
2844
+ costUsd: s2.costUsd,
2845
+ numTurns: void 0,
2846
+ createdAt: s2.timestamp,
2847
+ updatedAt: s2.timestamp,
2848
+ projectPath: void 0,
2849
+ source: "nexus"
2850
+ })),
2851
+ ...external.filter((s2) => !seen.has(s2.sessionId))
2852
+ ];
2853
+ return merged;
2854
+ });
2855
+ fastify.get("/api/replay/sessions", async () => {
2856
+ return SessionRecorder.listSessions(projectDir);
2857
+ });
2858
+ fastify.get("/api/replay/sessions/:sessionId", async (request, reply) => {
2859
+ const { sessionId } = request.params;
2860
+ const session = SessionRecorder.getSession(projectDir, sessionId);
2861
+ if (!session) {
2862
+ reply.code(404);
2863
+ return { error: "Session not found" };
2864
+ }
2865
+ return session;
2866
+ });
2867
+ fastify.get("/api/replay/sessions/:sessionId/turns/:turnId", async (request, reply) => {
2868
+ const { sessionId, turnId } = request.params;
2869
+ const turn = SessionRecorder.getTurn(projectDir, sessionId, turnId);
2870
+ if (!turn) {
2871
+ reply.code(404);
2872
+ return { error: "Turn not found" };
2873
+ }
2874
+ return turn;
2875
+ });
2876
+ fastify.delete("/api/replay/sessions/:sessionId", async (request) => {
2877
+ const { sessionId } = request.params;
2878
+ const deleted = SessionRecorder.deleteSession(projectDir, sessionId);
2879
+ return { success: deleted };
2880
+ });
2881
+ fastify.delete("/api/replay/sessions", async () => {
2882
+ const count = SessionRecorder.deleteAllSessions(projectDir);
2883
+ return { success: true, deleted: count };
2884
+ });
2885
+ let depGraphCache = null;
2886
+ const DEP_CACHE_TTL = 3e4;
2887
+ fastify.get("/api/deps", async () => {
2888
+ const now = Date.now();
2889
+ if (depGraphCache && now - depGraphCache.ts < DEP_CACHE_TTL) {
2890
+ return depGraphCache.graph;
2891
+ }
2892
+ const analyzer = new DependencyAnalyzer(projectDir);
2893
+ const graph = analyzer.analyze();
2894
+ depGraphCache = { graph, ts: now };
2895
+ return graph;
2896
+ });
2897
+ fastify.get("/api/file", async (request, reply) => {
2898
+ const { path: filePath } = request.query;
2899
+ if (!filePath) {
2900
+ reply.code(400);
2901
+ return { error: "Missing path parameter" };
2902
+ }
2903
+ if (filePath.includes("..") || path9.isAbsolute(filePath)) {
2904
+ reply.code(403);
2905
+ return { error: "Invalid path" };
2906
+ }
2907
+ const fullPath = path9.resolve(projectDir, filePath);
2908
+ if (!fullPath.startsWith(projectDir)) {
2909
+ reply.code(403);
2910
+ return { error: "Path traversal not allowed" };
2911
+ }
2912
+ try {
2913
+ const content = fs8.readFileSync(fullPath, "utf-8");
2914
+ return { content, path: filePath };
2915
+ } catch {
2916
+ reply.code(404);
2917
+ return { error: "File not found" };
2918
+ }
2919
+ });
2920
+ fastify.get("/api/file/raw", async (request, reply) => {
2921
+ const { path: filePath } = request.query;
2922
+ if (!filePath) {
2923
+ reply.code(400);
2924
+ return { error: "Missing path parameter" };
2925
+ }
2926
+ if (filePath.includes("..") || path9.isAbsolute(filePath)) {
2927
+ reply.code(403);
2928
+ return { error: "Invalid path" };
2929
+ }
2930
+ const fullPath = path9.resolve(projectDir, filePath);
2931
+ if (!fullPath.startsWith(projectDir)) {
2932
+ reply.code(403);
2933
+ return { error: "Path traversal not allowed" };
2934
+ }
2935
+ if (!fs8.existsSync(fullPath)) {
2936
+ reply.code(404);
2937
+ return { error: "File not found" };
2938
+ }
2939
+ const ext = path9.extname(fullPath).toLowerCase();
2940
+ const mimeMap = {
2941
+ ".png": "image/png",
2942
+ ".jpg": "image/jpeg",
2943
+ ".jpeg": "image/jpeg",
2944
+ ".gif": "image/gif",
2945
+ ".webp": "image/webp",
2946
+ ".ico": "image/x-icon",
2947
+ ".bmp": "image/bmp",
2948
+ ".avif": "image/avif",
2949
+ ".pdf": "application/pdf",
2950
+ ".svg": "image/svg+xml"
2951
+ };
2952
+ const mime = mimeMap[ext] || "application/octet-stream";
2953
+ const stream = fs8.createReadStream(fullPath);
2954
+ reply.type(mime);
2955
+ return reply.send(stream);
2956
+ });
2957
+ const notesPath = path9.join(projectDir, ".nexus", "notes.yaml");
2958
+ fastify.get("/api/notes", async () => {
2959
+ try {
2960
+ const raw = fs8.readFileSync(notesPath, "utf-8");
2961
+ const data = yaml3.load(raw);
2962
+ return { notes: data?.notes || [] };
2963
+ } catch {
2964
+ return { notes: [] };
2965
+ }
2966
+ });
2967
+ fastify.put("/api/notes", async (request, reply) => {
2968
+ try {
2969
+ const { notes } = request.body;
2970
+ fs8.mkdirSync(path9.dirname(notesPath), { recursive: true });
2971
+ fs8.writeFileSync(notesPath, yaml3.dump({ notes }, { lineWidth: -1 }), "utf-8");
2972
+ return { success: true };
2973
+ } catch (err) {
2974
+ reply.code(400);
2975
+ return { error: "Failed to save notes" };
2976
+ }
2977
+ });
2978
+ const webDistPath = path9.resolve(__dirname, "../../web/dist");
2979
+ if (!fs8.existsSync(webDistPath)) {
2980
+ console.warn(` [Warning] Frontend not found at ${webDistPath}`);
2981
+ console.warn(` Run 'pnpm run build:web' to build the frontend, or use dev mode.`);
2982
+ }
2983
+ if (fs8.existsSync(webDistPath)) {
2984
+ await fastify.register(fastifyStatic, {
2985
+ root: webDistPath,
2986
+ prefix: "/"
2987
+ });
2988
+ fastify.setNotFoundHandler((_req, reply) => {
2989
+ reply.sendFile("index.html");
2990
+ });
2991
+ }
2992
+ const shutdown = async () => {
2993
+ console.log("\nShutting down...");
2994
+ recorder.flush();
2995
+ agentsWriter.flush(workspaceManager.getPanes());
2996
+ fsWatcher.close();
2997
+ gitService.close();
2998
+ await workspaceManager.shutdown();
2999
+ await fastify.close();
3000
+ process.exit(0);
3001
+ };
3002
+ process.on("SIGINT", shutdown);
3003
+ process.on("SIGTERM", shutdown);
3004
+ try {
3005
+ await fastify.listen({ port, host: "0.0.0.0" });
3006
+ } catch (err) {
3007
+ if (err.code === "EADDRINUSE") {
3008
+ console.error(`Port ${port} is already in use. Kill the existing process or use a different port:`);
3009
+ console.error(` NEXUS_PORT=7800 pnpm dev`);
3010
+ console.error(` # or find and kill: lsof -i :${port}`);
3011
+ process.exit(1);
3012
+ }
3013
+ throw err;
3014
+ }
3015
+ console.log(`Nexus server running at http://localhost:${port}`);
3016
+ console.log(` Project dir: ${projectDir}`);
3017
+ console.log(` File tree: ${fsWatcher.getTree().length} top-level entries`);
3018
+ const { unstaged: u, staged: s } = gitService.getCurrentDiffs();
3019
+ console.log(` Git diffs: ${u.length} unstaged, ${s.length} staged`);
3020
+ return { fastify, workspaceManager, configManager };
3021
+ }
3022
+
3023
+ // src/cli.ts
3024
+ var nodeVersion = parseInt(process.versions.node.split(".")[0], 10);
3025
+ if (nodeVersion < 22) {
3026
+ console.error(`Error: Nexus requires Node.js >= 22, but you are running v${process.versions.node}`);
3027
+ console.error(` Please upgrade: nvm install 22 && nvm use 22`);
3028
+ process.exit(1);
3029
+ }
3030
+ delete process.env.CLAUDECODE;
3031
+ delete process.env.CLAUDE_CODE;
3032
+ delete process.env.CLAUDE_CODE_ENTRYPOINT;
3033
+ var DEFAULT_PORT = 7700;
3034
+ function printUsage() {
3035
+ console.log(`
3036
+ Usage: nexus [command] [directory]
3037
+
3038
+ Commands:
3039
+ start [dir] Start the Nexus server (default)
3040
+ init [dir] Initialize .nexus/ config in a project
3041
+ status [dir] Show workspace status
3042
+ stop Stop the running server
3043
+
3044
+ Arguments:
3045
+ dir Path to the project directory (defaults to cwd)
3046
+
3047
+ Environment:
3048
+ NEXUS_PORT Server port (default: ${DEFAULT_PORT})
3049
+
3050
+ Examples:
3051
+ nexus # Start in current directory
3052
+ nexus ~/projects/my-app # Start with a specific project
3053
+ nexus start ~/projects/app # Explicit start command
3054
+ nexus init . # Initialize config in cwd
3055
+ `.trimEnd());
3056
+ }
3057
+ function findProjectRoot(startDir) {
3058
+ const home = process.env.HOME || process.env.USERPROFILE || "";
3059
+ let dir = startDir;
3060
+ let bestMatch = startDir;
3061
+ while (dir !== path10.dirname(dir)) {
3062
+ if (fs9.existsSync(path10.join(dir, "pnpm-workspace.yaml"))) {
3063
+ return dir;
3064
+ }
3065
+ if (fs9.existsSync(path10.join(dir, ".git")) || fs9.existsSync(path10.join(dir, ".nexus"))) {
3066
+ bestMatch = dir;
3067
+ }
3068
+ const parent = path10.dirname(dir);
3069
+ if (home && parent === home && dir !== startDir) break;
3070
+ dir = parent;
3071
+ }
3072
+ if (home && bestMatch === home && startDir === home) {
3073
+ console.warn(`Warning: Running Nexus in HOME directory (${home}).`);
3074
+ console.warn(` Consider: nexus <project-path>`);
3075
+ }
3076
+ return bestMatch;
3077
+ }
3078
+ function resolveProjectDir(dirArg) {
3079
+ if (process.env.NEXUS_PROJECT_DIR) {
3080
+ return path10.resolve(process.env.NEXUS_PROJECT_DIR);
3081
+ }
3082
+ if (dirArg) {
3083
+ const resolved = path10.resolve(dirArg);
3084
+ if (!fs9.existsSync(resolved)) {
3085
+ console.error(`Error: directory does not exist: ${resolved}`);
3086
+ process.exit(1);
3087
+ }
3088
+ if (!fs9.statSync(resolved).isDirectory()) {
3089
+ console.error(`Error: not a directory: ${resolved}`);
3090
+ process.exit(1);
3091
+ }
3092
+ return resolved;
3093
+ }
3094
+ return findProjectRoot(process.cwd());
3095
+ }
3096
+ var COMMANDS = /* @__PURE__ */ new Set(["start", "init", "status", "stop", "help"]);
3097
+ async function main() {
3098
+ const args = process.argv.slice(2);
3099
+ let command;
3100
+ let dirArg;
3101
+ if (args.length === 0) {
3102
+ command = "start";
3103
+ } else if (args[0] === "--help" || args[0] === "-h") {
3104
+ command = "help";
3105
+ } else if (COMMANDS.has(args[0])) {
3106
+ command = args[0];
3107
+ dirArg = args[1];
3108
+ } else {
3109
+ command = "start";
3110
+ dirArg = args[0];
3111
+ }
3112
+ if (command === "help") {
3113
+ printUsage();
3114
+ return;
3115
+ }
3116
+ const projectDir = resolveProjectDir(dirArg);
3117
+ switch (command) {
3118
+ case "start": {
3119
+ const port = parseInt(process.env.NEXUS_PORT || String(DEFAULT_PORT), 10);
3120
+ await startServer(port, projectDir);
3121
+ const url = `http://localhost:${port}`;
3122
+ import("open").then((mod) => mod.default(url)).catch(() => {
3123
+ });
3124
+ const { ConfigManager: CM } = await Promise.resolve().then(() => (init_ConfigManager(), ConfigManager_exports));
3125
+ const cm = new CM(projectDir);
3126
+ cm.loadGlobalConfig();
3127
+ cm.checkAgentAvailability().then((availability) => {
3128
+ const missing = Object.entries(availability).filter(([, a]) => !a.installed);
3129
+ if (missing.length > 0) {
3130
+ console.log("\n Optional agents not found:");
3131
+ for (const [key, info] of missing) {
3132
+ console.log(` \u26A0 ${key} (${info.bin}) \u2014 install: ${info.installHint}`);
3133
+ }
3134
+ console.log();
3135
+ }
3136
+ }).catch(() => {
3137
+ });
3138
+ break;
3139
+ }
3140
+ case "init": {
3141
+ const { ConfigManager: ConfigManager2 } = await Promise.resolve().then(() => (init_ConfigManager(), ConfigManager_exports));
3142
+ const configManager = new ConfigManager2(projectDir);
3143
+ configManager.loadGlobalConfig();
3144
+ configManager.initWorkspace();
3145
+ console.log(`Initialized .nexus/ in ${projectDir}`);
3146
+ break;
3147
+ }
3148
+ case "status": {
3149
+ const { ConfigManager: ConfigManager2 } = await Promise.resolve().then(() => (init_ConfigManager(), ConfigManager_exports));
3150
+ const configManager = new ConfigManager2(projectDir);
3151
+ const wsConfig = configManager.loadWorkspaceConfig();
3152
+ if (!wsConfig) {
3153
+ console.log("No .nexus/config.yaml found. Run `nexus init` first.");
3154
+ break;
3155
+ }
3156
+ console.log(`Workspace: ${wsConfig.name}`);
3157
+ console.log(`Panes: ${wsConfig.panes.length}`);
3158
+ for (const pane of wsConfig.panes) {
3159
+ console.log(` - ${pane.id} [${pane.agent}] ${pane.name}${pane.task ? ` \u2014 ${pane.task}` : ""}`);
3160
+ }
3161
+ break;
3162
+ }
3163
+ case "stop": {
3164
+ try {
3165
+ const port = parseInt(process.env.NEXUS_PORT || String(DEFAULT_PORT), 10);
3166
+ const res = await fetch(`http://localhost:${port}/api/health`);
3167
+ if (res.ok) {
3168
+ console.log("Sending shutdown signal...");
3169
+ process.kill(process.pid, "SIGTERM");
3170
+ }
3171
+ } catch {
3172
+ console.log("No running Nexus server found.");
3173
+ }
3174
+ break;
3175
+ }
3176
+ default:
3177
+ console.error(`Unknown command: ${command}`);
3178
+ printUsage();
3179
+ process.exit(1);
3180
+ }
3181
+ }
3182
+ main().catch((err) => {
3183
+ console.error("Fatal error:", err);
3184
+ process.exit(1);
3185
+ });
3186
+ //# sourceMappingURL=cli.mjs.map