excaliagent 0.0.0-stage → 0.2.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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +106 -2
  3. package/bin/excaliagent.js +2 -0
  4. package/dist/server/args.js +52 -0
  5. package/dist/server/cli.js +100 -0
  6. package/dist/server/desktop/desktop.js +150 -0
  7. package/dist/server/desktop/exec.js +27 -0
  8. package/dist/server/desktop/process-table.js +167 -0
  9. package/dist/server/http.js +240 -0
  10. package/dist/server/hub.js +101 -0
  11. package/dist/server/panes/herdr-client.js +220 -0
  12. package/dist/server/panes/herdr-clients.js +37 -0
  13. package/dist/server/panes/herdr.js +248 -0
  14. package/dist/server/panes/none.js +10 -0
  15. package/dist/server/panes/types.js +1 -0
  16. package/dist/server/scene-store.js +61 -0
  17. package/dist/server/sources/claude.js +292 -0
  18. package/dist/server/sources/git.js +65 -0
  19. package/dist/server/sources/log.js +10 -0
  20. package/dist/server/sources/registry.js +79 -0
  21. package/dist/server/sources/transcript.js +204 -0
  22. package/dist/server/sources/types.js +1 -0
  23. package/dist/shared/types.js +9 -0
  24. package/dist/web/assets/Assistant-Bold-gm-uSS1B.woff2 +0 -0
  25. package/dist/web/assets/Assistant-Medium-DrcxCXg3.woff2 +0 -0
  26. package/dist/web/assets/Assistant-Regular-DVxZuzxb.woff2 +0 -0
  27. package/dist/web/assets/Assistant-SemiBold-SCI4bEL9.woff2 +0 -0
  28. package/dist/web/assets/abnfDiagram-VCTEODGH-tmjYj3VY.js +1 -0
  29. package/dist/web/assets/ar-SA-G6X2FPQ2-8ZGGBDRT.js +10 -0
  30. package/dist/web/assets/arc-Y1ARJVZ4.js +1 -0
  31. package/dist/web/assets/architecture-7GRP2DOG-CsvDkWt3.js +1 -0
  32. package/dist/web/assets/architectureDiagram-5GKGNRK7-Bq-1CDhU.js +36 -0
  33. package/dist/web/assets/array-BifhSqXX.js +1 -0
  34. package/dist/web/assets/az-AZ-76LH7QW2-DbdS_q-w.js +1 -0
  35. package/dist/web/assets/bg-BG-XCXSNQG7-CANqXhG1.js +5 -0
  36. package/dist/web/assets/blockDiagram-I7D4REHJ-DtYn-xOl.js +129 -0
  37. package/dist/web/assets/bn-BD-2XOGV67Q-DJdq0B1R.js +5 -0
  38. package/dist/web/assets/c4Diagram-7LVT6UL2-DW6Pzj1e.js +38 -0
  39. package/dist/web/assets/ca-ES-6MX7JW3Y-B7pUqerV.js +8 -0
  40. package/dist/web/assets/channel-gFr9jEgP.js +1 -0
  41. package/dist/web/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  42. package/dist/web/assets/chunk-4HAMMTFA-_AKNu1--.js +62 -0
  43. package/dist/web/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  44. package/dist/web/assets/chunk-75Z2AOVW-BoMl8Mqw.js +2 -0
  45. package/dist/web/assets/chunk-DU6HZSFF-DYHa8ENH.js +127 -0
  46. package/dist/web/assets/chunk-EIO257PC--adgQBo2.js +22 -0
  47. package/dist/web/assets/chunk-F27PBJKO-CvQi01qZ.js +1 -0
  48. package/dist/web/assets/chunk-FOHPRMQF-DD4dKubD.js +161 -0
  49. package/dist/web/assets/chunk-GMAD6QVW-ojaxPuMG.js +72 -0
  50. package/dist/web/assets/chunk-GVQU2GXP-DYAeD1IL.js +1 -0
  51. package/dist/web/assets/chunk-IMKFNOWR-oVPWqbpV.js +231 -0
  52. package/dist/web/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  53. package/dist/web/assets/chunk-L3NEJ4N5-BelAXiIN.js +1 -0
  54. package/dist/web/assets/chunk-OSK3NFVY-CATtjN8l.js +10 -0
  55. package/dist/web/assets/chunk-P2QGCYS3-CbKPCENJ.js +1 -0
  56. package/dist/web/assets/chunk-POPQ4Y6H-BfOCSezn.js +1 -0
  57. package/dist/web/assets/chunk-PWAF6VOD-C7O-JWJt.js +1 -0
  58. package/dist/web/assets/chunk-SHT3W25Y-BfEpPCTX.js +168 -0
  59. package/dist/web/assets/chunk-SVP7TREG-CaeKcCSu.js +88 -0
  60. package/dist/web/assets/chunk-TICWLB2K-Bx6AcLB_.js +206 -0
  61. package/dist/web/assets/chunk-XXDRQBXY-CnbRkih_.js +1 -0
  62. package/dist/web/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  63. package/dist/web/assets/classDiagram-ZZMXUADV-9J2CNLBI.js +1 -0
  64. package/dist/web/assets/classDiagram-v2-VYDZK3BY-9J2CNLBI.js +1 -0
  65. package/dist/web/assets/cose-bilkent-JH36ORCC-lWVj4msc.js +1 -0
  66. package/dist/web/assets/cs-CZ-2BRQDIVT-DF8hZ2mh.js +11 -0
  67. package/dist/web/assets/cynefin-OW5HDTMX-Dol6Weuo.js +1 -0
  68. package/dist/web/assets/cynefinDiagram-5FMLGOSQ-CbOJ2lks.js +62 -0
  69. package/dist/web/assets/cytoscape.esm-BegBNzOV.js +321 -0
  70. package/dist/web/assets/da-DK-5WZEPLOC-CgPdNz6E.js +5 -0
  71. package/dist/web/assets/dagre-D7JM2Yhv.js +1 -0
  72. package/dist/web/assets/dagre-GXQ25YYZ-CcuaBID7.js +4 -0
  73. package/dist/web/assets/de-DE-XR44H4JA-DaVeYWRc.js +8 -0
  74. package/dist/web/assets/defaultLocale-BFoDCU3G.js +1 -0
  75. package/dist/web/assets/diagram-S7CK7UJ4-BR2gIsNx.js +30 -0
  76. package/dist/web/assets/diagram-UQ7AKVKN-DwSZsV-Z.js +41 -0
  77. package/dist/web/assets/diagram-VSXAHHWV-BcJpQHbj.js +3 -0
  78. package/dist/web/assets/diagram-VX7I27RA-CvjRTkwu.js +24 -0
  79. package/dist/web/assets/diagram-Z3DM3KII-vg0-r_P4.js +24 -0
  80. package/dist/web/assets/directory-open-01563666-D4xXyWb_.js +1 -0
  81. package/dist/web/assets/directory-open-4ed118d0-1i309Asm.js +1 -0
  82. package/dist/web/assets/dist-CumnGVmJ.js +1 -0
  83. package/dist/web/assets/dist-D-3cz42D.js +62 -0
  84. package/dist/web/assets/ebnfDiagram-PWID7BFC-DZCaMmMd.js +1 -0
  85. package/dist/web/assets/el-GR-BZB4AONW-nYncjO-L.js +10 -0
  86. package/dist/web/assets/en-B4ZKOASM-DqIz0rJz.js +1 -0
  87. package/dist/web/assets/erDiagram-RLTQ6QDP-DgyrjoaZ.js +99 -0
  88. package/dist/web/assets/es-ES-U4NZUMDT-e3WeRDLl.js +9 -0
  89. package/dist/web/assets/eu-ES-A7QVB2H4-CwXv1t13.js +11 -0
  90. package/dist/web/assets/eventmodeling-NTZA5JFV-DCTWpAwP.js +1 -0
  91. package/dist/web/assets/fa-IR-HGAKTJCU-D-9plwKu.js +8 -0
  92. package/dist/web/assets/fi-FI-Z5N7JZ37-BTK1rs8G.js +6 -0
  93. package/dist/web/assets/file-open-002ab408-BHUWm0Sh.js +1 -0
  94. package/dist/web/assets/file-open-7c801643-DZtJT5zp.js +1 -0
  95. package/dist/web/assets/file-save-3189631c-CO9S4HFW.js +1 -0
  96. package/dist/web/assets/file-save-745eba88-Bwdfz6OZ.js +1 -0
  97. package/dist/web/assets/flowDiagram-HODETNUW--2QUGuZ7.js +1 -0
  98. package/dist/web/assets/fr-FR-RHASNOE6-CAuUMPtN.js +9 -0
  99. package/dist/web/assets/ganttDiagram-EL5Y4UJY-bFfu4BMj.js +292 -0
  100. package/dist/web/assets/gitGraph-4MIJSDKK-DIl4-oqK.js +1 -0
  101. package/dist/web/assets/gitGraphDiagram-WWUBYQGX-ho3FT8XP.js +106 -0
  102. package/dist/web/assets/gl-ES-HMX3MZ6V-DlKPdjtk.js +10 -0
  103. package/dist/web/assets/graphlib-DS17s2tU.js +1 -0
  104. package/dist/web/assets/he-IL-6SHJWFNN-CLyP0zBo.js +10 -0
  105. package/dist/web/assets/hi-IN-IWLTKZ5I-kvhBHNzH.js +4 -0
  106. package/dist/web/assets/hu-HU-A5ZG7DT2-Dtd-o-RJ.js +7 -0
  107. package/dist/web/assets/id-ID-SAP4L64H-VZ3IV-8B.js +10 -0
  108. package/dist/web/assets/image-GAAHSSAO-DWvjHHd3.js +1 -0
  109. package/dist/web/assets/image-blob-reduce.esm-Fj7y_Tzy.js +2 -0
  110. package/dist/web/assets/index-B2hGCC2V.js +212 -0
  111. package/dist/web/assets/index-DtRVLazd.css +1 -0
  112. package/dist/web/assets/info-A6RAGUB7-DN8pwhKt.js +1 -0
  113. package/dist/web/assets/infoDiagram-27XIBGKW-i2JHBJKe.js +2 -0
  114. package/dist/web/assets/init-C-OQMol4.js +1 -0
  115. package/dist/web/assets/ishikawaDiagram-5VMMS53U-DLLdu9-W.js +70 -0
  116. package/dist/web/assets/it-IT-JPQ66NNP-6SULWnRA.js +11 -0
  117. package/dist/web/assets/ja-JP-DBVTYXUO-BbgutQYO.js +8 -0
  118. package/dist/web/assets/journeyDiagram-3NMN7TZE-DsysKJxX.js +139 -0
  119. package/dist/web/assets/kaa-6HZHGXH3-Bc6yhZWQ.js +1 -0
  120. package/dist/web/assets/kab-KAB-ZGHBKWFO-BaICxis-.js +8 -0
  121. package/dist/web/assets/kanban-definition-UXKFOSKX-w3k_6RhT.js +89 -0
  122. package/dist/web/assets/katex-DJhrJSbw.js +257 -0
  123. package/dist/web/assets/kk-KZ-P5N5QNE5-BVsfmPoG.js +1 -0
  124. package/dist/web/assets/km-KH-HSX4SM5Z-BS1M1bUx.js +11 -0
  125. package/dist/web/assets/ko-KR-MTYHY66A-C0x16zQm.js +9 -0
  126. package/dist/web/assets/ku-TR-6OUDTVRD-BWI9z5E8.js +9 -0
  127. package/dist/web/assets/line-BRXlgUrO.js +1 -0
  128. package/dist/web/assets/linear-C9iwaMOi.js +1 -0
  129. package/dist/web/assets/lt-LT-XHIRWOB4-CTDq1zFR.js +3 -0
  130. package/dist/web/assets/lv-LV-5QDEKY6T-h8b-E15B.js +7 -0
  131. package/dist/web/assets/mermaid-parser.core-Da_XEVmR.js +7 -0
  132. package/dist/web/assets/mindmap-definition-YA3MSWOX-DUBdgu2g.js +96 -0
  133. package/dist/web/assets/mr-IN-CRQNXWMA-dTdkz007.js +13 -0
  134. package/dist/web/assets/my-MM-5M5IBNSE-CmYwTRil.js +1 -0
  135. package/dist/web/assets/nb-NO-T6EIAALU-DLLYcK-s.js +10 -0
  136. package/dist/web/assets/nl-NL-IS3SIHDZ-mtv15Mmm.js +8 -0
  137. package/dist/web/assets/nn-NO-6E72VCQL-CJDTT3kB.js +8 -0
  138. package/dist/web/assets/oc-FR-POXYY2M6-DdHJTqH4.js +8 -0
  139. package/dist/web/assets/ordinal-BDEzSJ7C.js +1 -0
  140. package/dist/web/assets/pa-IN-N4M65BXN-DxzzhbLu.js +4 -0
  141. package/dist/web/assets/packet-AYTQ26CC-CzX42SOE.js +1 -0
  142. package/dist/web/assets/path-fybaL0A-.js +1 -0
  143. package/dist/web/assets/pegDiagram-XKGWAZYB-1R2Hd3Th.js +1 -0
  144. package/dist/web/assets/percentages-BXMCSKIN-Br7824d3.js +1 -0
  145. package/dist/web/assets/pica-CXv_OHDX.js +2 -0
  146. package/dist/web/assets/pie-WAS4IAKB-DEI2uslz.js +1 -0
  147. package/dist/web/assets/pieDiagram-E7YTZNPT-DOQnFSrs.js +39 -0
  148. package/dist/web/assets/pl-PL-T2D74RX3-Dpyab0lI.js +9 -0
  149. package/dist/web/assets/pt-BR-5N22H2LF-DOGrUA6Y.js +9 -0
  150. package/dist/web/assets/pt-PT-UZXXM6DQ-DAoNiiOg.js +9 -0
  151. package/dist/web/assets/quadrantDiagram-AXDQQJYC-CQE1OxRJ.js +7 -0
  152. package/dist/web/assets/radar-RG4KPBEZ-B-er2ORT.js +1 -0
  153. package/dist/web/assets/railroad-74A4TZTK-D1Veuu81.js +1 -0
  154. package/dist/web/assets/railroad-abnf-HS5TGJTU-DTxLo5az.js +1 -0
  155. package/dist/web/assets/railroad-ebnf-LZEXJU2U-BmP2IZGe.js +1 -0
  156. package/dist/web/assets/railroad-peg-WCYAUIDC-Do60gMTC.js +1 -0
  157. package/dist/web/assets/railroadDiagram-O6MQD6OU-5I8Qk1dI.js +1 -0
  158. package/dist/web/assets/requirementDiagram-BXWQKSXE-CIIM8YmC.js +84 -0
  159. package/dist/web/assets/ro-RO-JPDTUUEW-DR9yu2MV.js +11 -0
  160. package/dist/web/assets/rolldown-runtime-COiEfhsA.js +1 -0
  161. package/dist/web/assets/rough.esm-Dy-Kn_BL.js +1 -0
  162. package/dist/web/assets/roundRect-D01gJrlt.js +1 -0
  163. package/dist/web/assets/ru-RU-B4JR7IUQ-B9uIhZUd.js +9 -0
  164. package/dist/web/assets/sankeyDiagram-P5KCCOFB-BdGmiiCQ.js +40 -0
  165. package/dist/web/assets/sequenceDiagram-WJ2MYXX4-BeCmgCpG.js +162 -0
  166. package/dist/web/assets/si-LK-N5RQ5JYF-C7DYCsOB.js +1 -0
  167. package/dist/web/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
  168. package/dist/web/assets/sk-SK-C5VTKIMK-BbBBLLCs.js +6 -0
  169. package/dist/web/assets/sl-SI-NN7IZMDC-BIaW2Iuj.js +6 -0
  170. package/dist/web/assets/src-BWF7jMDv.js +1 -0
  171. package/dist/web/assets/stateDiagram-D77RDMKH-CmENpSfH.js +1 -0
  172. package/dist/web/assets/stateDiagram-v2-MP3YSRHH-BWgn52cG.js +1 -0
  173. package/dist/web/assets/subset-shared.chunk-d2GNuFHV.js +1 -0
  174. package/dist/web/assets/subset-worker.chunk-Bi16Jh4x.js +1 -0
  175. package/dist/web/assets/sv-SE-XGPEYMSR-D5A1U3nk.js +10 -0
  176. package/dist/web/assets/swimlanes-42K2YHIH-_ymwQg8o.js +1 -0
  177. package/dist/web/assets/swimlanesDiagram-VR7AAH4N-fzrjtpfS.js +8 -0
  178. package/dist/web/assets/ta-IN-2NMHFXQM-BDzXUwiE.js +9 -0
  179. package/dist/web/assets/th-TH-HPSO5L25-DdVnWuWW.js +2 -0
  180. package/dist/web/assets/timeline-definition-24CTP7MA-CRcZde4k.js +120 -0
  181. package/dist/web/assets/tr-TR-DEFEU3FU-BQwfGN3o.js +7 -0
  182. package/dist/web/assets/treeView-Q6P3EWNA-BHZZuSXr.js +1 -0
  183. package/dist/web/assets/treemap-WGGIJYW6-DAzotEQZ.js +1 -0
  184. package/dist/web/assets/uk-UA-QMV73CPH-CFYnWqHD.js +6 -0
  185. package/dist/web/assets/vennDiagram-4TSXK5OY-BsNz9Qnn.js +34 -0
  186. package/dist/web/assets/vi-VN-M7AON7JQ-DO3BySmm.js +5 -0
  187. package/dist/web/assets/wardley-WFR3VGLG-Cc2r8Vp8.js +1 -0
  188. package/dist/web/assets/wardleyDiagram-VM6X3IG4-47UvJO4N.js +78 -0
  189. package/dist/web/assets/xychartDiagram-S5SC5T6Z-CVl63zr-.js +7 -0
  190. package/dist/web/assets/zh-CN-LNUGB5OW-DKJ2CF-O.js +10 -0
  191. package/dist/web/assets/zh-HK-E62DVLB3-DHW8OH5j.js +1 -0
  192. package/dist/web/assets/zh-TW-RAJ6MFWO-B9cMDifZ.js +9 -0
  193. package/dist/web/fonts/Assistant/Assistant-Bold.woff2 +0 -0
  194. package/dist/web/fonts/Assistant/Assistant-Medium.woff2 +0 -0
  195. package/dist/web/fonts/Assistant/Assistant-Regular.woff2 +0 -0
  196. package/dist/web/fonts/Assistant/Assistant-SemiBold.woff2 +0 -0
  197. package/dist/web/fonts/Cascadia/CascadiaCode-Regular.woff2 +0 -0
  198. package/dist/web/fonts/ComicShanns/ComicShanns-Regular-279a7b317d12eb88de06167bd672b4b4.woff2 +0 -0
  199. package/dist/web/fonts/ComicShanns/ComicShanns-Regular-6e066e8de2ac57ea9283adb9c24d7f0c.woff2 +0 -0
  200. package/dist/web/fonts/ComicShanns/ComicShanns-Regular-dc6a8806fa96795d7b3be5026f989a17.woff2 +0 -0
  201. package/dist/web/fonts/ComicShanns/ComicShanns-Regular-fcb0fc02dcbee4c9846b3e2508668039.woff2 +0 -0
  202. package/dist/web/fonts/Excalifont/Excalifont-Regular-349fac6ca4700ffec595a7150a0d1e1d.woff2 +0 -0
  203. package/dist/web/fonts/Excalifont/Excalifont-Regular-3f2c5db56cc93c5a6873b1361d730c16.woff2 +0 -0
  204. package/dist/web/fonts/Excalifont/Excalifont-Regular-41b173a47b57366892116a575a43e2b6.woff2 +0 -0
  205. package/dist/web/fonts/Excalifont/Excalifont-Regular-623ccf21b21ef6b3a0d87738f77eb071.woff2 +0 -0
  206. package/dist/web/fonts/Excalifont/Excalifont-Regular-a88b72a24fb54c9f94e3b5fdaa7481c9.woff2 +0 -0
  207. package/dist/web/fonts/Excalifont/Excalifont-Regular-b9dcf9d2e50a1eaf42fc664b50a3fd0d.woff2 +0 -0
  208. package/dist/web/fonts/Excalifont/Excalifont-Regular-be310b9bcd4f1a43f571c46df7809174.woff2 +0 -0
  209. package/dist/web/fonts/Liberation/LiberationSans-Regular.woff2 +0 -0
  210. package/dist/web/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYE98RXi4EwSsbg.woff2 +0 -0
  211. package/dist/web/fonts/Lilita/Lilita-Regular-i7dPIFZ9Zz-WBtRtedDbYEF8RXi4EwQ.woff2 +0 -0
  212. package/dist/web/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTA3j6zbXWjgevT5.woff2 +0 -0
  213. package/dist/web/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTQ3j6zbXWjgeg.woff2 +0 -0
  214. package/dist/web/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTk3j6zbXWjgevT5.woff2 +0 -0
  215. package/dist/web/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTo3j6zbXWjgevT5.woff2 +0 -0
  216. package/dist/web/fonts/Nunito/Nunito-Regular-XRXI3I6Li01BKofiOc5wtlZ2di8HDIkhdTs3j6zbXWjgevT5.woff2 +0 -0
  217. package/dist/web/fonts/Virgil/Virgil-Regular.woff2 +0 -0
  218. package/dist/web/index.html +15 -0
  219. package/package.json +68 -4
@@ -0,0 +1,240 @@
1
+ import { createServer } from "node:http";
2
+ import { readFile, stat } from "node:fs/promises";
3
+ import { extname, join, normalize, sep } from "node:path";
4
+ import { timingSafeEqual } from "node:crypto";
5
+ import { SESSION_ID } from "./hub.js";
6
+ const COOKIE = "excaliagent_token";
7
+ const MAX_BODY = 50 * 1024 * 1024;
8
+ const TYPES = {
9
+ ".html": "text/html; charset=utf-8",
10
+ ".js": "text/javascript; charset=utf-8",
11
+ ".mjs": "text/javascript; charset=utf-8",
12
+ ".css": "text/css; charset=utf-8",
13
+ ".json": "application/json",
14
+ ".svg": "image/svg+xml",
15
+ ".png": "image/png",
16
+ ".ico": "image/x-icon",
17
+ ".woff2": "font/woff2",
18
+ ".woff": "font/woff",
19
+ ".ttf": "font/ttf",
20
+ ".wasm": "application/wasm",
21
+ };
22
+ class HttpError extends Error {
23
+ status;
24
+ constructor(status, message) {
25
+ super(message);
26
+ this.status = status;
27
+ }
28
+ }
29
+ function sameToken(a, b) {
30
+ if (!a)
31
+ return false;
32
+ const x = Buffer.from(a);
33
+ const y = Buffer.from(b);
34
+ return x.length === y.length && timingSafeEqual(x, y);
35
+ }
36
+ function cookieToken(req) {
37
+ for (const part of (req.headers.cookie ?? "").split(";")) {
38
+ const [k, ...v] = part.trim().split("=");
39
+ if (k === COOKIE)
40
+ return v.join("=");
41
+ }
42
+ return undefined;
43
+ }
44
+ function json(res, status, body) {
45
+ res.writeHead(status, { "content-type": "application/json", "cache-control": "no-store" });
46
+ res.end(JSON.stringify(body));
47
+ }
48
+ function text(res, status, body) {
49
+ res.writeHead(status, { "content-type": "text/plain; charset=utf-8" });
50
+ res.end(body);
51
+ }
52
+ async function readJson(req) {
53
+ if (!(req.headers["content-type"] ?? "").startsWith("application/json")) {
54
+ throw new HttpError(415, "expected application/json");
55
+ }
56
+ const chunks = [];
57
+ let size = 0;
58
+ for await (const chunk of req) {
59
+ size += chunk.length;
60
+ if (size > MAX_BODY)
61
+ throw new HttpError(413, "body too large");
62
+ chunks.push(chunk);
63
+ }
64
+ try {
65
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
66
+ }
67
+ catch {
68
+ throw new HttpError(400, "invalid JSON");
69
+ }
70
+ }
71
+ export function createAppServer(opts) {
72
+ const host = opts.host ?? "127.0.0.1";
73
+ let port = 0;
74
+ const allowedHosts = () => new Set([`${host}:${port}`, `127.0.0.1:${port}`, `localhost:${port}`]);
75
+ const allowedOrigins = () => new Set([...allowedHosts()].map((h) => `http://${h}`));
76
+ async function serveStatic(req, res, pathname) {
77
+ const root = opts.webRoot;
78
+ if (!root)
79
+ return text(res, 404, "not found");
80
+ let rel;
81
+ try {
82
+ rel = decodeURIComponent(pathname);
83
+ }
84
+ catch {
85
+ return text(res, 400, "bad path");
86
+ }
87
+ if (rel === "/" || rel === "")
88
+ rel = "/index.html";
89
+ const file = normalize(join(root, rel));
90
+ if (!file.startsWith(normalize(root) + sep))
91
+ return text(res, 404, "not found");
92
+ try {
93
+ if (!(await stat(file)).isFile())
94
+ return text(res, 404, "not found");
95
+ const body = await readFile(file);
96
+ res.writeHead(200, {
97
+ "content-type": TYPES[extname(file)] ?? "application/octet-stream",
98
+ "cache-control": rel.startsWith("/assets/") ? "public, max-age=31536000, immutable" : "no-store",
99
+ });
100
+ res.end(req.method === "HEAD" ? undefined : body);
101
+ }
102
+ catch {
103
+ text(res, 404, "not found");
104
+ }
105
+ }
106
+ function events(req, res) {
107
+ res.writeHead(200, {
108
+ "content-type": "text/event-stream",
109
+ "cache-control": "no-store",
110
+ connection: "keep-alive",
111
+ });
112
+ const send = (sessions) => res.write(`event: sessions\ndata: ${JSON.stringify(sessions)}\n\n`);
113
+ send(opts.hub.sessions());
114
+ const unsubscribe = opts.hub.subscribe(send);
115
+ const heartbeat = setInterval(() => res.write(": ping\n\n"), 25_000);
116
+ req.on("close", () => {
117
+ clearInterval(heartbeat);
118
+ unsubscribe();
119
+ });
120
+ }
121
+ async function api(req, res, pathname) {
122
+ const route = `${req.method} ${pathname}`;
123
+ switch (route) {
124
+ case "GET /api/sessions":
125
+ return json(res, 200, opts.hub.sessions());
126
+ case "GET /api/events":
127
+ return events(req, res);
128
+ case "GET /api/picker":
129
+ return json(res, 200, await opts.hub.picker());
130
+ case "POST /api/lookup": {
131
+ const body = (await readJson(req));
132
+ if (!Array.isArray(body?.ids) || !body.ids.every((i) => typeof i === "string")) {
133
+ throw new HttpError(400, "ids must be a string array");
134
+ }
135
+ return json(res, 200, await opts.hub.lookup(body.ids));
136
+ }
137
+ case "GET /api/scene":
138
+ return json(res, 200, await opts.scenes.load());
139
+ case "PUT /api/scene": {
140
+ const body = (await readJson(req));
141
+ if (!body || !Array.isArray(body.elements))
142
+ throw new HttpError(400, "elements must be an array");
143
+ const appState = body.appState && typeof body.appState === "object" ? body.appState : {};
144
+ await opts.scenes.save({ elements: body.elements, appState: appState });
145
+ res.writeHead(204).end();
146
+ return;
147
+ }
148
+ case "POST /api/focus": {
149
+ const body = (await readJson(req));
150
+ if (typeof body?.sessionId !== "string")
151
+ throw new HttpError(400, "sessionId required");
152
+ if (await opts.hub.focus(body.sessionId)) {
153
+ res.writeHead(204).end();
154
+ return;
155
+ }
156
+ return json(res, 409, { error: "no pane" });
157
+ }
158
+ case "POST /api/resume": {
159
+ const body = (await readJson(req));
160
+ if (typeof body?.sessionId !== "string" || !SESSION_ID.test(body.sessionId)) {
161
+ throw new HttpError(400, "sessionId must be a session UUID");
162
+ }
163
+ const result = await opts.hub.resume(body.sessionId);
164
+ if (result === "resumed") {
165
+ res.writeHead(204).end();
166
+ return;
167
+ }
168
+ if (result === "unknown")
169
+ return json(res, 404, { error: "unknown session" });
170
+ return json(res, 409, { error: result === "live" ? "live" : "no herdr" });
171
+ }
172
+ default:
173
+ return json(res, 404, { error: "not found" });
174
+ }
175
+ }
176
+ async function handle(req, res) {
177
+ if (!allowedHosts().has(req.headers.host ?? ""))
178
+ return text(res, 421, "unexpected Host header");
179
+ const url = new URL(req.url ?? "/", "http://localhost");
180
+ const method = req.method ?? "GET";
181
+ if (method !== "GET" && method !== "HEAD") {
182
+ const origin = req.headers.origin;
183
+ if (origin !== undefined && !allowedOrigins().has(origin))
184
+ return text(res, 403, "cross-origin request");
185
+ }
186
+ const queryToken = url.searchParams.get("token");
187
+ if (queryToken !== null && method === "GET" && !url.pathname.startsWith("/api/")) {
188
+ if (!sameToken(queryToken, opts.token))
189
+ return text(res, 401, "bad token");
190
+ res.writeHead(302, {
191
+ location: "/",
192
+ "set-cookie": `${COOKIE}=${opts.token}; HttpOnly; SameSite=Strict; Path=/`,
193
+ });
194
+ res.end();
195
+ return;
196
+ }
197
+ if (!sameToken(cookieToken(req), opts.token)) {
198
+ return text(res, 401, "excaliagent: open the URL printed in the terminal (it carries the access token).");
199
+ }
200
+ if (url.pathname.startsWith("/api/"))
201
+ return api(req, res, url.pathname);
202
+ if (opts.middleware)
203
+ return opts.middleware(req, res, () => text(res, 404, "not found"));
204
+ return serveStatic(req, res, url.pathname);
205
+ }
206
+ const server = createServer((req, res) => {
207
+ handle(req, res).catch((err) => {
208
+ const status = err instanceof HttpError ? err.status : 500;
209
+ if (status === 500)
210
+ console.error("excaliagent:", err);
211
+ if (!res.headersSent)
212
+ json(res, status, { error: err.message });
213
+ else
214
+ res.end();
215
+ });
216
+ });
217
+ return {
218
+ server,
219
+ get port() {
220
+ return port;
221
+ },
222
+ listen(p) {
223
+ return new Promise((resolve, reject) => {
224
+ server.once("error", reject);
225
+ server.listen(p, host, () => {
226
+ server.off("error", reject);
227
+ const addr = server.address();
228
+ port = typeof addr === "object" && addr ? addr.port : p;
229
+ resolve();
230
+ });
231
+ });
232
+ },
233
+ close() {
234
+ return new Promise((resolve) => {
235
+ server.close(() => resolve());
236
+ server.closeAllConnections();
237
+ });
238
+ },
239
+ };
240
+ }
@@ -0,0 +1,101 @@
1
+ /** Claude session ids are UUIDs; anything else is never put into a command. */
2
+ export const SESSION_ID = /^[0-9a-f-]{36}$/i;
3
+ const LABEL_MAX = 40;
4
+ function resumeLabel(s) {
5
+ const title = s.title?.trim();
6
+ if (!title)
7
+ return s.repo;
8
+ const chars = [...title];
9
+ return chars.length <= LABEL_MAX ? title : chars.slice(0, LABEL_MAX - 1).join("") + "…";
10
+ }
11
+ /** Joins a Source with a PaneLink and fans the result out to subscribers. */
12
+ export function createHub({ source, panes }) {
13
+ let raw = [];
14
+ let current = [];
15
+ let serialized = "[]";
16
+ const listeners = new Set();
17
+ const cleanups = [];
18
+ const withPane = (s) => {
19
+ const pane = s.pid !== undefined ? panes.match(s.pid) : undefined;
20
+ const { pane: _old, ...rest } = s;
21
+ return pane ? { ...rest, pane } : rest;
22
+ };
23
+ function recompute() {
24
+ const next = raw.map(withPane);
25
+ const json = JSON.stringify(next);
26
+ if (json === serialized)
27
+ return;
28
+ current = next;
29
+ serialized = json;
30
+ for (const cb of listeners)
31
+ cb(current);
32
+ }
33
+ function setRaw(next) {
34
+ const ids = (l) => l.map((s) => `${s.id}:${s.pid}`).join(",");
35
+ const membershipChanged = ids(next) !== ids(raw);
36
+ raw = next;
37
+ recompute();
38
+ if (membershipChanged)
39
+ void panes.refresh?.().catch(() => { });
40
+ }
41
+ return {
42
+ async start() {
43
+ raw = await source.list();
44
+ current = raw.map(withPane);
45
+ serialized = JSON.stringify(current);
46
+ cleanups.push(source.watch(setRaw));
47
+ cleanups.push(panes.onChange(recompute));
48
+ },
49
+ stop() {
50
+ for (const c of cleanups.splice(0))
51
+ c();
52
+ listeners.clear();
53
+ },
54
+ sessions: () => current,
55
+ subscribe(cb) {
56
+ listeners.add(cb);
57
+ return () => listeners.delete(cb);
58
+ },
59
+ async lookup(ids) {
60
+ const out = [];
61
+ for (const id of ids) {
62
+ const live = current.find((s) => s.id === id);
63
+ const found = live ?? (await source.get(id));
64
+ if (found)
65
+ out.push(live ? found : withPane(found));
66
+ }
67
+ return out;
68
+ },
69
+ async picker() {
70
+ const live = new Set(current.map((s) => s.id));
71
+ return (await source.listDormant()).filter((s) => !s.live && !live.has(s.id));
72
+ },
73
+ async resume(sessionId) {
74
+ if (!SESSION_ID.test(sessionId))
75
+ return "unknown";
76
+ if (current.some((s) => s.id === sessionId))
77
+ return "live";
78
+ const s = await source.get(sessionId);
79
+ if (!s)
80
+ return "unknown";
81
+ if (s.live)
82
+ return "live";
83
+ if (!panes.resume || !panes.available())
84
+ return "no-herdr";
85
+ await panes.resume({
86
+ cwd: s.cwd,
87
+ ...(s.repoRoot !== undefined ? { repoRoot: s.repoRoot } : {}),
88
+ command: `claude --resume ${s.id}`,
89
+ label: resumeLabel(s),
90
+ });
91
+ return "resumed";
92
+ },
93
+ async focus(sessionId) {
94
+ const pane = current.find((s) => s.id === sessionId)?.pane;
95
+ if (!pane)
96
+ return false;
97
+ await panes.focus(pane.paneId);
98
+ return true;
99
+ },
100
+ };
101
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * Minimal herdr socket client (verified against herdr 0.9.0, protocol 22).
3
+ *
4
+ * Wire: newline-delimited JSON over a Unix stream socket.
5
+ * request {"id","method","params"}
6
+ * response {"id","result":{"type",...}} | {"id","error":{"code","message"}}
7
+ * event {"event":"pane_created","data":{"type":"pane_created",...}} (no id)
8
+ *
9
+ * herdr serves ONE request per connection: a second line on the same
10
+ * connection resets it, including on an events.subscribe connection. So this
11
+ * client keeps one long-lived subscription connection (events, reconnect with
12
+ * backoff, `connected()`), and opens a short connection per request.
13
+ * Malformed requests get an error with id "" — on a one-shot connection any
14
+ * reply is ours, so it is accepted.
15
+ */
16
+ import { existsSync } from "node:fs";
17
+ import net from "node:net";
18
+ export class HerdrError extends Error {
19
+ code;
20
+ constructor(code, message) {
21
+ super(message);
22
+ this.name = "HerdrError";
23
+ this.code = code;
24
+ }
25
+ }
26
+ /** Parse one wire line; undefined for blank or unrecognised lines. */
27
+ export function parseLine(line) {
28
+ let msg;
29
+ try {
30
+ msg = JSON.parse(line);
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ if (!msg || typeof msg !== "object")
36
+ return undefined;
37
+ const m = msg;
38
+ if (m.error && typeof m.error === "object") {
39
+ const e = m.error;
40
+ return { kind: "error", id: String(m.id ?? ""), code: String(e.code ?? "unknown"), message: String(e.message ?? "") };
41
+ }
42
+ if ("result" in m)
43
+ return { kind: "response", id: String(m.id ?? ""), result: m.result };
44
+ if (typeof m.event === "string") {
45
+ const data = m.data && typeof m.data === "object" ? m.data : {};
46
+ return { kind: "event", event: { event: m.event, data } };
47
+ }
48
+ return undefined;
49
+ }
50
+ const defaultConnect = (p) => net.createConnection(p);
51
+ function onLines(sock, cb) {
52
+ let buf = "";
53
+ sock.setEncoding("utf8");
54
+ sock.on("data", (chunk) => {
55
+ buf += chunk;
56
+ let i;
57
+ while ((i = buf.indexOf("\n")) >= 0) {
58
+ const line = buf.slice(0, i);
59
+ buf = buf.slice(i + 1);
60
+ if (line.trim())
61
+ cb(line);
62
+ }
63
+ });
64
+ }
65
+ let seq = 0;
66
+ const nextId = (prefix) => `${prefix}-${process.pid}-${++seq}`;
67
+ /** One request on its own connection. Never throws synchronously. */
68
+ export function herdrRequest(socketPath, method, params = {}, opts = {}) {
69
+ const { timeoutMs = 5000, connect = defaultConnect } = opts;
70
+ return new Promise((resolve, reject) => {
71
+ if (!existsSync(socketPath)) {
72
+ reject(new HerdrError("unavailable", `herdr socket not found: ${socketPath}`));
73
+ return;
74
+ }
75
+ const id = nextId("ea");
76
+ let sock;
77
+ try {
78
+ sock = connect(socketPath);
79
+ }
80
+ catch (e) {
81
+ reject(new HerdrError("unavailable", String(e)));
82
+ return;
83
+ }
84
+ const untrack = opts.track?.(sock);
85
+ let done = false;
86
+ const finish = (err, value) => {
87
+ if (done)
88
+ return;
89
+ done = true;
90
+ clearTimeout(timer);
91
+ untrack?.();
92
+ sock.destroy();
93
+ if (err)
94
+ reject(err);
95
+ else
96
+ resolve(value);
97
+ };
98
+ const timer = setTimeout(() => finish(new HerdrError("timeout", `${method} timed out after ${timeoutMs} ms`)), timeoutMs);
99
+ sock.on("error", (e) => finish(new HerdrError("unavailable", `${method}: ${e.code ?? e.message}`)));
100
+ sock.on("close", () => finish(new HerdrError("closed", `${method}: connection closed without a reply`)));
101
+ onLines(sock, (line) => {
102
+ const msg = parseLine(line);
103
+ if (!msg || msg.kind === "event")
104
+ return;
105
+ if (msg.id !== id && msg.id !== "")
106
+ return;
107
+ if (msg.kind === "error")
108
+ finish(new HerdrError(msg.code, msg.message));
109
+ else
110
+ finish(undefined, msg.result);
111
+ });
112
+ sock.write(JSON.stringify({ id, method, params }) + "\n");
113
+ });
114
+ }
115
+ export function createHerdrClient(opts) {
116
+ const { socketPath, requestTimeoutMs = 5000, retryMinMs = 250, retryMaxMs = 5000 } = opts;
117
+ const connect = opts.connect ?? defaultConnect;
118
+ let closed = false;
119
+ let isConnected = false;
120
+ let sub;
121
+ let retryTimer;
122
+ let delay = retryMinMs;
123
+ const inflight = new Set();
124
+ const scheduleRetry = () => {
125
+ if (closed || retryTimer)
126
+ return;
127
+ retryTimer = setTimeout(() => {
128
+ retryTimer = undefined;
129
+ start();
130
+ }, delay);
131
+ retryTimer.unref?.();
132
+ delay = Math.min(delay * 2, retryMaxMs);
133
+ };
134
+ const start = () => {
135
+ if (closed)
136
+ return;
137
+ if (!existsSync(socketPath)) {
138
+ scheduleRetry();
139
+ return;
140
+ }
141
+ let sock;
142
+ try {
143
+ sock = connect(socketPath);
144
+ }
145
+ catch {
146
+ scheduleRetry();
147
+ return;
148
+ }
149
+ sub = sock;
150
+ const subId = nextId("ea-sub");
151
+ let acked = false;
152
+ sock.on("error", () => { });
153
+ sock.on("close", () => {
154
+ if (sub === sock)
155
+ sub = undefined;
156
+ const was = isConnected && acked;
157
+ if (acked)
158
+ isConnected = false;
159
+ if (was)
160
+ opts.onDisconnect?.();
161
+ scheduleRetry();
162
+ });
163
+ onLines(sock, (line) => {
164
+ const msg = parseLine(line);
165
+ if (!msg)
166
+ return;
167
+ if (msg.kind === "event") {
168
+ if (msg.event.event === "events_lost")
169
+ opts.onEventsLost?.();
170
+ else
171
+ opts.onEvent?.(msg.event);
172
+ return;
173
+ }
174
+ if (msg.kind === "error") {
175
+ if (msg.code === "events_lost")
176
+ opts.onEventsLost?.();
177
+ else if (!acked)
178
+ sock.destroy(); // subscribe rejected: retry later
179
+ return;
180
+ }
181
+ if (!acked && (msg.id === subId || msg.id === "")) {
182
+ acked = true;
183
+ isConnected = true;
184
+ delay = retryMinMs;
185
+ opts.onConnect?.();
186
+ }
187
+ });
188
+ sock.write(JSON.stringify({ id: subId, method: "events.subscribe", params: { subscriptions: opts.subscriptions } }) + "\n");
189
+ };
190
+ start();
191
+ return {
192
+ request(method, params = {}) {
193
+ if (closed)
194
+ return Promise.reject(new HerdrError("closed", "herdr client closed"));
195
+ return herdrRequest(socketPath, method, params, {
196
+ timeoutMs: requestTimeoutMs,
197
+ connect,
198
+ track: (s) => {
199
+ inflight.add(s);
200
+ return () => inflight.delete(s);
201
+ },
202
+ });
203
+ },
204
+ connected: () => isConnected && !closed,
205
+ close() {
206
+ if (closed)
207
+ return;
208
+ closed = true;
209
+ isConnected = false;
210
+ if (retryTimer)
211
+ clearTimeout(retryTimer);
212
+ retryTimer = undefined;
213
+ sub?.destroy();
214
+ sub = undefined;
215
+ for (const s of inflight)
216
+ s.destroy();
217
+ inflight.clear();
218
+ },
219
+ };
220
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Find herdr client processes (the TUI attached to a herdr server): processes
3
+ * whose executable name is "herdr" and that have no "server" argument. Clients
4
+ * whose socket is ours are preferred; if none can be tied to it (environment
5
+ * unreadable), every client is returned.
6
+ */
7
+ import { basename, join } from "node:path";
8
+ /**
9
+ * herdr's API socket for an environment (checked on herdr 0.9.0 / Linux):
10
+ * HERDR_SOCKET_PATH, else $XDG_CONFIG_HOME/herdr/herdr.sock, else
11
+ * $HOME/.config/herdr/herdr.sock. The binary has no macOS-specific path
12
+ * (no ~/Library); assumed the same there (unverified on real hardware).
13
+ */
14
+ export function herdrSocketPath(env, home) {
15
+ if (env.HERDR_SOCKET_PATH)
16
+ return env.HERDR_SOCKET_PATH;
17
+ if (env.XDG_CONFIG_HOME)
18
+ return join(env.XDG_CONFIG_HOME, "herdr", "herdr.sock");
19
+ const h = env.HOME || home;
20
+ return h ? join(h, ".config", "herdr", "herdr.sock") : undefined;
21
+ }
22
+ export async function findHerdrClientPids(opts) {
23
+ const { table } = opts;
24
+ const candidates = (await table.list()).filter((p) => basename(p.comm) === "herdr");
25
+ const all = [];
26
+ const ours = [];
27
+ await Promise.all(candidates.map(async ({ pid }) => {
28
+ const argv = await table.argv(pid);
29
+ if (argv?.slice(1).includes("server"))
30
+ return;
31
+ all.push(pid);
32
+ const env = await table.env(pid);
33
+ if (env && herdrSocketPath(env) === opts.socketPath)
34
+ ours.push(pid);
35
+ }));
36
+ return (ours.length > 0 ? ours : all).sort((a, b) => a - b);
37
+ }