pi-kot 0.1.21 → 0.1.23

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 (348) hide show
  1. package/bin/pi-kot.mjs +148 -0
  2. package/dist/client/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  3. package/dist/client/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  4. package/dist/client/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  5. package/dist/client/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  6. package/dist/client/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  7. package/dist/client/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  8. package/dist/client/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  9. package/dist/client/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  10. package/dist/client/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  11. package/dist/client/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  12. package/dist/client/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  13. package/dist/client/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  14. package/dist/client/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  15. package/dist/client/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  16. package/dist/client/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  17. package/dist/client/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  18. package/dist/client/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  19. package/dist/client/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  20. package/dist/client/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  21. package/dist/client/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  22. package/dist/client/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  23. package/dist/client/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  24. package/dist/client/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  25. package/dist/client/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  26. package/dist/client/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  27. package/dist/client/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  28. package/dist/client/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  29. package/dist/client/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  30. package/dist/client/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  31. package/dist/client/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  32. package/dist/client/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  33. package/dist/client/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  34. package/dist/client/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  35. package/dist/client/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  36. package/dist/client/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  37. package/dist/client/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  38. package/dist/client/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  39. package/dist/client/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  40. package/dist/client/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  41. package/dist/client/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  42. package/dist/client/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  43. package/dist/client/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  44. package/dist/client/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  45. package/dist/client/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  46. package/dist/client/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  47. package/dist/client/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  48. package/dist/client/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  49. package/dist/client/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  50. package/dist/client/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  51. package/dist/client/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  52. package/dist/client/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  53. package/dist/client/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  54. package/dist/client/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  55. package/dist/client/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  56. package/dist/client/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  57. package/dist/client/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  58. package/dist/client/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  59. package/dist/client/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  60. package/dist/client/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  61. package/dist/client/assets/index-CE_Na97I.js +75 -0
  62. package/dist/client/assets/index-CE_Na97I.js.map +1 -0
  63. package/dist/client/assets/index-QP95zkpd.css +1 -0
  64. package/dist/client/assets/rolldown-runtime-QTnfLwEv.js +1 -0
  65. package/dist/client/assets/vendor-codemirror-CD3uCqRj.js +48 -0
  66. package/dist/client/assets/vendor-codemirror-CD3uCqRj.js.map +1 -0
  67. package/dist/client/assets/vendor-icons-W8thHNMl.js +2 -0
  68. package/dist/client/assets/vendor-icons-W8thHNMl.js.map +1 -0
  69. package/dist/client/assets/vendor-katex-4z5K3dyr.js +260 -0
  70. package/dist/client/assets/vendor-katex-4z5K3dyr.js.map +1 -0
  71. package/dist/client/assets/vendor-katex-CYlHEDRF.css +1 -0
  72. package/dist/client/assets/vendor-markdown-BSzxNR_7.js +35 -0
  73. package/dist/client/assets/vendor-markdown-BSzxNR_7.js.map +1 -0
  74. package/dist/client/assets/vendor-prism-CQGp13s2.js +4 -0
  75. package/dist/client/assets/vendor-prism-CQGp13s2.js.map +1 -0
  76. package/dist/client/assets/vendor-xterm-BrP-ENHg.css +1 -0
  77. package/dist/client/assets/vendor-xterm-DXwh06H3.js +36 -0
  78. package/dist/client/assets/vendor-xterm-DXwh06H3.js.map +1 -0
  79. package/dist/client/index.html +23 -0
  80. package/dist/server/ask-user-question/envelope.d.ts +11 -0
  81. package/dist/server/ask-user-question/envelope.d.ts.map +1 -0
  82. package/dist/server/ask-user-question/envelope.js +41 -0
  83. package/dist/server/ask-user-question/envelope.js.map +1 -0
  84. package/dist/server/ask-user-question/plan-mode-question-tool.d.ts +26 -0
  85. package/dist/server/ask-user-question/plan-mode-question-tool.d.ts.map +1 -0
  86. package/dist/server/ask-user-question/plan-mode-question-tool.js +273 -0
  87. package/dist/server/ask-user-question/plan-mode-question-tool.js.map +1 -0
  88. package/dist/server/ask-user-question/prompt-strings.d.ts +4 -0
  89. package/dist/server/ask-user-question/prompt-strings.d.ts.map +1 -0
  90. package/dist/server/ask-user-question/prompt-strings.js +18 -0
  91. package/dist/server/ask-user-question/prompt-strings.js.map +1 -0
  92. package/dist/server/ask-user-question/registry.d.ts +39 -0
  93. package/dist/server/ask-user-question/registry.d.ts.map +1 -0
  94. package/dist/server/ask-user-question/registry.js +124 -0
  95. package/dist/server/ask-user-question/registry.js.map +1 -0
  96. package/dist/server/ask-user-question/tool.d.ts +4 -0
  97. package/dist/server/ask-user-question/tool.d.ts.map +1 -0
  98. package/dist/server/ask-user-question/tool.js +68 -0
  99. package/dist/server/ask-user-question/tool.js.map +1 -0
  100. package/dist/server/ask-user-question/types.d.ts +50 -0
  101. package/dist/server/ask-user-question/types.d.ts.map +1 -0
  102. package/dist/server/ask-user-question/types.js +7 -0
  103. package/dist/server/ask-user-question/types.js.map +1 -0
  104. package/dist/server/ask-user-question/validate.d.ts +3 -0
  105. package/dist/server/ask-user-question/validate.d.ts.map +1 -0
  106. package/dist/server/ask-user-question/validate.js +67 -0
  107. package/dist/server/ask-user-question/validate.js.map +1 -0
  108. package/dist/server/clone-provider.d.ts +55 -0
  109. package/dist/server/clone-provider.d.ts.map +1 -0
  110. package/dist/server/clone-provider.js +172 -0
  111. package/dist/server/clone-provider.js.map +1 -0
  112. package/dist/server/compaction-cards.d.ts +62 -0
  113. package/dist/server/compaction-cards.d.ts.map +1 -0
  114. package/dist/server/compaction-cards.js +180 -0
  115. package/dist/server/compaction-cards.js.map +1 -0
  116. package/dist/server/compaction-continuation.d.ts +112 -0
  117. package/dist/server/compaction-continuation.d.ts.map +1 -0
  118. package/dist/server/compaction-continuation.js +65 -0
  119. package/dist/server/compaction-continuation.js.map +1 -0
  120. package/dist/server/config-store.d.ts +97 -0
  121. package/dist/server/config-store.d.ts.map +1 -0
  122. package/dist/server/config-store.js +351 -0
  123. package/dist/server/config-store.js.map +1 -0
  124. package/dist/server/config.d.ts +24 -0
  125. package/dist/server/config.d.ts.map +1 -0
  126. package/dist/server/config.js +67 -0
  127. package/dist/server/config.js.map +1 -0
  128. package/dist/server/event-stream.d.ts +28 -0
  129. package/dist/server/event-stream.d.ts.map +1 -0
  130. package/dist/server/event-stream.js +153 -0
  131. package/dist/server/event-stream.js.map +1 -0
  132. package/dist/server/extension-manager.d.ts +116 -0
  133. package/dist/server/extension-manager.d.ts.map +1 -0
  134. package/dist/server/extension-manager.js +586 -0
  135. package/dist/server/extension-manager.js.map +1 -0
  136. package/dist/server/extension-scanner.d.ts +26 -0
  137. package/dist/server/extension-scanner.d.ts.map +1 -0
  138. package/dist/server/extension-scanner.js +107 -0
  139. package/dist/server/extension-scanner.js.map +1 -0
  140. package/dist/server/extension-ui-bridge.d.ts +35 -0
  141. package/dist/server/extension-ui-bridge.d.ts.map +1 -0
  142. package/dist/server/extension-ui-bridge.js +228 -0
  143. package/dist/server/extension-ui-bridge.js.map +1 -0
  144. package/dist/server/file-manager.d.ts +79 -0
  145. package/dist/server/file-manager.d.ts.map +1 -0
  146. package/dist/server/file-manager.js +535 -0
  147. package/dist/server/file-manager.js.map +1 -0
  148. package/dist/server/file-searcher.d.ts +29 -0
  149. package/dist/server/file-searcher.d.ts.map +1 -0
  150. package/dist/server/file-searcher.js +318 -0
  151. package/dist/server/file-searcher.js.map +1 -0
  152. package/dist/server/git-operations.d.ts +346 -0
  153. package/dist/server/git-operations.d.ts.map +1 -0
  154. package/dist/server/git-operations.js +884 -0
  155. package/dist/server/git-operations.js.map +1 -0
  156. package/dist/server/hunk-stager.d.ts +56 -0
  157. package/dist/server/hunk-stager.d.ts.map +1 -0
  158. package/dist/server/hunk-stager.js +137 -0
  159. package/dist/server/hunk-stager.js.map +1 -0
  160. package/dist/server/index.d.ts +21 -0
  161. package/dist/server/index.d.ts.map +1 -0
  162. package/dist/server/index.js +262 -0
  163. package/dist/server/index.js.map +1 -0
  164. package/dist/server/mcp/config.d.ts +23 -0
  165. package/dist/server/mcp/config.d.ts.map +1 -0
  166. package/dist/server/mcp/config.js +139 -0
  167. package/dist/server/mcp/config.js.map +1 -0
  168. package/dist/server/mcp/manager.d.ts +40 -0
  169. package/dist/server/mcp/manager.d.ts.map +1 -0
  170. package/dist/server/mcp/manager.js +435 -0
  171. package/dist/server/mcp/manager.js.map +1 -0
  172. package/dist/server/mcp/stdio-trust.d.ts +5 -0
  173. package/dist/server/mcp/stdio-trust.d.ts.map +1 -0
  174. package/dist/server/mcp/stdio-trust.js +78 -0
  175. package/dist/server/mcp/stdio-trust.js.map +1 -0
  176. package/dist/server/mcp/tool-bridge.d.ts +33 -0
  177. package/dist/server/mcp/tool-bridge.d.ts.map +1 -0
  178. package/dist/server/mcp/tool-bridge.js +138 -0
  179. package/dist/server/mcp/tool-bridge.js.map +1 -0
  180. package/dist/server/orchestration/config.d.ts +5 -0
  181. package/dist/server/orchestration/config.d.ts.map +1 -0
  182. package/dist/server/orchestration/config.js +35 -0
  183. package/dist/server/orchestration/config.js.map +1 -0
  184. package/dist/server/orchestration/event-bridge.d.ts +32 -0
  185. package/dist/server/orchestration/event-bridge.d.ts.map +1 -0
  186. package/dist/server/orchestration/event-bridge.js +110 -0
  187. package/dist/server/orchestration/event-bridge.js.map +1 -0
  188. package/dist/server/orchestration/inbox.d.ts +9 -0
  189. package/dist/server/orchestration/inbox.d.ts.map +1 -0
  190. package/dist/server/orchestration/inbox.js +186 -0
  191. package/dist/server/orchestration/inbox.js.map +1 -0
  192. package/dist/server/orchestration/init.d.ts +2 -0
  193. package/dist/server/orchestration/init.d.ts.map +1 -0
  194. package/dist/server/orchestration/init.js +22 -0
  195. package/dist/server/orchestration/init.js.map +1 -0
  196. package/dist/server/orchestration/store.d.ts +33 -0
  197. package/dist/server/orchestration/store.d.ts.map +1 -0
  198. package/dist/server/orchestration/store.js +331 -0
  199. package/dist/server/orchestration/store.js.map +1 -0
  200. package/dist/server/orchestration/tools.d.ts +15 -0
  201. package/dist/server/orchestration/tools.d.ts.map +1 -0
  202. package/dist/server/orchestration/tools.js +621 -0
  203. package/dist/server/orchestration/tools.js.map +1 -0
  204. package/dist/server/orchestration/types.d.ts +68 -0
  205. package/dist/server/orchestration/types.d.ts.map +1 -0
  206. package/dist/server/orchestration/types.js +43 -0
  207. package/dist/server/orchestration/types.js.map +1 -0
  208. package/dist/server/orchestration/worker-lifecycle.d.ts +15 -0
  209. package/dist/server/orchestration/worker-lifecycle.d.ts.map +1 -0
  210. package/dist/server/orchestration/worker-lifecycle.js +45 -0
  211. package/dist/server/orchestration/worker-lifecycle.js.map +1 -0
  212. package/dist/server/path-refs.d.ts +5 -0
  213. package/dist/server/path-refs.d.ts.map +1 -0
  214. package/dist/server/path-refs.js +249 -0
  215. package/dist/server/path-refs.js.map +1 -0
  216. package/dist/server/routes/_schemas.d.ts +16 -0
  217. package/dist/server/routes/_schemas.d.ts.map +1 -0
  218. package/dist/server/routes/_schemas.js +12 -0
  219. package/dist/server/routes/_schemas.js.map +1 -0
  220. package/dist/server/routes/ask-user-question.d.ts +3 -0
  221. package/dist/server/routes/ask-user-question.d.ts.map +1 -0
  222. package/dist/server/routes/ask-user-question.js +105 -0
  223. package/dist/server/routes/ask-user-question.js.map +1 -0
  224. package/dist/server/routes/auth.d.ts +8 -0
  225. package/dist/server/routes/auth.d.ts.map +1 -0
  226. package/dist/server/routes/auth.js +225 -0
  227. package/dist/server/routes/auth.js.map +1 -0
  228. package/dist/server/routes/config.d.ts +3 -0
  229. package/dist/server/routes/config.d.ts.map +1 -0
  230. package/dist/server/routes/config.js +602 -0
  231. package/dist/server/routes/config.js.map +1 -0
  232. package/dist/server/routes/control.d.ts +3 -0
  233. package/dist/server/routes/control.d.ts.map +1 -0
  234. package/dist/server/routes/control.js +365 -0
  235. package/dist/server/routes/control.js.map +1 -0
  236. package/dist/server/routes/exec.d.ts +3 -0
  237. package/dist/server/routes/exec.d.ts.map +1 -0
  238. package/dist/server/routes/exec.js +426 -0
  239. package/dist/server/routes/exec.js.map +1 -0
  240. package/dist/server/routes/extension-commands.d.ts +22 -0
  241. package/dist/server/routes/extension-commands.d.ts.map +1 -0
  242. package/dist/server/routes/extension-commands.js +178 -0
  243. package/dist/server/routes/extension-commands.js.map +1 -0
  244. package/dist/server/routes/extensions.d.ts +8 -0
  245. package/dist/server/routes/extensions.d.ts.map +1 -0
  246. package/dist/server/routes/extensions.js +414 -0
  247. package/dist/server/routes/extensions.js.map +1 -0
  248. package/dist/server/routes/files.d.ts +3 -0
  249. package/dist/server/routes/files.d.ts.map +1 -0
  250. package/dist/server/routes/files.js +835 -0
  251. package/dist/server/routes/files.js.map +1 -0
  252. package/dist/server/routes/git.d.ts +3 -0
  253. package/dist/server/routes/git.d.ts.map +1 -0
  254. package/dist/server/routes/git.js +1058 -0
  255. package/dist/server/routes/git.js.map +1 -0
  256. package/dist/server/routes/mcp.d.ts +3 -0
  257. package/dist/server/routes/mcp.d.ts.map +1 -0
  258. package/dist/server/routes/mcp.js +403 -0
  259. package/dist/server/routes/mcp.js.map +1 -0
  260. package/dist/server/routes/orchestration.d.ts +3 -0
  261. package/dist/server/routes/orchestration.d.ts.map +1 -0
  262. package/dist/server/routes/orchestration.js +251 -0
  263. package/dist/server/routes/orchestration.js.map +1 -0
  264. package/dist/server/routes/projects.d.ts +3 -0
  265. package/dist/server/routes/projects.d.ts.map +1 -0
  266. package/dist/server/routes/projects.js +421 -0
  267. package/dist/server/routes/projects.js.map +1 -0
  268. package/dist/server/routes/prompt.d.ts +3 -0
  269. package/dist/server/routes/prompt.d.ts.map +1 -0
  270. package/dist/server/routes/prompt.js +311 -0
  271. package/dist/server/routes/prompt.js.map +1 -0
  272. package/dist/server/routes/session-extensions.d.ts +11 -0
  273. package/dist/server/routes/session-extensions.d.ts.map +1 -0
  274. package/dist/server/routes/session-extensions.js +171 -0
  275. package/dist/server/routes/session-extensions.js.map +1 -0
  276. package/dist/server/routes/sessions.d.ts +3 -0
  277. package/dist/server/routes/sessions.d.ts.map +1 -0
  278. package/dist/server/routes/sessions.js +706 -0
  279. package/dist/server/routes/sessions.js.map +1 -0
  280. package/dist/server/routes/skills.d.ts +14 -0
  281. package/dist/server/routes/skills.d.ts.map +1 -0
  282. package/dist/server/routes/skills.js +448 -0
  283. package/dist/server/routes/skills.js.map +1 -0
  284. package/dist/server/routes/stream.d.ts +3 -0
  285. package/dist/server/routes/stream.d.ts.map +1 -0
  286. package/dist/server/routes/stream.js +112 -0
  287. package/dist/server/routes/stream.js.map +1 -0
  288. package/dist/server/routes/terminal.d.ts +28 -0
  289. package/dist/server/routes/terminal.d.ts.map +1 -0
  290. package/dist/server/routes/terminal.js +211 -0
  291. package/dist/server/routes/terminal.js.map +1 -0
  292. package/dist/server/routes/tunnel.d.ts +13 -0
  293. package/dist/server/routes/tunnel.d.ts.map +1 -0
  294. package/dist/server/routes/tunnel.js +231 -0
  295. package/dist/server/routes/tunnel.js.map +1 -0
  296. package/dist/server/session-alerts.d.ts +28 -0
  297. package/dist/server/session-alerts.d.ts.map +1 -0
  298. package/dist/server/session-alerts.js +38 -0
  299. package/dist/server/session-alerts.js.map +1 -0
  300. package/dist/server/session-store.d.ts +164 -0
  301. package/dist/server/session-store.d.ts.map +1 -0
  302. package/dist/server/session-store.js +754 -0
  303. package/dist/server/session-store.js.map +1 -0
  304. package/dist/server/skill-policy.d.ts +47 -0
  305. package/dist/server/skill-policy.d.ts.map +1 -0
  306. package/dist/server/skill-policy.js +178 -0
  307. package/dist/server/skill-policy.js.map +1 -0
  308. package/dist/server/terminal-provider.d.ts +54 -0
  309. package/dist/server/terminal-provider.d.ts.map +1 -0
  310. package/dist/server/terminal-provider.js +220 -0
  311. package/dist/server/terminal-provider.js.map +1 -0
  312. package/dist/server/tool-policy.d.ts +39 -0
  313. package/dist/server/tool-policy.d.ts.map +1 -0
  314. package/dist/server/tool-policy.js +121 -0
  315. package/dist/server/tool-policy.js.map +1 -0
  316. package/dist/server/tunnel/executable-search.d.ts +43 -0
  317. package/dist/server/tunnel/executable-search.d.ts.map +1 -0
  318. package/dist/server/tunnel/executable-search.js +148 -0
  319. package/dist/server/tunnel/executable-search.js.map +1 -0
  320. package/dist/server/tunnel/install-help.d.ts +19 -0
  321. package/dist/server/tunnel/install-help.d.ts.map +1 -0
  322. package/dist/server/tunnel/install-help.js +47 -0
  323. package/dist/server/tunnel/install-help.js.map +1 -0
  324. package/dist/server/tunnel/providers/ngrok.d.ts +31 -0
  325. package/dist/server/tunnel/providers/ngrok.d.ts.map +1 -0
  326. package/dist/server/tunnel/providers/ngrok.js +461 -0
  327. package/dist/server/tunnel/providers/ngrok.js.map +1 -0
  328. package/dist/server/tunnel/registry.d.ts +13 -0
  329. package/dist/server/tunnel/registry.d.ts.map +1 -0
  330. package/dist/server/tunnel/registry.js +56 -0
  331. package/dist/server/tunnel/registry.js.map +1 -0
  332. package/dist/server/tunnel/service.d.ts +13 -0
  333. package/dist/server/tunnel/service.d.ts.map +1 -0
  334. package/dist/server/tunnel/service.js +156 -0
  335. package/dist/server/tunnel/service.js.map +1 -0
  336. package/dist/server/tunnel/types.d.ts +163 -0
  337. package/dist/server/tunnel/types.d.ts.map +1 -0
  338. package/dist/server/tunnel/types.js +214 -0
  339. package/dist/server/tunnel/types.js.map +1 -0
  340. package/dist/server/ui-settings-store.d.ts +25 -0
  341. package/dist/server/ui-settings-store.d.ts.map +1 -0
  342. package/dist/server/ui-settings-store.js +91 -0
  343. package/dist/server/ui-settings-store.js.map +1 -0
  344. package/dist/server/workspace-store.d.ts +25 -0
  345. package/dist/server/workspace-store.d.ts.map +1 -0
  346. package/dist/server/workspace-store.js +157 -0
  347. package/dist/server/workspace-store.js.map +1 -0
  348. package/package.json +5 -3
@@ -0,0 +1,835 @@
1
+ import { basename, dirname, join } from "node:path";
2
+ import { ChecksumMismatchError, DirectoryNotEmptyError, FileTooLargeError, InvalidNameError, NotAFileError, NotFoundError, PathOutsideRootError, TargetExistsError, deleteEntry, downloadStream, getTree, listAllFiles, makeDirectory, moveEntry, readFile, renameEntry, writeFile, writeFileBytes, } from "../file-manager.js";
3
+ import { getProject } from "../workspace-store.js";
4
+ import { searchFiles, SearchEngineUnavailableError } from "../file-searcher.js";
5
+ import { errorSchema } from "./_schemas.js";
6
+ const MAX_UPLOAD_BYTES = 100 * 1024 * 1024; // 100 MB per file
7
+ const MAX_UPLOAD_FILES = 50;
8
+ const MAX_TOTAL_UPLOAD_BYTES = 500 * 1024 * 1024; // 500 MB aggregate
9
+ const treeNodeSchema = {
10
+ type: "object",
11
+ required: ["name", "path", "type"],
12
+ additionalProperties: true,
13
+ properties: {
14
+ name: { type: "string" },
15
+ path: { type: "string" },
16
+ type: { type: "string", enum: ["file", "directory"] },
17
+ children: { type: "array", items: { type: "object", additionalProperties: true } },
18
+ truncated: { type: "boolean" },
19
+ },
20
+ };
21
+ const readResponseSchema = {
22
+ type: "object",
23
+ required: ["path", "content", "size", "language", "binary"],
24
+ properties: {
25
+ path: { type: "string" },
26
+ content: { type: "string" },
27
+ size: { type: "integer", minimum: 0 },
28
+ language: { type: "string" },
29
+ binary: { type: "boolean" },
30
+ },
31
+ };
32
+ function clampLimit(raw) {
33
+ if (raw === undefined)
34
+ return 50;
35
+ const n = Number.parseInt(raw, 10);
36
+ if (!Number.isFinite(n) || n <= 0)
37
+ return 50;
38
+ return Math.min(n, 200);
39
+ }
40
+ function mapError(reply, err) {
41
+ if (err instanceof PathOutsideRootError) {
42
+ return reply.code(403).send({ error: "path_not_allowed", message: "path is outside the project root" });
43
+ }
44
+ if (err instanceof InvalidNameError) {
45
+ return reply.code(400).send({ error: "invalid_name", message: err.message });
46
+ }
47
+ if (err instanceof NotFoundError) {
48
+ return reply.code(404).send({ error: "not_found", message: "file or directory not found" });
49
+ }
50
+ if (err instanceof NotAFileError) {
51
+ return reply.code(400).send({ error: "not_a_file", message: "target is not a regular file" });
52
+ }
53
+ if (err instanceof FileTooLargeError) {
54
+ return reply.code(413).send({ error: "file_too_large", message: `${err.size} > ${err.limit}` });
55
+ }
56
+ if (err instanceof DirectoryNotEmptyError) {
57
+ return reply.code(409).send({
58
+ error: "directory_not_empty",
59
+ message: "delete the contents first; recursive delete is not supported",
60
+ });
61
+ }
62
+ if (err instanceof TargetExistsError) {
63
+ return reply.code(409).send({ error: "target_exists", message: "destination already exists" });
64
+ }
65
+ if (err instanceof ChecksumMismatchError) {
66
+ return reply.code(422).send({
67
+ error: "checksum_mismatch",
68
+ message: `expected sha256 ${err.expected}, computed ${err.actual}`,
69
+ });
70
+ }
71
+ if (err instanceof SearchEngineUnavailableError) {
72
+ return reply.code(503).send({ error: "engine_unavailable", message: err.message });
73
+ }
74
+ const code = err.code;
75
+ if (code === "ENOENT") {
76
+ return reply.code(404).send({ error: "not_found", message: "file or directory not found" });
77
+ }
78
+ if (code === "EACCES" || code === "EPERM") {
79
+ return reply.code(403).send({ error: "permission_denied", message: "filesystem permission denied" });
80
+ }
81
+ if (code === "EISDIR") {
82
+ return reply.code(400).send({ error: "not_a_file", message: "target is a directory, not a file" });
83
+ }
84
+ if (code === "ENOTDIR") {
85
+ return reply.code(400).send({ error: "not_a_directory", message: "target is a file, not a directory" });
86
+ }
87
+ reply.log.error({ err }, "unmapped file-manager error");
88
+ return reply.code(500).send({ error: "internal_error" });
89
+ }
90
+ async function resolveProject(projectId, reply) {
91
+ const project = await getProject(projectId);
92
+ if (project === undefined) {
93
+ await reply.code(404).send({ error: "project_not_found", message: "no project with that id" });
94
+ return undefined;
95
+ }
96
+ return { id: project.id, path: project.path };
97
+ }
98
+ export const fileRoutes = async (fastify) => {
99
+ fastify.get("/files/complete", {
100
+ logLevel: "warn",
101
+ schema: {
102
+ description: "Flat list of project files matching `query` (path-substring, " +
103
+ "case-insensitive). Used by the chat input's `@` autocomplete. " +
104
+ "Skips the same noisy directories as /files/tree. Returns up to " +
105
+ "`limit` (default 50) POSIX-style paths relative to the project " +
106
+ "root, ranked so a basename match beats a deep-path match and " +
107
+ "shorter paths beat longer ones.",
108
+ tags: ["files"],
109
+ querystring: {
110
+ type: "object",
111
+ required: ["projectId"],
112
+ properties: {
113
+ projectId: { type: "string", minLength: 1 },
114
+ query: { type: "string", maxLength: 256 },
115
+ limit: { type: "string" },
116
+ },
117
+ },
118
+ response: {
119
+ 200: {
120
+ type: "object",
121
+ required: ["paths"],
122
+ properties: { paths: { type: "array", items: { type: "string" } } },
123
+ },
124
+ 404: errorSchema,
125
+ 500: errorSchema,
126
+ },
127
+ },
128
+ }, async (req, reply) => {
129
+ const project = await getProject(req.query.projectId);
130
+ if (project === undefined) {
131
+ return reply.code(404).send({ error: "project_not_found" });
132
+ }
133
+ const query = (req.query.query ?? "").toLowerCase();
134
+ const limit = clampLimit(req.query.limit);
135
+ try {
136
+ const all = await listAllFiles(project.path);
137
+ if (query.length === 0) {
138
+ return { paths: all.sort().slice(0, limit) };
139
+ }
140
+ const scored = [];
141
+ for (const p of all) {
142
+ const lower = p.toLowerCase();
143
+ const slash = lower.lastIndexOf("/");
144
+ const base = slash === -1 ? lower : lower.slice(slash + 1);
145
+ let score;
146
+ if (base === query)
147
+ score = 0;
148
+ else if (base.startsWith(query))
149
+ score = 1;
150
+ else if (base.includes(query))
151
+ score = 2;
152
+ else if (lower.includes(query))
153
+ score = 3;
154
+ else
155
+ continue;
156
+ scored.push({ path: p, score });
157
+ }
158
+ scored.sort((a, b) => a.score !== b.score ? a.score - b.score : a.path.length - b.path.length);
159
+ return { paths: scored.slice(0, limit).map((s) => s.path) };
160
+ }
161
+ catch (err) {
162
+ return mapError(reply, err);
163
+ }
164
+ });
165
+ fastify.get("/files/tree", {
166
+ schema: {
167
+ description: "Recursive directory tree for the project. Skips noisy folders " +
168
+ "(node_modules, .git, dist, build, __pycache__, .next, .nuxt, " +
169
+ "coverage, .vite, .turbo, .cache). Recursion is capped at " +
170
+ "max depth 32 to avoid unbounded filesystem walks.",
171
+ tags: ["files"],
172
+ querystring: {
173
+ type: "object",
174
+ required: ["projectId"],
175
+ properties: {
176
+ projectId: { type: "string", minLength: 1 },
177
+ maxDepth: { type: "string", pattern: "^[0-9]+$" },
178
+ },
179
+ },
180
+ response: { 200: treeNodeSchema, 404: errorSchema, 500: errorSchema },
181
+ },
182
+ }, async (req, reply) => {
183
+ const project = await resolveProject(req.query.projectId, reply);
184
+ if (project === undefined)
185
+ return reply;
186
+ try {
187
+ let maxDepth;
188
+ if (req.query.maxDepth !== undefined) {
189
+ const n = Number.parseInt(req.query.maxDepth, 10);
190
+ maxDepth = Math.min(Math.max(n, 1), 32);
191
+ }
192
+ const tree = await getTree(project.path, maxDepth !== undefined ? { maxDepth } : {});
193
+ return tree;
194
+ }
195
+ catch (err) {
196
+ return mapError(reply, err);
197
+ }
198
+ });
199
+ fastify.get("/files/download", {
200
+ schema: {
201
+ description: "Download a file or directory from the project. Files stream " +
202
+ "verbatim with `Content-Disposition: attachment`; directories " +
203
+ "stream as a zip archive (`<dir>.zip`) with the same exclusions " +
204
+ "as the file tree (node_modules, .git, dist, build, etc.). Omitting " +
205
+ "`path` downloads the whole project as a zip.",
206
+ tags: ["files"],
207
+ querystring: {
208
+ type: "object",
209
+ required: ["projectId"],
210
+ properties: {
211
+ projectId: { type: "string", minLength: 1 },
212
+ path: { type: "string", minLength: 1 },
213
+ },
214
+ },
215
+ response: {
216
+ 200: { type: "string", format: "binary" },
217
+ 400: errorSchema,
218
+ 403: errorSchema,
219
+ 404: errorSchema,
220
+ 500: errorSchema,
221
+ },
222
+ },
223
+ }, async (req, reply) => {
224
+ const project = await resolveProject(req.query.projectId, reply);
225
+ if (project === undefined)
226
+ return reply;
227
+ const target = req.query.path !== undefined ? join(project.path, req.query.path) : project.path;
228
+ try {
229
+ const result = await downloadStream(target, project.path);
230
+ const asciiName = result.filename.replace(/[^\x20-\x7e]/g, "_");
231
+ const utfName = encodeURIComponent(result.filename);
232
+ reply.header("Content-Disposition", `attachment; filename="${asciiName}"; filename*=UTF-8''${utfName}`);
233
+ if (result.kind === "file") {
234
+ reply.header("Content-Type", "application/octet-stream");
235
+ reply.header("Content-Length", String(result.size));
236
+ }
237
+ else {
238
+ reply.header("Content-Type", "application/zip");
239
+ }
240
+ return reply.send(result.stream);
241
+ }
242
+ catch (err) {
243
+ return mapError(reply, err);
244
+ }
245
+ });
246
+ fastify.get("/files/read", {
247
+ schema: {
248
+ description: "Read a UTF-8 file from the project. 5 MB cap (returns 413). " +
249
+ "Binary files return `{ binary: true, content: '' }` rather than a " +
250
+ "garbled UTF-8 decode — clients should not pass binary content " +
251
+ "to the editor.",
252
+ tags: ["files"],
253
+ querystring: {
254
+ type: "object",
255
+ required: ["projectId", "path"],
256
+ properties: {
257
+ projectId: { type: "string", minLength: 1 },
258
+ path: { type: "string", minLength: 1 },
259
+ },
260
+ },
261
+ response: {
262
+ 200: readResponseSchema,
263
+ 400: errorSchema,
264
+ 403: errorSchema,
265
+ 404: errorSchema,
266
+ 413: errorSchema,
267
+ 500: errorSchema,
268
+ },
269
+ },
270
+ }, async (req, reply) => {
271
+ const project = await resolveProject(req.query.projectId, reply);
272
+ if (project === undefined)
273
+ return reply;
274
+ try {
275
+ const result = await readFile(join(project.path, req.query.path), project.path);
276
+ return result;
277
+ }
278
+ catch (err) {
279
+ return mapError(reply, err);
280
+ }
281
+ });
282
+ fastify.put("/files/write", {
283
+ schema: {
284
+ description: "Atomic write (tmp + rename). Creates parent directories as " +
285
+ "needed. The body's `path` is required to be inside the project " +
286
+ "root — 403 otherwise.",
287
+ tags: ["files"],
288
+ body: {
289
+ type: "object",
290
+ required: ["projectId", "path", "content"],
291
+ additionalProperties: false,
292
+ properties: {
293
+ projectId: { type: "string", minLength: 1 },
294
+ path: { type: "string", minLength: 1 },
295
+ content: { type: "string" },
296
+ },
297
+ },
298
+ response: {
299
+ 200: { type: "object", required: ["path"], properties: { path: { type: "string" } } },
300
+ 400: errorSchema,
301
+ 403: errorSchema,
302
+ 404: errorSchema,
303
+ 500: errorSchema,
304
+ },
305
+ },
306
+ }, async (req, reply) => {
307
+ const project = await resolveProject(req.body.projectId, reply);
308
+ if (project === undefined)
309
+ return reply;
310
+ try {
311
+ await writeFile(join(project.path, req.body.path), project.path, req.body.content);
312
+ return { path: req.body.path };
313
+ }
314
+ catch (err) {
315
+ return mapError(reply, err);
316
+ }
317
+ });
318
+ fastify.post("/files/mkdir", {
319
+ schema: {
320
+ description: "Create a single directory under `parentPath`.",
321
+ tags: ["files"],
322
+ body: {
323
+ type: "object",
324
+ required: ["projectId", "parentPath", "name"],
325
+ additionalProperties: false,
326
+ properties: {
327
+ projectId: { type: "string", minLength: 1 },
328
+ parentPath: { type: "string", minLength: 1 },
329
+ name: { type: "string", minLength: 1 },
330
+ },
331
+ },
332
+ response: {
333
+ 200: { type: "object", required: ["path"], properties: { path: { type: "string" } } },
334
+ 400: errorSchema,
335
+ 403: errorSchema,
336
+ 404: errorSchema,
337
+ 409: errorSchema,
338
+ 500: errorSchema,
339
+ },
340
+ },
341
+ }, async (req, reply) => {
342
+ const project = await resolveProject(req.body.projectId, reply);
343
+ if (project === undefined)
344
+ return reply;
345
+ try {
346
+ const created = await makeDirectory(join(project.path, req.body.parentPath), project.path, req.body.name);
347
+ return { path: created };
348
+ }
349
+ catch (err) {
350
+ return mapError(reply, err);
351
+ }
352
+ });
353
+ fastify.post("/files/rename", {
354
+ schema: {
355
+ description: "Rename a file or directory in place — `name` is the new basename. " +
356
+ "Use /files/move to relocate across directories.",
357
+ tags: ["files"],
358
+ body: {
359
+ type: "object",
360
+ required: ["projectId", "path", "name"],
361
+ additionalProperties: false,
362
+ properties: {
363
+ projectId: { type: "string", minLength: 1 },
364
+ path: { type: "string", minLength: 1 },
365
+ name: { type: "string", minLength: 1 },
366
+ },
367
+ },
368
+ response: {
369
+ 200: { type: "object", required: ["path"], properties: { path: { type: "string" } } },
370
+ 400: errorSchema,
371
+ 403: errorSchema,
372
+ 404: errorSchema,
373
+ 409: errorSchema,
374
+ 500: errorSchema,
375
+ },
376
+ },
377
+ }, async (req, reply) => {
378
+ const project = await resolveProject(req.body.projectId, reply);
379
+ if (project === undefined)
380
+ return reply;
381
+ try {
382
+ const renamed = await renameEntry(join(project.path, req.body.path), project.path, req.body.name);
383
+ return { path: renamed };
384
+ }
385
+ catch (err) {
386
+ return mapError(reply, err);
387
+ }
388
+ });
389
+ fastify.post("/files/move", {
390
+ schema: {
391
+ description: "Move a file or directory to `dest` (a full destination path). " +
392
+ "Refuses to move a directory under itself; refuses if `dest` " +
393
+ "already exists.",
394
+ tags: ["files"],
395
+ body: {
396
+ type: "object",
397
+ required: ["projectId", "src", "dest"],
398
+ additionalProperties: false,
399
+ properties: {
400
+ projectId: { type: "string", minLength: 1 },
401
+ src: { type: "string", minLength: 1 },
402
+ dest: { type: "string", minLength: 1 },
403
+ },
404
+ },
405
+ response: {
406
+ 200: { type: "object", required: ["path"], properties: { path: { type: "string" } } },
407
+ 400: errorSchema,
408
+ 403: errorSchema,
409
+ 404: errorSchema,
410
+ 409: errorSchema,
411
+ 500: errorSchema,
412
+ },
413
+ },
414
+ }, async (req, reply) => {
415
+ const project = await resolveProject(req.body.projectId, reply);
416
+ if (project === undefined)
417
+ return reply;
418
+ try {
419
+ const moved = await moveEntry(join(project.path, req.body.src), join(project.path, req.body.dest), project.path);
420
+ return { path: moved };
421
+ }
422
+ catch (err) {
423
+ return mapError(reply, err);
424
+ }
425
+ });
426
+ fastify.delete("/files/delete", {
427
+ schema: {
428
+ description: "Delete a file or directory. Empty directories delete unconditionally. " +
429
+ "Non-empty directories return 409 unless `?recursive=true` is set, in " +
430
+ "which case the entire subtree is removed. The UI prompts the user with " +
431
+ "a second confirmation before retrying with the recursive flag — single- " +
432
+ "user single-tenant, but `rm -rf` should still be an explicit choice.",
433
+ tags: ["files"],
434
+ querystring: {
435
+ type: "object",
436
+ required: ["projectId", "path"],
437
+ properties: {
438
+ projectId: { type: "string", minLength: 1 },
439
+ path: { type: "string", minLength: 1 },
440
+ recursive: { type: "string", enum: ["true", "false"] },
441
+ },
442
+ },
443
+ response: {
444
+ 204: { type: "null" },
445
+ 400: errorSchema,
446
+ 403: errorSchema,
447
+ 404: errorSchema,
448
+ 409: errorSchema,
449
+ 500: errorSchema,
450
+ },
451
+ },
452
+ }, async (req, reply) => {
453
+ const project = await resolveProject(req.query.projectId, reply);
454
+ if (project === undefined)
455
+ return reply;
456
+ try {
457
+ const recursive = req.query.recursive === "true";
458
+ await deleteEntry(join(project.path, req.query.path), project.path, { recursive });
459
+ return reply.code(204).send();
460
+ }
461
+ catch (err) {
462
+ return mapError(reply, err);
463
+ }
464
+ });
465
+ fastify.get("/files/search", {
466
+ schema: {
467
+ description: "Cross-project text + regex search. Uses ripgrep when available " +
468
+ "(fast + gitignore-aware) and falls back to a Node walk on hosts " +
469
+ "without rg. Response includes `engine: 'ripgrep' | 'node'` so the " +
470
+ "UI can render a fallback-mode badge. Hard caps: 1000 matches max " +
471
+ "per request, 30s wall clock, 5 MB per file. Binary files are " +
472
+ "skipped via NUL-byte heuristic on the fallback path; ripgrep " +
473
+ "uses its own (better) binary detection.",
474
+ tags: ["files"],
475
+ querystring: {
476
+ type: "object",
477
+ required: ["projectId", "q"],
478
+ properties: {
479
+ projectId: { type: "string", minLength: 1 },
480
+ q: { type: "string", minLength: 1, maxLength: 1024 },
481
+ regex: { type: "string", enum: ["0", "1", "true", "false"] },
482
+ caseSensitive: { type: "string", enum: ["0", "1", "true", "false"] },
483
+ includeGitignored: { type: "string", enum: ["0", "1", "true", "false"] },
484
+ include: { type: "string", maxLength: 256 },
485
+ exclude: { type: "string", maxLength: 256 },
486
+ limit: { type: "string", pattern: "^[0-9]+$" },
487
+ },
488
+ },
489
+ response: {
490
+ 200: {
491
+ type: "object",
492
+ required: ["engine", "matches", "truncated"],
493
+ properties: {
494
+ engine: { type: "string", enum: ["ripgrep", "node"] },
495
+ truncated: { type: "boolean" },
496
+ matches: {
497
+ type: "array",
498
+ items: {
499
+ type: "object",
500
+ required: ["path", "line", "column", "length", "lineSnippet"],
501
+ properties: {
502
+ path: { type: "string" },
503
+ line: { type: "integer", minimum: 1 },
504
+ column: { type: "integer", minimum: 1 },
505
+ length: { type: "integer", minimum: 0 },
506
+ lineSnippet: { type: "string" },
507
+ },
508
+ },
509
+ },
510
+ },
511
+ },
512
+ 400: errorSchema,
513
+ 404: errorSchema,
514
+ 500: errorSchema,
515
+ },
516
+ },
517
+ }, async (req, reply) => {
518
+ const project = await resolveProject(req.query.projectId, reply);
519
+ if (project === undefined)
520
+ return reply;
521
+ const { q } = req.query;
522
+ const regex = req.query.regex === "1" || req.query.regex === "true";
523
+ const caseSensitive = req.query.caseSensitive === "1" || req.query.caseSensitive === "true";
524
+ const includeGitignored = req.query.includeGitignored === "1" || req.query.includeGitignored === "true";
525
+ const limit = req.query.limit !== undefined
526
+ ? Math.min(1000, Math.max(1, Number.parseInt(req.query.limit, 10)))
527
+ : 200;
528
+ try {
529
+ const opts = {
530
+ query: q,
531
+ regex,
532
+ caseSensitive,
533
+ includeGitignored,
534
+ limit,
535
+ timeoutMs: 30_000,
536
+ };
537
+ if (req.query.include !== undefined && req.query.include.length > 0) {
538
+ opts.include = req.query.include;
539
+ }
540
+ if (req.query.exclude !== undefined && req.query.exclude.length > 0) {
541
+ opts.exclude = req.query.exclude;
542
+ }
543
+ const result = await searchFiles(project.path, opts);
544
+ return result;
545
+ }
546
+ catch (err) {
547
+ return mapError(reply, err);
548
+ }
549
+ });
550
+ // GET /api/v1/filesystem/home — filesystem home directory (public, no auth)
551
+ fastify.get("/filesystem/home", {
552
+ config: { public: true },
553
+ schema: {
554
+ description: "Returns the user's home directory path. Used by the Add Project " +
555
+ "dialog to initialise the path input to ~/.",
556
+ tags: ["filesystem"],
557
+ response: {
558
+ 200: {
559
+ type: "object",
560
+ required: ["home"],
561
+ properties: { home: { type: "string" } },
562
+ },
563
+ },
564
+ },
565
+ }, async () => {
566
+ const { homedir } = await import("node:os");
567
+ return { home: homedir() };
568
+ });
569
+ // GET /api/v1/filesystem/list — list directory entries (public, no auth)
570
+ fastify.get("/filesystem/list", {
571
+ config: { public: true },
572
+ schema: {
573
+ description: "Lists directory entries (name, path, isDirectory) for a given " +
574
+ "filesystem path. Used by the Add Project dialog for live directory " +
575
+ "browsing. Hidden entries (dotfiles) are returned — filtering is " +
576
+ "the client's choice.",
577
+ tags: ["filesystem"],
578
+ querystring: {
579
+ type: "object",
580
+ properties: {
581
+ path: { type: "string", description: "Absolute filesystem path to list" },
582
+ },
583
+ },
584
+ response: {
585
+ 200: {
586
+ type: "object",
587
+ required: ["entries"],
588
+ properties: {
589
+ entries: {
590
+ type: "array",
591
+ items: {
592
+ type: "object",
593
+ required: ["name", "path", "isDirectory"],
594
+ properties: {
595
+ name: { type: "string" },
596
+ path: { type: "string" },
597
+ isDirectory: { type: "boolean" },
598
+ },
599
+ },
600
+ },
601
+ },
602
+ },
603
+ 400: errorSchema,
604
+ 403: errorSchema,
605
+ 404: errorSchema,
606
+ 500: errorSchema,
607
+ },
608
+ },
609
+ }, async (req, reply) => {
610
+ const { readdir, stat } = await import("node:fs/promises");
611
+ const targetPath = (req.query.path ?? "").trim();
612
+ if (!targetPath) {
613
+ return reply.code(400).send({ error: "path_required", message: "path query parameter is required" });
614
+ }
615
+ try {
616
+ const st = await stat(targetPath);
617
+ if (!st.isDirectory()) {
618
+ return reply.code(400).send({ error: "not_a_directory", message: "path is not a directory" });
619
+ }
620
+ }
621
+ catch (err) {
622
+ const code = err.code;
623
+ if (code === "ENOENT") {
624
+ return reply.code(404).send({ error: "not_found", message: "directory not found" });
625
+ }
626
+ if (code === "EACCES" || code === "EPERM") {
627
+ return reply.code(403).send({ error: "permission_denied", message: "permission denied" });
628
+ }
629
+ throw err;
630
+ }
631
+ const entries = [];
632
+ try {
633
+ const dirEntries = await readdir(targetPath, { withFileTypes: true });
634
+ for (const entry of dirEntries) {
635
+ entries.push({
636
+ name: entry.name,
637
+ path: join(targetPath, entry.name),
638
+ isDirectory: entry.isDirectory(),
639
+ });
640
+ }
641
+ }
642
+ catch (err) {
643
+ const code = err.code;
644
+ if (code === "EACCES" || code === "EPERM") {
645
+ return reply.code(403).send({ error: "permission_denied", message: "permission denied" });
646
+ }
647
+ throw err;
648
+ }
649
+ // Sort: directories first, then by name
650
+ entries.sort((a, b) => {
651
+ if (a.isDirectory !== b.isDirectory)
652
+ return a.isDirectory ? -1 : 1;
653
+ return a.name.localeCompare(b.name);
654
+ });
655
+ return { entries };
656
+ });
657
+ // ----------------------------- upload -----------------------------
658
+ // Multipart upload of one or more files into a project folder.
659
+ // Each file is streamed to a tmp path, hashed with SHA-256 as bytes
660
+ // flow, and atomically renamed into place. Per-file and aggregate
661
+ // caps are enforced.
662
+ fastify.post("/files/upload", {
663
+ schema: {
664
+ description: `Upload one or more files into a project folder via multipart/form-data. ` +
665
+ `Each file is streamed to disk, its SHA-256 is computed on the fly, and ` +
666
+ `the rename to the final name is performed atomically (tmp + rename). ` +
667
+ `Per-file cap: ${MAX_UPLOAD_BYTES / (1024 * 1024)} MB. Aggregate cap: ` +
668
+ `${MAX_TOTAL_UPLOAD_BYTES / (1024 * 1024)} MB across all parts. Max ` +
669
+ `${MAX_UPLOAD_FILES} files per request. Existing targets return 409 unless ` +
670
+ `\`overwrite=1\` is sent.`,
671
+ tags: ["files"],
672
+ consumes: ["multipart/form-data"],
673
+ response: {
674
+ 200: {
675
+ type: "object",
676
+ required: ["files"],
677
+ properties: {
678
+ files: {
679
+ type: "array",
680
+ items: {
681
+ type: "object",
682
+ required: ["path", "size", "sha256"],
683
+ properties: {
684
+ path: { type: "string" },
685
+ size: { type: "integer", minimum: 0 },
686
+ sha256: { type: "string" },
687
+ },
688
+ },
689
+ },
690
+ },
691
+ },
692
+ 400: errorSchema,
693
+ 403: errorSchema,
694
+ 404: errorSchema,
695
+ 409: errorSchema,
696
+ 413: errorSchema,
697
+ 415: errorSchema,
698
+ 422: errorSchema,
699
+ 500: errorSchema,
700
+ },
701
+ },
702
+ }, async (req, reply) => {
703
+ if (!req.isMultipart()) {
704
+ return reply.code(415).send({ error: "expected_multipart" });
705
+ }
706
+ let projectId;
707
+ let parentPath;
708
+ let overwrite = false;
709
+ let aggregateBytes = 0;
710
+ const relativePaths = new Map();
711
+ const written = [];
712
+ try {
713
+ const parts = req.parts({
714
+ limits: {
715
+ fileSize: MAX_UPLOAD_BYTES,
716
+ files: MAX_UPLOAD_FILES,
717
+ fields: MAX_UPLOAD_FILES * 2 + 8,
718
+ },
719
+ });
720
+ for await (const part of parts) {
721
+ if (part.type === "field") {
722
+ if (part.fieldname === "projectId" && typeof part.value === "string") {
723
+ projectId = part.value;
724
+ }
725
+ else if (part.fieldname === "parentPath" && typeof part.value === "string") {
726
+ parentPath = part.value;
727
+ }
728
+ else if (part.fieldname === "overwrite" && typeof part.value === "string") {
729
+ overwrite = part.value === "1" || part.value === "true";
730
+ }
731
+ else if (part.fieldname.startsWith("path:") && typeof part.value === "string") {
732
+ const key = part.fieldname.slice("path:".length);
733
+ if (key.length > 0)
734
+ relativePaths.set(key, part.value);
735
+ }
736
+ continue;
737
+ }
738
+ // File part
739
+ if (projectId === undefined) {
740
+ return reply.code(400).send({
741
+ error: "missing_field",
742
+ message: "projectId must precede file parts in the multipart body",
743
+ });
744
+ }
745
+ if (parentPath === undefined) {
746
+ return reply.code(400).send({
747
+ error: "missing_field",
748
+ message: "parentPath must precede file parts in the multipart body",
749
+ });
750
+ }
751
+ const project = await getProject(projectId);
752
+ if (project === undefined) {
753
+ return reply.code(404).send({ error: "project_not_found" });
754
+ }
755
+ const filename = part.filename;
756
+ if (filename === undefined || filename.length === 0) {
757
+ return reply.code(400).send({ error: "missing_filename" });
758
+ }
759
+ // Determine upload path: if a path:<index> field was sent, use it;
760
+ // otherwise use the filename as-is (flat upload into parentPath).
761
+ const indexedKey = part.fieldname.startsWith("file:")
762
+ ? part.fieldname.slice("file:".length)
763
+ : undefined;
764
+ const uploadPath = indexedKey !== undefined ? (relativePaths.get(indexedKey) ?? filename) : filename;
765
+ const isNested = uploadPath.includes("/") || uploadPath.includes("\\");
766
+ // Enforce aggregate cap before starting the next file
767
+ if (aggregateBytes >= MAX_TOTAL_UPLOAD_BYTES) {
768
+ return reply.code(413).send({
769
+ error: "aggregate_too_large",
770
+ message: `Total upload size exceeds the ${MAX_TOTAL_UPLOAD_BYTES / (1024 * 1024)} MB aggregate limit.`,
771
+ });
772
+ }
773
+ // Track aggregate as the stream flows
774
+ const absParent = join(project.path, parentPath);
775
+ req.log.info({ absParent, uploadPath, isNested, filename }, "upload: about to write");
776
+ const trackedSource = (async function* () {
777
+ for await (const chunk of part.file) {
778
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
779
+ aggregateBytes += buf.byteLength;
780
+ if (aggregateBytes > MAX_TOTAL_UPLOAD_BYTES) {
781
+ // Roll back already-written files
782
+ for (const prior of written) {
783
+ await deleteEntry(prior.path, project.path).catch(() => undefined);
784
+ }
785
+ throw Object.assign(new Error(`Aggregate upload exceeds ${MAX_TOTAL_UPLOAD_BYTES} bytes`), { aggregateLimit: true });
786
+ }
787
+ yield buf;
788
+ }
789
+ })();
790
+ let result;
791
+ try {
792
+ if (isNested) {
793
+ // For nested uploads (folder uploads with path:<index>),
794
+ // write to parentPath/uploadPath directly
795
+ const targetAbs = join(absParent, uploadPath);
796
+ result = await writeFileBytes(dirname(targetAbs), basename(uploadPath), project.path, trackedSource, {
797
+ overwrite,
798
+ });
799
+ }
800
+ else {
801
+ result = await writeFileBytes(absParent, uploadPath, project.path, trackedSource, {
802
+ overwrite,
803
+ });
804
+ }
805
+ }
806
+ catch (err) {
807
+ if (err.aggregateLimit) {
808
+ return reply.code(413).send({
809
+ error: "aggregate_too_large",
810
+ message: `Total upload size exceeds the ${MAX_TOTAL_UPLOAD_BYTES / (1024 * 1024)} MB aggregate limit.`,
811
+ });
812
+ }
813
+ // Roll back everything written so far on any error
814
+ for (const prior of written) {
815
+ await deleteEntry(prior.path, project.path).catch(() => undefined);
816
+ }
817
+ throw err;
818
+ }
819
+ if (part.file.truncated) {
820
+ await deleteEntry(result.path, project.path).catch(() => undefined);
821
+ return reply.code(413).send({
822
+ error: "file_too_large",
823
+ message: `Upload "${filename}" exceeds the ${MAX_UPLOAD_BYTES / (1024 * 1024)} MB per-file limit.`,
824
+ });
825
+ }
826
+ written.push(result);
827
+ }
828
+ }
829
+ catch (err) {
830
+ return mapError(reply, err);
831
+ }
832
+ return { files: written };
833
+ });
834
+ };
835
+ //# sourceMappingURL=files.js.map