circuschief 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. package/package.json +33 -0
  2. package/packages/server/bin/cli.js +4 -0
  3. package/packages/server/src/agents/AgentGateway.js +64 -0
  4. package/packages/server/src/agents/BaseAgent.js +41 -0
  5. package/packages/server/src/agents/LoggingAgentWrapper.js +73 -0
  6. package/packages/server/src/agents/adapters/ClaudeCodeAdapter.js +33 -0
  7. package/packages/server/src/agents/adapters/CodexAdapter.js +26 -0
  8. package/packages/server/src/agents/types.js +43 -0
  9. package/packages/server/src/agents/vcr/CassetteStore.js +111 -0
  10. package/packages/server/src/agents/vcr/VCRAgentAdapter.js +126 -0
  11. package/packages/server/src/agents/vcr/VCRSummaryWrapper.js +71 -0
  12. package/packages/server/src/api/canvas-helpers.js +249 -0
  13. package/packages/server/src/api/canvas-trash-routes.js +205 -0
  14. package/packages/server/src/api/canvas.js +331 -0
  15. package/packages/server/src/api/commandButtons.js +312 -0
  16. package/packages/server/src/api/commands.js +169 -0
  17. package/packages/server/src/api/filesystem.js +62 -0
  18. package/packages/server/src/api/git.js +85 -0
  19. package/packages/server/src/api/index.js +44 -0
  20. package/packages/server/src/api/kanban.js +342 -0
  21. package/packages/server/src/api/metrics.js +194 -0
  22. package/packages/server/src/api/projects-helpers.js +43 -0
  23. package/packages/server/src/api/projects-session-helpers.js +295 -0
  24. package/packages/server/src/api/projects.js +384 -0
  25. package/packages/server/src/api/providers.js +249 -0
  26. package/packages/server/src/api/quickResponses.js +129 -0
  27. package/packages/server/src/api/sessions-archive.js +69 -0
  28. package/packages/server/src/api/sessions-commands.js +220 -0
  29. package/packages/server/src/api/sessions-conversations.js +168 -0
  30. package/packages/server/src/api/sessions-draft.js +72 -0
  31. package/packages/server/src/api/sessions-lifecycle.js +190 -0
  32. package/packages/server/src/api/sessions-messages.js +141 -0
  33. package/packages/server/src/api/sessions-notes.js +51 -0
  34. package/packages/server/src/api/sessions-patch.js +252 -0
  35. package/packages/server/src/api/sessions-streaming.js +86 -0
  36. package/packages/server/src/api/sessions.js +269 -0
  37. package/packages/server/src/api/settings.js +194 -0
  38. package/packages/server/src/api/templates.js +63 -0
  39. package/packages/server/src/app.js +51 -0
  40. package/packages/server/src/database.js +58 -0
  41. package/packages/server/src/db/AgentCallLogRepository.js +322 -0
  42. package/packages/server/src/db/AttachmentRepository.js +191 -0
  43. package/packages/server/src/db/BaseRepository.js +39 -0
  44. package/packages/server/src/db/CanvasItemRepository.js +315 -0
  45. package/packages/server/src/db/CommandButtonRepository.js +75 -0
  46. package/packages/server/src/db/CommandRunRepository.js +219 -0
  47. package/packages/server/src/db/ConversationRepository.js +379 -0
  48. package/packages/server/src/db/DatabaseManager.js +91 -0
  49. package/packages/server/src/db/KanbanBoardRepository.js +92 -0
  50. package/packages/server/src/db/KanbanCardRepository.js +286 -0
  51. package/packages/server/src/db/KanbanLaneRepository.js +279 -0
  52. package/packages/server/src/db/MessageRepository.js +156 -0
  53. package/packages/server/src/db/ProjectDefaultsRepository.js +173 -0
  54. package/packages/server/src/db/ProjectRepository.js +110 -0
  55. package/packages/server/src/db/ProviderRepository.js +307 -0
  56. package/packages/server/src/db/QuickResponseRepository.js +186 -0
  57. package/packages/server/src/db/SessionNoteRepository.js +60 -0
  58. package/packages/server/src/db/SessionRepository.js +314 -0
  59. package/packages/server/src/db/SessionSummaryRepository.js +200 -0
  60. package/packages/server/src/db/SessionTemplateRepository.js +171 -0
  61. package/packages/server/src/db/SettingsRepository.js +211 -0
  62. package/packages/server/src/db/TodoRepository.js +132 -0
  63. package/packages/server/src/db/WorkLogRepository.js +122 -0
  64. package/packages/server/src/db/conversation-helpers.js +119 -0
  65. package/packages/server/src/db/index.js +100 -0
  66. package/packages/server/src/db/migrations/canvasItemsMigrations.js +109 -0
  67. package/packages/server/src/db/migrations/conversationsMigrations.js +183 -0
  68. package/packages/server/src/db/migrations/index.js +199 -0
  69. package/packages/server/src/db/migrations/kanbanMigrations.js +99 -0
  70. package/packages/server/src/db/migrations/migrationUtils.js +55 -0
  71. package/packages/server/src/db/migrations/miscMigrations.js +242 -0
  72. package/packages/server/src/db/migrations/projectsMigrations.js +95 -0
  73. package/packages/server/src/db/migrations/sessionsMigrations.js +282 -0
  74. package/packages/server/src/db/session-helpers.js +150 -0
  75. package/packages/server/src/index.js +106 -0
  76. package/packages/server/src/logger.js +22 -0
  77. package/packages/server/src/middleware/sessionLookup.js +57 -0
  78. package/packages/server/src/middleware/upload.js +94 -0
  79. package/packages/server/src/schema.sql +363 -0
  80. package/packages/server/src/services/agentCallLogger.js +116 -0
  81. package/packages/server/src/services/canvasStore.js +56 -0
  82. package/packages/server/src/services/childSessionContext.js +61 -0
  83. package/packages/server/src/services/commandRunner.js +422 -0
  84. package/packages/server/src/services/conversationContext.js +72 -0
  85. package/packages/server/src/services/diffService.js +172 -0
  86. package/packages/server/src/services/draftSessionService.js +181 -0
  87. package/packages/server/src/services/encryption.js +134 -0
  88. package/packages/server/src/services/ghService.js +169 -0
  89. package/packages/server/src/services/gitService.js +520 -0
  90. package/packages/server/src/services/gitSessionSetup.js +48 -0
  91. package/packages/server/src/services/hookService.js +60 -0
  92. package/packages/server/src/services/kanbanService.js +262 -0
  93. package/packages/server/src/services/kanbanTriggers.js +273 -0
  94. package/packages/server/src/services/nodeSpawnHelper.js +63 -0
  95. package/packages/server/src/services/prStatusService.js +204 -0
  96. package/packages/server/src/services/prUrlService.js +224 -0
  97. package/packages/server/src/services/providerTestService.js +81 -0
  98. package/packages/server/src/services/scheduleService.js +110 -0
  99. package/packages/server/src/services/schedulerService.js +281 -0
  100. package/packages/server/src/services/sessionDuplicator.js +63 -0
  101. package/packages/server/src/services/sessionErrors.js +173 -0
  102. package/packages/server/src/services/sessionExecution.js +378 -0
  103. package/packages/server/src/services/sessionManager.js +356 -0
  104. package/packages/server/src/services/sessionPrompts.js +427 -0
  105. package/packages/server/src/services/sessionProvider.js +107 -0
  106. package/packages/server/src/services/slashCommandDiscovery.js +258 -0
  107. package/packages/server/src/services/slashCommandPluginDiscovery.js +216 -0
  108. package/packages/server/src/services/slashCommandService.js +306 -0
  109. package/packages/server/src/services/streamEventCallbacks.js +170 -0
  110. package/packages/server/src/services/streamEventHandler.js +488 -0
  111. package/packages/server/src/services/streamUsageHandler.js +228 -0
  112. package/packages/server/src/services/summaryBroadcast.js +61 -0
  113. package/packages/server/src/services/summaryClaudeClient.js +180 -0
  114. package/packages/server/src/services/summaryPrompts.js +169 -0
  115. package/packages/server/src/services/summaryService.js +552 -0
  116. package/packages/server/src/services/summaryStaleCheck.js +35 -0
  117. package/packages/server/src/services/systemMonitor.js +281 -0
  118. package/packages/server/src/services/templateTriggerService.js +197 -0
  119. package/packages/server/src/services/terminalOutput.js +160 -0
  120. package/packages/server/src/services/todoStore.js +58 -0
  121. package/packages/server/src/services/usageTracker.js +69 -0
  122. package/packages/server/src/services/withConcurrencyGuard.js +110 -0
  123. package/packages/server/src/websocket.js +10 -0
  124. package/packages/server/src/ws/WebSocketManager.js +240 -0
  125. package/packages/server/src/ws/index.js +50 -0
  126. package/packages/shared/package.json +27 -0
  127. package/packages/shared/src/constants.js +44 -0
  128. package/packages/shared/src/contracts/canvas.js +25 -0
  129. package/packages/shared/src/contracts/commandButtons.js +36 -0
  130. package/packages/shared/src/contracts/kanban.js +142 -0
  131. package/packages/shared/src/contracts/projects.js +63 -0
  132. package/packages/shared/src/contracts/providers.js +81 -0
  133. package/packages/shared/src/contracts/quickResponses.js +44 -0
  134. package/packages/shared/src/contracts/sessions.js +112 -0
  135. package/packages/shared/src/contracts/templates.js +51 -0
  136. package/packages/shared/src/index.js +5 -0
  137. package/packages/shared/src/protocol.js +76 -0
  138. package/packages/shared/src/routeParams.js +36 -0
  139. package/packages/shared/src/types.js +167 -0
  140. package/packages/shared/src/utils.js +101 -0
  141. package/packages/web/dist/assets/ActiveSessionsView-BQc76Jc8.js +1 -0
  142. package/packages/web/dist/assets/ActiveSessionsView-ofSvx-K1.css +1 -0
  143. package/packages/web/dist/assets/AgentLogsView-CTCjHjsu.js +2 -0
  144. package/packages/web/dist/assets/AgentLogsView-D90PnQVk.css +1 -0
  145. package/packages/web/dist/assets/ApiClient-Dbs1H78V.js +1 -0
  146. package/packages/web/dist/assets/ArchiveConfirmModal-CCxSZ52u.js +1 -0
  147. package/packages/web/dist/assets/ArchiveConfirmModal-CQZeuYBz.css +1 -0
  148. package/packages/web/dist/assets/CommandButtonDetailView-CF_-LXpU.js +1 -0
  149. package/packages/web/dist/assets/CommandButtonDetailView-DBm3rzhw.css +1 -0
  150. package/packages/web/dist/assets/EffortLevelSelector-BQaQmU2d.css +1 -0
  151. package/packages/web/dist/assets/EffortLevelSelector-DPofLvm-.js +1 -0
  152. package/packages/web/dist/assets/GeneralSettingsView-BCf53fpC.css +1 -0
  153. package/packages/web/dist/assets/GeneralSettingsView-BY1G-Kv8.js +1 -0
  154. package/packages/web/dist/assets/InterpolationHelp-CgdbNcJB.js +1 -0
  155. package/packages/web/dist/assets/InterpolationHelp-iNxTxmhs.css +1 -0
  156. package/packages/web/dist/assets/MarkdownEditor-CqT1U8lo.js +2 -0
  157. package/packages/web/dist/assets/MarkdownEditor-enuH2yvP.css +1 -0
  158. package/packages/web/dist/assets/ModelSelector-BBn_Ve0D.js +1 -0
  159. package/packages/web/dist/assets/ModelSelector-DPPD-92R.css +1 -0
  160. package/packages/web/dist/assets/NewSessionView-Bo5l49nu.js +3 -0
  161. package/packages/web/dist/assets/NewSessionView-Byoi1XdQ.css +1 -0
  162. package/packages/web/dist/assets/PathChooser-BoMGzeg2.css +1 -0
  163. package/packages/web/dist/assets/PathChooser-Cx9gQ-Qt.js +1 -0
  164. package/packages/web/dist/assets/ProjectEditView-BFuscj-V.js +1 -0
  165. package/packages/web/dist/assets/ProjectEditView-DNwBUNRk.css +1 -0
  166. package/packages/web/dist/assets/ProjectListView-C55H1JHQ.css +1 -0
  167. package/packages/web/dist/assets/ProjectListView-Dj0jBZ46.js +1 -0
  168. package/packages/web/dist/assets/ProjectNewView-Brdp-xUu.js +1 -0
  169. package/packages/web/dist/assets/ProjectNewView-CpgE4R-l.css +1 -0
  170. package/packages/web/dist/assets/ProvidersView-B_QQF3RM.css +1 -0
  171. package/packages/web/dist/assets/ProvidersView-Cxc-1skq.js +1 -0
  172. package/packages/web/dist/assets/QuickResponseSettings-B2eVAtHW.js +1 -0
  173. package/packages/web/dist/assets/QuickResponseSettings-B8188A1D.css +1 -0
  174. package/packages/web/dist/assets/QuickResponsesPanel-DIBQFj0W.css +1 -0
  175. package/packages/web/dist/assets/QuickResponsesPanel-lU8pW2B0.js +1 -0
  176. package/packages/web/dist/assets/ResizableTextarea-B5nAA0RV.css +1 -0
  177. package/packages/web/dist/assets/ResizableTextarea-DSy1mWGY.js +1 -0
  178. package/packages/web/dist/assets/SessionCard-BvjLwVYg.js +1 -0
  179. package/packages/web/dist/assets/SessionCard-D20G3bX8.css +1 -0
  180. package/packages/web/dist/assets/SessionDetailView-BQbPg-RJ.js +36 -0
  181. package/packages/web/dist/assets/SessionDetailView-BrMG4p2-.css +1 -0
  182. package/packages/web/dist/assets/SessionFormOptions-BgqFR-5f.js +1 -0
  183. package/packages/web/dist/assets/SessionFormOptions-BuLlDF-7.css +1 -0
  184. package/packages/web/dist/assets/SessionListView-BAIBtJF7.css +1 -0
  185. package/packages/web/dist/assets/SessionListView-CYIHI8qF.js +1 -0
  186. package/packages/web/dist/assets/SessionLogStream-B-FwUMJQ.js +18 -0
  187. package/packages/web/dist/assets/SessionLogStream-zPUTiGbe.css +1 -0
  188. package/packages/web/dist/assets/SettingsView-DC8-hTQ-.css +1 -0
  189. package/packages/web/dist/assets/SettingsView-fZxpiGp7.js +1 -0
  190. package/packages/web/dist/assets/SlashCommandWizard-BB30cSvo.css +1 -0
  191. package/packages/web/dist/assets/SlashCommandWizard-BgaOw9W3.js +1 -0
  192. package/packages/web/dist/assets/SummarySettingsView-DcsmSVJI.css +1 -0
  193. package/packages/web/dist/assets/SummarySettingsView-eeu1Xq86.js +1 -0
  194. package/packages/web/dist/assets/TemplateDetailView-DEPKSwDo.js +1 -0
  195. package/packages/web/dist/assets/TemplateDetailView-DT2m06W7.css +1 -0
  196. package/packages/web/dist/assets/apl-B4CMkyY2.js +1 -0
  197. package/packages/web/dist/assets/asciiarmor-Df11BRmG.js +1 -0
  198. package/packages/web/dist/assets/asn1-EdZsLKOL.js +1 -0
  199. package/packages/web/dist/assets/asterisk-B-8jnY81.js +1 -0
  200. package/packages/web/dist/assets/brainfuck-C4LP7Hcl.js +1 -0
  201. package/packages/web/dist/assets/clike-B9uivgTg.js +1 -0
  202. package/packages/web/dist/assets/clojure-BMjYHr_A.js +1 -0
  203. package/packages/web/dist/assets/cmake-BQqOBYOt.js +1 -0
  204. package/packages/web/dist/assets/cobol-CWcv1MsR.js +1 -0
  205. package/packages/web/dist/assets/coffeescript-S37ZYGWr.js +1 -0
  206. package/packages/web/dist/assets/commandButtons-DNSHH8IA.js +4 -0
  207. package/packages/web/dist/assets/commonlisp-DBKNyK5s.js +1 -0
  208. package/packages/web/dist/assets/crystal-SjHAIU92.js +1 -0
  209. package/packages/web/dist/assets/css-BnMrqG3P.js +1 -0
  210. package/packages/web/dist/assets/cypher-C_CwsFkJ.js +1 -0
  211. package/packages/web/dist/assets/d-pRatUO7H.js +1 -0
  212. package/packages/web/dist/assets/diff-DbItnlRl.js +1 -0
  213. package/packages/web/dist/assets/dockerfile-BKs6k2Af.js +1 -0
  214. package/packages/web/dist/assets/dtd-DF_7sFjM.js +1 -0
  215. package/packages/web/dist/assets/dylan-DwRh75JA.js +1 -0
  216. package/packages/web/dist/assets/ebnf-CDyGwa7X.js +1 -0
  217. package/packages/web/dist/assets/ecl-Cabwm37j.js +1 -0
  218. package/packages/web/dist/assets/eiffel-CnydiIhH.js +1 -0
  219. package/packages/web/dist/assets/elm-vLlmbW-K.js +1 -0
  220. package/packages/web/dist/assets/erlang-BNw1qcRV.js +1 -0
  221. package/packages/web/dist/assets/factor-kuTfRLto.js +1 -0
  222. package/packages/web/dist/assets/fcl-Kvtd6kyn.js +1 -0
  223. package/packages/web/dist/assets/forth-Ffai-XNe.js +1 -0
  224. package/packages/web/dist/assets/fortran-DYz_wnZ1.js +1 -0
  225. package/packages/web/dist/assets/gas-Bneqetm1.js +1 -0
  226. package/packages/web/dist/assets/gherkin-heZmZLOM.js +1 -0
  227. package/packages/web/dist/assets/groovy-D9Dt4D0W.js +1 -0
  228. package/packages/web/dist/assets/haskell-BWDZoCOh.js +1 -0
  229. package/packages/web/dist/assets/haxe-H-WmDvRZ.js +1 -0
  230. package/packages/web/dist/assets/http-DBlCnlav.js +1 -0
  231. package/packages/web/dist/assets/idl-BEugSyMb.js +1 -0
  232. package/packages/web/dist/assets/index-BZlHgDSz.js +1 -0
  233. package/packages/web/dist/assets/index-BhWX8AfE.js +2 -0
  234. package/packages/web/dist/assets/index-Bi3XvF_f.js +1 -0
  235. package/packages/web/dist/assets/index-BqXoPf_D.js +1 -0
  236. package/packages/web/dist/assets/index-CAuTOZSD.js +1 -0
  237. package/packages/web/dist/assets/index-CKYk-fkb.js +1 -0
  238. package/packages/web/dist/assets/index-CTumW_tV.js +318 -0
  239. package/packages/web/dist/assets/index-CVOJVSsC.js +82 -0
  240. package/packages/web/dist/assets/index-CXK2Z3_z.js +1 -0
  241. package/packages/web/dist/assets/index-CYllQ3Vd.js +1 -0
  242. package/packages/web/dist/assets/index-CpsfI08O.js +1 -0
  243. package/packages/web/dist/assets/index-DQkhDeTA.js +3 -0
  244. package/packages/web/dist/assets/index-DWP8iCBp.js +1 -0
  245. package/packages/web/dist/assets/index-DkVb9W_J.js +1 -0
  246. package/packages/web/dist/assets/index-DmKHPbIa.js +1 -0
  247. package/packages/web/dist/assets/index-DrlQi03X.js +1 -0
  248. package/packages/web/dist/assets/index-gmCCsCQ1.css +1 -0
  249. package/packages/web/dist/assets/index-prTEzzgO.js +1 -0
  250. package/packages/web/dist/assets/index-wqgejMCM.js +1 -0
  251. package/packages/web/dist/assets/index-yh0ZHIWw.js +7 -0
  252. package/packages/web/dist/assets/javascript-qCveANmP.js +1 -0
  253. package/packages/web/dist/assets/julia-DuME0IfC.js +1 -0
  254. package/packages/web/dist/assets/livescript-BwQOo05w.js +1 -0
  255. package/packages/web/dist/assets/lua-BgMRiT3U.js +1 -0
  256. package/packages/web/dist/assets/mathematica-DTrFuWx2.js +1 -0
  257. package/packages/web/dist/assets/mbox-CNhZ1qSd.js +1 -0
  258. package/packages/web/dist/assets/mirc-CjQqDB4T.js +1 -0
  259. package/packages/web/dist/assets/mllike-CXdrOF99.js +1 -0
  260. package/packages/web/dist/assets/modelica-Dc1JOy9r.js +1 -0
  261. package/packages/web/dist/assets/mscgen-BA5vi2Kp.js +1 -0
  262. package/packages/web/dist/assets/mumps-BT43cFF4.js +1 -0
  263. package/packages/web/dist/assets/nginx-DdIZxoE0.js +1 -0
  264. package/packages/web/dist/assets/nsis-LdVXkNf5.js +1 -0
  265. package/packages/web/dist/assets/ntriples-BfvgReVJ.js +1 -0
  266. package/packages/web/dist/assets/octave-Ck1zUtKM.js +1 -0
  267. package/packages/web/dist/assets/oz-BzwKVEFT.js +1 -0
  268. package/packages/web/dist/assets/pascal--L3eBynH.js +1 -0
  269. package/packages/web/dist/assets/perl-CdXCOZ3F.js +1 -0
  270. package/packages/web/dist/assets/pig-CevX1Tat.js +1 -0
  271. package/packages/web/dist/assets/powershell-CFHJl5sT.js +1 -0
  272. package/packages/web/dist/assets/projects-DbBQQH-V.js +1 -0
  273. package/packages/web/dist/assets/properties-C78fOPTZ.js +1 -0
  274. package/packages/web/dist/assets/protobuf-ChK-085T.js +1 -0
  275. package/packages/web/dist/assets/providers-ceCc4xRU.js +1 -0
  276. package/packages/web/dist/assets/pug-DukmZTjD.js +1 -0
  277. package/packages/web/dist/assets/puppet-DMA9R1ak.js +1 -0
  278. package/packages/web/dist/assets/python-BuPzkPfP.js +1 -0
  279. package/packages/web/dist/assets/q-pXgVlZs6.js +1 -0
  280. package/packages/web/dist/assets/r-DUYO_cvP.js +1 -0
  281. package/packages/web/dist/assets/rpm-CTu-6PCP.js +1 -0
  282. package/packages/web/dist/assets/ruby-B2Rjki9n.js +1 -0
  283. package/packages/web/dist/assets/sas-B4kiWyti.js +1 -0
  284. package/packages/web/dist/assets/scheme-C41bIUwD.js +1 -0
  285. package/packages/web/dist/assets/sessions-D681M81k.js +1 -0
  286. package/packages/web/dist/assets/settings-D0evez2V.js +1 -0
  287. package/packages/web/dist/assets/shell-CjFT_Tl9.js +1 -0
  288. package/packages/web/dist/assets/sieve-C3Gn_uJK.js +1 -0
  289. package/packages/web/dist/assets/simple-mode-GW_nhZxv.js +1 -0
  290. package/packages/web/dist/assets/smalltalk-CnHTOXQT.js +1 -0
  291. package/packages/web/dist/assets/solr-DehyRSwq.js +1 -0
  292. package/packages/web/dist/assets/sparql-DkYu6x3z.js +1 -0
  293. package/packages/web/dist/assets/spreadsheet-BCZA_wO0.js +1 -0
  294. package/packages/web/dist/assets/sql-D0XecflT.js +1 -0
  295. package/packages/web/dist/assets/stex-C3f8Ysf7.js +1 -0
  296. package/packages/web/dist/assets/style-BTin-zR_.css +1 -0
  297. package/packages/web/dist/assets/stylus-B533Al4x.js +1 -0
  298. package/packages/web/dist/assets/swift-BzpIVaGY.js +1 -0
  299. package/packages/web/dist/assets/tcl-DVfN8rqt.js +1 -0
  300. package/packages/web/dist/assets/textile-CnDTJFAw.js +1 -0
  301. package/packages/web/dist/assets/tiddlywiki-DO-Gjzrf.js +1 -0
  302. package/packages/web/dist/assets/tiki-DGYXhP31.js +1 -0
  303. package/packages/web/dist/assets/toml-Bm5Em-hy.js +1 -0
  304. package/packages/web/dist/assets/troff-wAsdV37c.js +1 -0
  305. package/packages/web/dist/assets/ttcn-CfJYG6tj.js +1 -0
  306. package/packages/web/dist/assets/ttcn-cfg-B9xdYoR4.js +1 -0
  307. package/packages/web/dist/assets/turtle-B1tBg_DP.js +1 -0
  308. package/packages/web/dist/assets/vb-CmGdzxic.js +1 -0
  309. package/packages/web/dist/assets/vbscript-BuJXcnF6.js +1 -0
  310. package/packages/web/dist/assets/velocity-D8B20fx6.js +1 -0
  311. package/packages/web/dist/assets/verilog-C6RDOZhf.js +1 -0
  312. package/packages/web/dist/assets/vhdl-lSbBsy5d.js +1 -0
  313. package/packages/web/dist/assets/webidl-ZXfAyPTL.js +1 -0
  314. package/packages/web/dist/assets/xquery-CQfU5ijd.js +1 -0
  315. package/packages/web/dist/assets/yacas-BJ4BC0dw.js +1 -0
  316. package/packages/web/dist/assets/z80-Hz9HOZM7.js +1 -0
  317. package/packages/web/dist/favicon.png +0 -0
  318. package/packages/web/dist/index.html +17 -0
  319. package/packages/web/dist/logo.png +0 -0
@@ -0,0 +1,169 @@
1
+ import { Router } from 'express';
2
+ import { sessions, projects } from '../database.js';
3
+ import * as slashCommandService from '../services/slashCommandService.js';
4
+ import { continueSession } from '../services/sessionManager.js';
5
+
6
+ const router = Router();
7
+
8
+ /**
9
+ * GET /api/commands
10
+ * List all available slash commands for a directory
11
+ *
12
+ * Discovers commands from:
13
+ * - Project .claude/commands/ folder
14
+ * - User ~/.claude/commands/ folder
15
+ * - Installed plugins
16
+ * - Built-in Claude Code commands
17
+ *
18
+ * Query params:
19
+ * - directory: Working directory to discover commands from (required)
20
+ */
21
+ router.get('/', async (req, res) => {
22
+ const { directory } = req.query;
23
+
24
+ if (!directory) {
25
+ return res.status(400).json({ error: 'directory query parameter is required' });
26
+ }
27
+
28
+ try {
29
+ const commands = await slashCommandService.getCommands(directory);
30
+ res.json(commands);
31
+ } catch (err) {
32
+ console.error('Error getting commands:', err);
33
+ res.status(500).json({ error: err.message });
34
+ }
35
+ });
36
+
37
+ /**
38
+ * GET /api/commands/:name
39
+ * Get a single command's details
40
+ *
41
+ * Query params:
42
+ * - directory: Working directory to discover commands from (required)
43
+ */
44
+ router.get('/:name', async (req, res) => {
45
+ const { directory } = req.query;
46
+ const { name } = req.params;
47
+
48
+ if (!directory) {
49
+ return res.status(400).json({ error: 'directory query parameter is required' });
50
+ }
51
+
52
+ try {
53
+ const command = await slashCommandService.getCommand(directory, name);
54
+
55
+ if (!command) {
56
+ return res.status(404).json({ error: `Command not found: ${name}` });
57
+ }
58
+
59
+ res.json(command);
60
+ } catch (err) {
61
+ console.error('Error getting command:', err);
62
+ res.status(500).json({ error: err.message });
63
+ }
64
+ });
65
+
66
+ /**
67
+ * POST /api/commands/:name/execute
68
+ * Execute a slash command in a session
69
+ *
70
+ * Body:
71
+ * - sessionId: Session to execute command in (required)
72
+ * - args: Object with argument values keyed by argument name (optional)
73
+ */
74
+ router.post('/:name/execute', async (req, res) => {
75
+ const { name } = req.params;
76
+ const { sessionId, args = {} } = req.body;
77
+
78
+ if (!sessionId) {
79
+ return res.status(400).json({ error: 'sessionId is required' });
80
+ }
81
+
82
+ // Get the session
83
+ const session = sessions.getById(sessionId);
84
+ if (!session) {
85
+ return res.status(404).json({ error: 'Session not found' });
86
+ }
87
+
88
+ // Get the project to find the working directory
89
+ const project = projects.getById(session.projectId);
90
+ if (!project) {
91
+ return res.status(404).json({ error: 'Project not found' });
92
+ }
93
+
94
+ // Determine working directory (may be a worktree)
95
+ const workingDirectory = session.gitWorktree || project.workingDirectory;
96
+
97
+ // The session must be in a state that accepts messages (waiting, stopped, error)
98
+ const validStatuses = ['waiting', 'stopped', 'error'];
99
+ if (!validStatuses.includes(session.status)) {
100
+ return res.status(400).json({
101
+ error: `Session is not ready for commands. Current status: ${session.status}`,
102
+ });
103
+ }
104
+
105
+ try {
106
+ // Check if this is a skill — skills need special handling:
107
+ // skill body → system prompt, user args → user message
108
+ const skillInvocation = await slashCommandService.buildSkillInvocation(
109
+ workingDirectory,
110
+ name,
111
+ args
112
+ );
113
+
114
+ if (skillInvocation) {
115
+ // Skill: inject body as system prompt context, args as user message
116
+ const skillSystemPrompt = slashCommandService.buildSkillSystemPrompt(
117
+ project.systemPrompt || null,
118
+ skillInvocation
119
+ );
120
+ const userMessage = slashCommandService.buildSkillUserMessage(skillInvocation);
121
+
122
+ continueSession(
123
+ sessionId,
124
+ userMessage,
125
+ workingDirectory,
126
+ { systemPrompt: skillSystemPrompt } // No file attachments for slash commands
127
+ ).catch(err => {
128
+ console.error('Error executing skill:', err);
129
+ });
130
+
131
+ res.json({
132
+ success: true,
133
+ command: name,
134
+ message: userMessage,
135
+ });
136
+ return;
137
+ }
138
+
139
+ // Non-skill command: build command string and send as user message
140
+ const commandString = await slashCommandService.buildCommandString(
141
+ workingDirectory,
142
+ name,
143
+ args
144
+ );
145
+
146
+ // Use continueSession to send the command
147
+ // This handles the full message flow including broadcasting to WebSocket
148
+ continueSession(
149
+ sessionId,
150
+ commandString,
151
+ workingDirectory,
152
+ { systemPrompt: project.systemPrompt || null } // No file attachments for slash commands
153
+ ).catch(err => {
154
+ // Log but don't throw - continueSession runs asynchronously
155
+ console.error('Error executing command:', err);
156
+ });
157
+
158
+ res.json({
159
+ success: true,
160
+ command: name,
161
+ message: commandString,
162
+ });
163
+ } catch (err) {
164
+ console.error('Error executing command:', err);
165
+ res.status(500).json({ error: err.message });
166
+ }
167
+ });
168
+
169
+ export default router;
@@ -0,0 +1,62 @@
1
+ import { Router } from 'express';
2
+ import { readdir } from 'fs/promises';
3
+ import { homedir } from 'os';
4
+ import { resolve, dirname, normalize } from 'path';
5
+
6
+ const router = Router();
7
+
8
+ /**
9
+ * GET /api/filesystem/browse - Browse directory contents
10
+ * Query params:
11
+ * - path: Directory path to browse (defaults to home directory)
12
+ * Returns only directories, sorted alphabetically
13
+ */
14
+ router.get('/browse', async (req, res) => {
15
+ try {
16
+ // Use provided path or default to home directory
17
+ const requestedPath = req.query.path ? String(req.query.path) : homedir();
18
+ const normalizedPath = normalize(resolve(requestedPath));
19
+
20
+ // Get parent directory (null if at root)
21
+ const parent = normalizedPath === '/' ? null : dirname(normalizedPath);
22
+
23
+ let entries = [];
24
+ let error = null;
25
+
26
+ try {
27
+ const dirents = await readdir(normalizedPath, { withFileTypes: true });
28
+
29
+ // Filter to only directories and sort alphabetically
30
+ entries = dirents
31
+ .filter((dirent) => dirent.isDirectory())
32
+ .map((dirent) => ({
33
+ name: dirent.name,
34
+ type: 'directory',
35
+ }))
36
+ .sort((a, b) => a.name.localeCompare(b.name));
37
+ } catch (err) {
38
+ // Handle permission errors or non-existent directories gracefully
39
+ if (err.code === 'EACCES') {
40
+ error = 'Permission denied';
41
+ } else if (err.code === 'ENOENT') {
42
+ error = 'Directory not found';
43
+ } else if (err.code === 'ENOTDIR') {
44
+ error = 'Not a directory';
45
+ } else {
46
+ error = 'Unable to read directory';
47
+ }
48
+ }
49
+
50
+ res.json({
51
+ path: normalizedPath,
52
+ parent,
53
+ entries,
54
+ error,
55
+ });
56
+ } catch (err) {
57
+ console.error('Filesystem browse error:', err);
58
+ res.status(500).json({ error: 'Failed to browse directory' });
59
+ }
60
+ });
61
+
62
+ export default router;
@@ -0,0 +1,85 @@
1
+ import { Router } from 'express';
2
+ import { projects } from '../database.js';
3
+ import * as gitService from '../services/gitService.js';
4
+
5
+ const router = Router();
6
+
7
+ // GET /api/projects/:id/git/status - Check if git repo, get branches
8
+ router.get('/projects/:id/status', async (req, res) => {
9
+ const project = projects.getById(req.params.id);
10
+ if (!project) {
11
+ return res.status(404).json({ error: 'Project not found' });
12
+ }
13
+
14
+ try {
15
+ const isRepo = await gitService.isGitRepo(project.workingDirectory);
16
+ if (!isRepo) {
17
+ return res.json({ isGitRepo: false });
18
+ }
19
+
20
+ const [branches, currentBranch] = await Promise.all([
21
+ gitService.getBranches(project.workingDirectory),
22
+ gitService.getCurrentBranch(project.workingDirectory),
23
+ ]);
24
+
25
+ res.json({
26
+ isGitRepo: true,
27
+ currentBranch,
28
+ branches,
29
+ });
30
+ } catch (error) {
31
+ res.status(500).json({ error: error.message });
32
+ }
33
+ });
34
+
35
+ // GET /api/projects/:id/git/worktrees - List worktrees
36
+ router.get('/projects/:id/worktrees', async (req, res) => {
37
+ const project = projects.getById(req.params.id);
38
+ if (!project) {
39
+ return res.status(404).json({ error: 'Project not found' });
40
+ }
41
+
42
+ try {
43
+ const worktrees = await gitService.getWorktrees(project.workingDirectory);
44
+ res.json(worktrees);
45
+ } catch (error) {
46
+ res.status(500).json({ error: error.message });
47
+ }
48
+ });
49
+
50
+ // POST /api/projects/:id/git/worktrees - Create worktree
51
+ router.post('/projects/:id/worktrees', async (req, res) => {
52
+ const project = projects.getById(req.params.id);
53
+ if (!project) {
54
+ return res.status(404).json({ error: 'Project not found' });
55
+ }
56
+
57
+ const { branch, path } = req.body;
58
+ if (!branch || !path) {
59
+ return res.status(400).json({ error: 'Branch and path are required' });
60
+ }
61
+
62
+ try {
63
+ const worktree = await gitService.createWorktree(project.workingDirectory, branch, path);
64
+ res.status(201).json(worktree);
65
+ } catch (error) {
66
+ res.status(500).json({ error: error.message });
67
+ }
68
+ });
69
+
70
+ // DELETE /api/projects/:id/git/worktrees/:path - Remove worktree
71
+ router.delete('/projects/:id/worktrees/:path(*)', async (req, res) => {
72
+ const project = projects.getById(req.params.id);
73
+ if (!project) {
74
+ return res.status(404).json({ error: 'Project not found' });
75
+ }
76
+
77
+ try {
78
+ await gitService.removeWorktree(project.workingDirectory, req.params.path);
79
+ res.status(204).send();
80
+ } catch (error) {
81
+ res.status(500).json({ error: error.message });
82
+ }
83
+ });
84
+
85
+ export default router;
@@ -0,0 +1,44 @@
1
+ import { Router } from 'express';
2
+ import projectsRouter from './projects.js';
3
+ import sessionsRouter from './sessions.js';
4
+ import templatesRouter from './templates.js';
5
+ import canvasRouter from './canvas.js';
6
+ import gitRouter from './git.js';
7
+ import filesystemRouter from './filesystem.js';
8
+ import quickResponsesRouter from './quickResponses.js';
9
+ import settingsRouter from './settings.js';
10
+ import providersRouter from './providers.js';
11
+ import commandsRouter from './commands.js';
12
+ import metricsRouter from './metrics.js';
13
+ import kanbanRouter from './kanban.js';
14
+
15
+ const router = Router();
16
+
17
+ // Lightweight identity endpoint — lets tools (e.g. pw.sh) verify which
18
+ // worktree / working directory this server instance belongs to.
19
+ router.get('/server-info', (_req, res) => {
20
+ res.json({ cwd: process.cwd() });
21
+ });
22
+
23
+ router.use('/projects', projectsRouter);
24
+ router.use('/sessions', sessionsRouter);
25
+ router.use('/templates', templatesRouter);
26
+ router.use('/git', gitRouter);
27
+ router.use('/filesystem', filesystemRouter);
28
+ router.use('/settings', settingsRouter);
29
+ router.use('/providers', providersRouter);
30
+ router.use('/commands', commandsRouter)
31
+
32
+ // Canvas routes are nested under sessions
33
+ router.use('/sessions', canvasRouter);
34
+
35
+ // Kanban routes are nested under projects
36
+ router.use('/projects/:projectId/kanban', kanbanRouter);
37
+
38
+ // Metrics routes (agent call stats)
39
+ router.use('/', metricsRouter);
40
+
41
+ // Quick responses routes (nested under both projects and standalone)
42
+ router.use('/', quickResponsesRouter);
43
+
44
+ export default router;
@@ -0,0 +1,342 @@
1
+ import { Router } from 'express';
2
+ import { kanbanBoards, kanbanLanes, kanbanCards, projects } from '../database.js';
3
+ import { broadcastToProject } from '../websocket.js';
4
+ import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
5
+ import {
6
+ CreateKanbanLaneRequest,
7
+ UpdateKanbanLaneRequest,
8
+ ReorderKanbanLanesRequest,
9
+ CreateKanbanCardRequest,
10
+ MoveKanbanCardRequest,
11
+ ReorderKanbanCardsRequest,
12
+ } from '../../../shared/src/contracts/kanban.js';
13
+ import { moveCard as moveCardService } from '../services/kanbanService.js';
14
+
15
+ const router = Router({ mergeParams: true });
16
+
17
+ /**
18
+ * Helper to build full board response with lanes and cards
19
+ */
20
+ function buildFullBoardResponse(board) {
21
+ if (!board) return null;
22
+
23
+ const lanes = kanbanLanes.getByBoardId(board.id);
24
+ const allCards = kanbanCards.getByBoardId(board.id);
25
+
26
+ // Group cards by lane
27
+ const cardsByLane = {};
28
+ for (const lane of lanes) {
29
+ cardsByLane[lane.id] = [];
30
+ }
31
+ for (const card of allCards) {
32
+ if (cardsByLane[card.laneId]) {
33
+ cardsByLane[card.laneId].push(card);
34
+ }
35
+ }
36
+
37
+ return {
38
+ id: board.id,
39
+ projectId: board.projectId,
40
+ lanes: lanes.map(lane => ({
41
+ ...lane,
42
+ cards: cardsByLane[lane.id] || [],
43
+ })),
44
+ createdAt: board.createdAt,
45
+ updatedAt: board.updatedAt,
46
+ };
47
+ }
48
+
49
+ // ============== Board Endpoints ==============
50
+
51
+ /**
52
+ * GET /api/projects/:projectId/kanban
53
+ * Get board with all lanes and cards. Auto-creates if kanban_enabled.
54
+ */
55
+ router.get('/', (req, res) => {
56
+ const { projectId } = req.params;
57
+
58
+ const project = projects.getById(projectId);
59
+ if (!project) {
60
+ return res.status(404).json({ error: 'Project not found' });
61
+ }
62
+
63
+ // If kanban is disabled, return null
64
+ if (!project.kanbanEnabled) {
65
+ return res.json(null);
66
+ }
67
+
68
+ // Get or create board
69
+ const board = kanbanBoards.getOrCreateForProject(projectId);
70
+ const fullBoard = buildFullBoardResponse(board);
71
+
72
+ res.json(fullBoard);
73
+ });
74
+
75
+ /**
76
+ * DELETE /api/projects/:projectId/kanban
77
+ * Delete board (resets all kanban data)
78
+ */
79
+ router.delete('/', (req, res) => {
80
+ const { projectId } = req.params;
81
+
82
+ const board = kanbanBoards.getByProjectId(projectId);
83
+ if (!board) {
84
+ return res.status(404).json({ error: 'Board not found' });
85
+ }
86
+
87
+ kanbanBoards.delete(board.id);
88
+
89
+ broadcastToProject(projectId, WS_MESSAGE_TYPES.KANBAN_BOARD_UPDATED, {
90
+ projectId,
91
+ board: null,
92
+ });
93
+
94
+ res.status(204).send();
95
+ });
96
+
97
+ // ============== Lane Endpoints ==============
98
+
99
+ /**
100
+ * POST /api/projects/:projectId/kanban/lanes
101
+ * Create a new lane
102
+ */
103
+ router.post('/lanes', (req, res) => {
104
+ const { projectId } = req.params;
105
+
106
+ const result = CreateKanbanLaneRequest.safeParse(req.body);
107
+ if (!result.success) {
108
+ return res.status(400).json({ error: result.error.issues[0].message });
109
+ }
110
+
111
+ const board = kanbanBoards.getByProjectId(projectId);
112
+ if (!board) {
113
+ return res.status(404).json({ error: 'Board not found' });
114
+ }
115
+
116
+ const lane = kanbanLanes.create(board.id, result.data);
117
+
118
+ // Broadcast updated board
119
+ const fullBoard = buildFullBoardResponse(board);
120
+ broadcastToProject(projectId, WS_MESSAGE_TYPES.KANBAN_BOARD_UPDATED, {
121
+ projectId,
122
+ board: fullBoard,
123
+ });
124
+
125
+ res.status(201).json(lane);
126
+ });
127
+
128
+ /**
129
+ * PATCH /api/projects/:projectId/kanban/lanes/:laneId
130
+ * Update a lane
131
+ */
132
+ router.patch('/lanes/:laneId', (req, res) => {
133
+ const { projectId, laneId } = req.params;
134
+
135
+ const result = UpdateKanbanLaneRequest.safeParse(req.body);
136
+ if (!result.success) {
137
+ return res.status(400).json({ error: result.error.issues[0].message });
138
+ }
139
+
140
+ const lane = kanbanLanes.getById(laneId);
141
+ if (!lane) {
142
+ return res.status(404).json({ error: 'Lane not found' });
143
+ }
144
+
145
+ const updated = kanbanLanes.update(laneId, result.data);
146
+
147
+ // Broadcast updated board
148
+ const board = kanbanBoards.getByProjectId(projectId);
149
+ const fullBoard = buildFullBoardResponse(board);
150
+ broadcastToProject(projectId, WS_MESSAGE_TYPES.KANBAN_BOARD_UPDATED, {
151
+ projectId,
152
+ board: fullBoard,
153
+ });
154
+
155
+ res.json(updated);
156
+ });
157
+
158
+ /**
159
+ * DELETE /api/projects/:projectId/kanban/lanes/:laneId
160
+ * Delete a lane
161
+ */
162
+ router.delete('/lanes/:laneId', (req, res) => {
163
+ const { projectId, laneId } = req.params;
164
+
165
+ const lane = kanbanLanes.getById(laneId);
166
+ if (!lane) {
167
+ return res.status(404).json({ error: 'Lane not found' });
168
+ }
169
+
170
+ kanbanLanes.delete(laneId);
171
+
172
+ // Broadcast updated board
173
+ const board = kanbanBoards.getByProjectId(projectId);
174
+ const fullBoard = buildFullBoardResponse(board);
175
+ broadcastToProject(projectId, WS_MESSAGE_TYPES.KANBAN_BOARD_UPDATED, {
176
+ projectId,
177
+ board: fullBoard,
178
+ });
179
+
180
+ res.status(204).send();
181
+ });
182
+
183
+ /**
184
+ * PUT /api/projects/:projectId/kanban/lanes/reorder
185
+ * Reorder all lanes
186
+ */
187
+ router.put('/lanes/reorder', (req, res) => {
188
+ const { projectId } = req.params;
189
+
190
+ const result = ReorderKanbanLanesRequest.safeParse(req.body);
191
+ if (!result.success) {
192
+ return res.status(400).json({ error: result.error.issues[0].message });
193
+ }
194
+
195
+ const board = kanbanBoards.getByProjectId(projectId);
196
+ if (!board) {
197
+ return res.status(404).json({ error: 'Board not found' });
198
+ }
199
+
200
+ kanbanLanes.reorder(board.id, result.data);
201
+
202
+ // Broadcast updated board
203
+ const fullBoard = buildFullBoardResponse(board);
204
+ broadcastToProject(projectId, WS_MESSAGE_TYPES.KANBAN_BOARD_UPDATED, {
205
+ projectId,
206
+ board: fullBoard,
207
+ });
208
+
209
+ res.json(fullBoard);
210
+ });
211
+
212
+ // ============== Card Endpoints ==============
213
+
214
+ /**
215
+ * POST /api/projects/:projectId/kanban/cards
216
+ * Add a session to the board (create card in a lane)
217
+ */
218
+ router.post('/cards', (req, res) => {
219
+ const { projectId } = req.params;
220
+
221
+ const result = CreateKanbanCardRequest.safeParse(req.body);
222
+ if (!result.success) {
223
+ return res.status(400).json({ error: result.error.issues[0].message });
224
+ }
225
+
226
+ const { sessionId, laneId } = result.data;
227
+
228
+ // Check if session already has a card
229
+ const existingCard = kanbanCards.getBySessionId(sessionId);
230
+ if (existingCard) {
231
+ return res.status(409).json({ error: 'Session already has a card on the board' });
232
+ }
233
+
234
+ // Verify lane exists
235
+ const lane = kanbanLanes.getById(laneId);
236
+ if (!lane) {
237
+ return res.status(404).json({ error: 'Lane not found' });
238
+ }
239
+
240
+ const card = kanbanCards.create(laneId, sessionId);
241
+
242
+ broadcastToProject(projectId, WS_MESSAGE_TYPES.KANBAN_CARD_ADDED, {
243
+ projectId,
244
+ card,
245
+ laneId,
246
+ });
247
+
248
+ res.status(201).json(card);
249
+ });
250
+
251
+ /**
252
+ * PATCH /api/projects/:projectId/kanban/cards/:cardId/move
253
+ * Move card to a different lane
254
+ */
255
+ router.patch('/cards/:cardId/move', async (req, res) => {
256
+ const { cardId } = req.params;
257
+
258
+ const result = MoveKanbanCardRequest.safeParse(req.body);
259
+ if (!result.success) {
260
+ return res.status(400).json({ error: result.error.issues[0].message });
261
+ }
262
+
263
+ const card = kanbanCards.getByIdWithLane(cardId);
264
+ if (!card) {
265
+ return res.status(404).json({ error: 'Card not found' });
266
+ }
267
+
268
+ const { targetLaneId, sortOrder, runOnEnterTemplate } = result.data;
269
+
270
+ // Verify target lane exists
271
+ const targetLane = kanbanLanes.getById(targetLaneId);
272
+ if (!targetLane) {
273
+ return res.status(404).json({ error: 'Target lane not found' });
274
+ }
275
+
276
+ try {
277
+ const movedCard = await moveCardService(cardId, targetLaneId, {
278
+ sortOrder,
279
+ runOnEnterTemplate,
280
+ });
281
+ res.json(movedCard);
282
+ } catch (error) {
283
+ console.error('Failed to move kanban card:', error);
284
+ res.status(500).json({ error: error.message });
285
+ }
286
+ });
287
+
288
+ /**
289
+ * DELETE /api/projects/:projectId/kanban/cards/:cardId
290
+ * Remove card from board
291
+ */
292
+ router.delete('/cards/:cardId', (req, res) => {
293
+ const { projectId, cardId } = req.params;
294
+
295
+ const card = kanbanCards.getByIdWithLane(cardId);
296
+ if (!card) {
297
+ return res.status(404).json({ error: 'Card not found' });
298
+ }
299
+
300
+ const laneId = card.laneId;
301
+ kanbanCards.delete(cardId);
302
+
303
+ broadcastToProject(projectId, WS_MESSAGE_TYPES.KANBAN_CARD_REMOVED, {
304
+ projectId,
305
+ cardId,
306
+ laneId,
307
+ });
308
+
309
+ res.status(204).send();
310
+ });
311
+
312
+ /**
313
+ * PUT /api/projects/:projectId/kanban/lanes/:laneId/cards/reorder
314
+ * Reorder cards within a lane
315
+ */
316
+ router.put('/lanes/:laneId/cards/reorder', (req, res) => {
317
+ const { projectId, laneId } = req.params;
318
+
319
+ const result = ReorderKanbanCardsRequest.safeParse(req.body);
320
+ if (!result.success) {
321
+ return res.status(400).json({ error: result.error.issues[0].message });
322
+ }
323
+
324
+ const lane = kanbanLanes.getById(laneId);
325
+ if (!lane) {
326
+ return res.status(404).json({ error: 'Lane not found' });
327
+ }
328
+
329
+ kanbanCards.reorder(laneId, result.data);
330
+
331
+ // Broadcast updated board
332
+ const board = kanbanBoards.getByProjectId(projectId);
333
+ const fullBoard = buildFullBoardResponse(board);
334
+ broadcastToProject(projectId, WS_MESSAGE_TYPES.KANBAN_BOARD_UPDATED, {
335
+ projectId,
336
+ board: fullBoard,
337
+ });
338
+
339
+ res.json({ success: true });
340
+ });
341
+
342
+ export default router;