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,384 @@
1
+ import { Router } from 'express';
2
+ import { projects, sessions, sessionTemplates, commandButtons, projectDefaults, commandRuns } from '../database.js';
3
+ import { commandRunner } from '../services/commandRunner.js';
4
+ import { CreateProjectRequest, UpdateProjectRequest, ProjectSessionDefaultsRequest } from '../../../shared/src/contracts/projects.js';
5
+ import { ProjectDefaultsRepository } from '../db/ProjectDefaultsRepository.js';
6
+ import { CreateSessionTemplateRequest } from '../../../shared/src/contracts/templates.js';
7
+ import { CreateCommandButtonRequest, UpdateCommandButtonRequest } from '../../../shared/src/contracts/commandButtons.js';
8
+ import { broadcastToProject } from '../websocket.js';
9
+ import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
10
+ import { handleUploadError, uploadMiddleware } from '../middleware/upload.js';
11
+ import {
12
+ generateInitialName,
13
+ prepareSessionConfig,
14
+ applyTemplateOverrides,
15
+ resolveNextTemplateId,
16
+ determineInitialStatus,
17
+ buildSchedulingUpdate,
18
+ setupAndStartSession,
19
+ } from './projects-session-helpers.js';
20
+ import { validateGitSettings, buildRunsBySession } from './projects-helpers.js';
21
+
22
+ // Error message constants
23
+ const ERR_PROJECT_NOT_FOUND = 'Project not found';
24
+
25
+ const router = Router();
26
+
27
+ // GET /api/projects - List all projects
28
+ router.get('/', (_req, res) => {
29
+ const allProjects = projects.getAll();
30
+ res.json(allProjects);
31
+ });
32
+
33
+ // POST /api/projects - Create project
34
+ router.post('/', (req, res) => {
35
+ const result = CreateProjectRequest.safeParse(req.body);
36
+ if (!result.success) {
37
+ return res.status(400).json({ error: result.error.issues[0].message });
38
+ }
39
+
40
+ const { name, workingDirectory, systemPrompt, onSessionCreated, onSessionDeleted } = result.data;
41
+ const project = projects.create(name, workingDirectory, systemPrompt || null, {
42
+ onSessionCreated: onSessionCreated || null,
43
+ onSessionDeleted: onSessionDeleted || null,
44
+ });
45
+ res.status(201).json(project);
46
+ });
47
+
48
+ // GET /api/projects/:id - Get project
49
+ router.get('/:id', (req, res) => {
50
+ const project = projects.getById(req.params.id);
51
+ if (!project) {
52
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
53
+ }
54
+ res.json(project);
55
+ });
56
+
57
+ // PUT /api/projects/:id - Update project
58
+ router.put('/:id', (req, res) => {
59
+ const project = projects.getById(req.params.id);
60
+ if (!project) {
61
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
62
+ }
63
+
64
+ const result = UpdateProjectRequest.safeParse(req.body);
65
+ if (!result.success) {
66
+ return res.status(400).json({ error: result.error.issues[0].message });
67
+ }
68
+
69
+ const updated = projects.update(req.params.id, result.data);
70
+ res.json(updated);
71
+ });
72
+
73
+ // DELETE /api/projects/:id - Delete project
74
+ router.delete('/:id', (req, res) => {
75
+ const project = projects.getById(req.params.id);
76
+ if (!project) {
77
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
78
+ }
79
+
80
+ projects.delete(req.params.id);
81
+ res.status(204).send();
82
+ });
83
+
84
+ // GET /api/projects/:id/sessions - List project sessions
85
+ // Supports ?archived=true|false to filter by archive status
86
+ // Supports ?starred=true|false to filter by starred status
87
+ // Supports ?limit=N&offset=M for pagination (returns pagination metadata when limit is specified)
88
+ // Each session includes latestCommandRuns (merged from DB completed runs + in-memory running commands)
89
+ router.get('/:id/sessions', (req, res) => {
90
+ const project = projects.getById(req.params.id);
91
+ if (!project) {
92
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
93
+ }
94
+
95
+ // Parse archived query param: undefined = all, 'true' = archived only, 'false' = non-archived only
96
+ const { archived, starred, limit, offset } = req.query;
97
+ let archivedFilter = null;
98
+ if (archived === 'true') archivedFilter = true;
99
+ else if (archived === 'false') archivedFilter = false;
100
+
101
+ let starredFilter = null;
102
+ if (starred === 'true') starredFilter = true;
103
+ else if (starred === 'false') starredFilter = false;
104
+
105
+ // Parse pagination params
106
+ const parsedLimit = limit ? parseInt(limit, 10) : null;
107
+ const parsedOffset = offset ? parseInt(offset, 10) : 0;
108
+
109
+ const projectSessions = sessions.getByProjectId(req.params.id, {
110
+ archived: archivedFilter,
111
+ starred: starredFilter,
112
+ limit: parsedLimit,
113
+ offset: parsedOffset,
114
+ });
115
+
116
+ // Get total count for pagination (only when limit is specified)
117
+ let total = null;
118
+ if (parsedLimit !== null) {
119
+ total = sessions.getCountByProjectId(req.params.id, {
120
+ archived: archivedFilter,
121
+ starred: starredFilter,
122
+ });
123
+ }
124
+
125
+ // Build merged index of latest command runs per session
126
+ const runsBySession = buildRunsBySession(
127
+ commandRuns.getLatestRunsForProject(req.params.id),
128
+ commandRunner.getRunningByProjectId(req.params.id, (sessionId) => sessions.getById(sessionId))
129
+ );
130
+
131
+ // Attach latestCommandRuns to each session as array
132
+ const sessionsWithRuns = projectSessions.map(session => ({
133
+ ...session,
134
+ latestCommandRuns: Object.values(runsBySession[session.id] || {}),
135
+ }));
136
+
137
+ // Return response with pagination metadata when limit is specified
138
+ if (parsedLimit !== null) {
139
+ res.json({
140
+ sessions: sessionsWithRuns,
141
+ pagination: {
142
+ total,
143
+ limit: parsedLimit,
144
+ offset: parsedOffset,
145
+ hasMore: parsedOffset + projectSessions.length < total,
146
+ },
147
+ });
148
+ } else {
149
+ // Backward compatible: return array when no pagination
150
+ res.json(sessionsWithRuns);
151
+ }
152
+ });
153
+
154
+ // POST /api/projects/:id/sessions - Create session
155
+ // Supports both JSON and multipart/form-data (for file attachments)
156
+ router.post('/:id/sessions', uploadMiddleware('files', 10), handleUploadError, async (req, res) => {
157
+ const project = projects.getById(req.params.id);
158
+ if (!project) {
159
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
160
+ }
161
+
162
+ const projectDefs = projectDefaults.getByProjectId(req.params.id);
163
+ const systemDefaults = ProjectDefaultsRepository.getSystemDefaults();
164
+ const config = prepareSessionConfig(req.body, projectDefs, systemDefaults);
165
+ config.files = req.files || [];
166
+
167
+ if (!config.prompt) {
168
+ return res.status(400).json({ error: 'Prompt is required' });
169
+ }
170
+
171
+ // Apply template overrides and resolve nextTemplateId
172
+ applyTemplateOverrides(config);
173
+ const { nextTemplateId, error: nextTemplateError } = resolveNextTemplateId(req.body, config.nextTemplateId || null);
174
+ if (nextTemplateError) {
175
+ return res.status(400).json({ error: nextTemplateError });
176
+ }
177
+ config.nextTemplateId = nextTemplateId;
178
+
179
+ const initialStatus = determineInitialStatus(config);
180
+
181
+ // Validate git settings for git repos
182
+ const gitError = await validateGitSettings(config, project);
183
+ if (gitError) {
184
+ return res.status(400).json({ error: gitError });
185
+ }
186
+
187
+ const sessionName = config.name || generateInitialName(config.prompt);
188
+ const session = sessions.create(req.params.id, sessionName, config.prompt, {
189
+ mode: config.mode,
190
+ thinkingEnabled: config.thinkingEnabled,
191
+ gitBranch: config.gitBranch,
192
+ parentSessionId: config.parentSessionId,
193
+ status: initialStatus,
194
+ model: config.model,
195
+ effortLevel: config.effortLevel,
196
+ });
197
+
198
+ // Apply optional post-create updates (next template + scheduling) in one pass
199
+ const postCreateUpdate = {
200
+ ...(nextTemplateId ? { nextTemplateId } : {}),
201
+ ...buildSchedulingUpdate(config, initialStatus),
202
+ };
203
+ if (Object.keys(postCreateUpdate).length > 0) {
204
+ sessions.update(session.id, postCreateUpdate);
205
+ }
206
+
207
+ // Setup git environment, start session, and broadcast
208
+ try {
209
+ const { updatedSession } = await setupAndStartSession({
210
+ session, config, project, projectId: req.params.id, files: config.files,
211
+ });
212
+ res.status(201).json(updatedSession);
213
+ } catch (error) {
214
+ console.error('Git setup error:', error);
215
+ const updatedSession = sessions.update(session.id, { status: 'error', error: error.message });
216
+
217
+ broadcastToProject(req.params.id, WS_MESSAGE_TYPES.SESSION_UPDATED, {
218
+ projectId: req.params.id,
219
+ sessionId: session.id,
220
+ session: updatedSession,
221
+ });
222
+
223
+ res.status(500).json({ error: `Git setup failed: ${error.message}` });
224
+ }
225
+ });
226
+
227
+ // GET /api/projects/:id/templates - List available templates for project (project + global)
228
+ router.get('/:id/templates', (req, res) => {
229
+ const project = projects.getById(req.params.id);
230
+ if (!project) {
231
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
232
+ }
233
+
234
+ const available = sessionTemplates.getAvailableForProject(req.params.id);
235
+ res.json(available);
236
+ });
237
+
238
+ // POST /api/projects/:id/templates - Create project template
239
+ router.post('/:id/templates', (req, res) => {
240
+ const project = projects.getById(req.params.id);
241
+ if (!project) {
242
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
243
+ }
244
+
245
+ const result = CreateSessionTemplateRequest.safeParse(req.body);
246
+ if (!result.success) {
247
+ return res.status(400).json({ error: result.error.issues[0].message });
248
+ }
249
+
250
+ const template = sessionTemplates.create({
251
+ projectId: req.params.id,
252
+ ...result.data,
253
+ });
254
+ res.status(201).json(template);
255
+ });
256
+
257
+ // GET /api/projects/:id/command-buttons - List all command buttons for project
258
+ router.get('/:id/command-buttons', (req, res) => {
259
+ const project = projects.getById(req.params.id);
260
+ if (!project) {
261
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
262
+ }
263
+
264
+ const buttons = commandButtons.getByProjectId(req.params.id);
265
+ res.json(buttons);
266
+ });
267
+
268
+ // POST /api/projects/:id/command-buttons - Create new command button
269
+ router.post('/:id/command-buttons', (req, res) => {
270
+ const project = projects.getById(req.params.id);
271
+ if (!project) {
272
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
273
+ }
274
+
275
+ const result = CreateCommandButtonRequest.safeParse(req.body);
276
+ if (!result.success) {
277
+ return res.status(400).json({ error: result.error.issues[0].message });
278
+ }
279
+
280
+ const button = commandButtons.create({
281
+ projectId: req.params.id,
282
+ label: result.data.label,
283
+ command: result.data.command,
284
+ sortOrder: result.data.sortOrder,
285
+ showOnList: result.data.showOnList,
286
+ });
287
+
288
+ res.status(201).json(button);
289
+ });
290
+
291
+ // GET /api/projects/:id/command-buttons/:buttonId - Get single button
292
+ router.get('/:id/command-buttons/:buttonId', (req, res) => {
293
+ const project = projects.getById(req.params.id);
294
+ if (!project) {
295
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
296
+ }
297
+
298
+ const button = commandButtons.getById(req.params.buttonId);
299
+ if (!button) {
300
+ return res.status(404).json({ error: 'Command button not found' });
301
+ }
302
+ res.json(button);
303
+ });
304
+
305
+ // PATCH /api/projects/:id/command-buttons/:buttonId - Update button
306
+ router.patch('/:id/command-buttons/:buttonId', (req, res) => {
307
+ const project = projects.getById(req.params.id);
308
+ if (!project) {
309
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
310
+ }
311
+
312
+ const button = commandButtons.getById(req.params.buttonId);
313
+ if (!button) {
314
+ return res.status(404).json({ error: 'Command button not found' });
315
+ }
316
+
317
+ const result = UpdateCommandButtonRequest.safeParse(req.body);
318
+ if (!result.success) {
319
+ return res.status(400).json({ error: result.error.issues[0].message });
320
+ }
321
+
322
+ const updated = commandButtons.update(req.params.buttonId, result.data);
323
+ res.json(updated);
324
+ });
325
+
326
+ // DELETE /api/projects/:id/command-buttons/:buttonId - Delete button
327
+ router.delete('/:id/command-buttons/:buttonId', (req, res) => {
328
+ const project = projects.getById(req.params.id);
329
+ if (!project) {
330
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
331
+ }
332
+
333
+ const button = commandButtons.getById(req.params.buttonId);
334
+ if (!button) {
335
+ return res.status(404).json({ error: 'Command button not found' });
336
+ }
337
+
338
+ commandButtons.delete(req.params.buttonId);
339
+ res.status(204).send();
340
+ });
341
+
342
+ // GET /api/projects/:id/session-defaults - Get session defaults for project
343
+ router.get('/:id/session-defaults', (req, res) => {
344
+ const project = projects.getById(req.params.id);
345
+ if (!project) {
346
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
347
+ }
348
+
349
+ const defaults = projectDefaults.getByProjectId(req.params.id);
350
+ if (!defaults) {
351
+ return res.json(null);
352
+ }
353
+
354
+ res.json(defaults);
355
+ });
356
+
357
+ // POST /api/projects/:id/session-defaults - Update/create session defaults for project
358
+ router.post('/:id/session-defaults', (req, res) => {
359
+ const project = projects.getById(req.params.id);
360
+ if (!project) {
361
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
362
+ }
363
+
364
+ const result = ProjectSessionDefaultsRequest.safeParse(req.body);
365
+ if (!result.success) {
366
+ return res.status(400).json({ error: result.error.issues[0].message });
367
+ }
368
+
369
+ const updated = projectDefaults.upsert(req.params.id, result.data);
370
+ res.status(200).json(updated);
371
+ });
372
+
373
+ // DELETE /api/projects/:id/session-defaults - Reset session defaults for project
374
+ router.delete('/:id/session-defaults', (req, res) => {
375
+ const project = projects.getById(req.params.id);
376
+ if (!project) {
377
+ return res.status(404).json({ error: ERR_PROJECT_NOT_FOUND });
378
+ }
379
+
380
+ projectDefaults.resetToDefaults(req.params.id);
381
+ res.json({ message: 'Session defaults reset to system defaults' });
382
+ });
383
+
384
+ export default router;
@@ -0,0 +1,249 @@
1
+ import { Router } from 'express';
2
+ import { modelProviders } from '../database.js';
3
+ import {
4
+ CreateProviderRequest,
5
+ UpdateProviderRequest,
6
+ CreateProviderModelRequest,
7
+ TestConnectionRequest,
8
+ } from '../../../shared/src/contracts/providers.js';
9
+ import { testProviderConnection } from '../services/providerTestService.js';
10
+
11
+ // Error message constants
12
+ const ERR_PROVIDER_NOT_FOUND = 'Provider not found';
13
+
14
+ const router = Router();
15
+
16
+ /**
17
+ * Redact auth token in provider response
18
+ * @param {Object} provider - Provider object
19
+ * @returns {Object} Provider with redacted auth token
20
+ */
21
+ function redactAuthToken(provider) {
22
+ if (!provider) return provider;
23
+ return {
24
+ ...provider,
25
+ authToken: provider.authToken ? '••••••••' : null,
26
+ };
27
+ }
28
+
29
+ // GET /api/providers - List all providers
30
+ router.get('/', (_req, res) => {
31
+ try {
32
+ const allProviders = modelProviders.getAll();
33
+ // Redact auth tokens in response
34
+ const sanitized = allProviders.map(redactAuthToken);
35
+ res.json(sanitized);
36
+ } catch (error) {
37
+ res.status(500).json({ error: error.message });
38
+ }
39
+ });
40
+
41
+ // POST /api/providers - Create provider
42
+ router.post('/', (req, res) => {
43
+ const result = CreateProviderRequest.safeParse(req.body);
44
+ if (!result.success) {
45
+ return res.status(400).json({ error: result.error.issues[0].message });
46
+ }
47
+
48
+ try {
49
+ const provider = modelProviders.create(result.data);
50
+ res.status(201).json(redactAuthToken(provider));
51
+ } catch (error) {
52
+ res.status(500).json({ error: error.message });
53
+ }
54
+ });
55
+
56
+ // GET /api/providers/:id - Get provider details
57
+ router.get('/:id', (req, res) => {
58
+ try {
59
+ const provider = modelProviders.getById(req.params.id);
60
+ if (!provider) {
61
+ return res.status(404).json({ error: ERR_PROVIDER_NOT_FOUND });
62
+ }
63
+ res.json(redactAuthToken(provider));
64
+ } catch (error) {
65
+ res.status(500).json({ error: error.message });
66
+ }
67
+ });
68
+
69
+ // PATCH /api/providers/:id - Update provider
70
+ router.patch('/:id', (req, res) => {
71
+ try {
72
+ const provider = modelProviders.getById(req.params.id);
73
+ if (!provider) {
74
+ return res.status(404).json({ error: ERR_PROVIDER_NOT_FOUND });
75
+ }
76
+
77
+ const result = UpdateProviderRequest.safeParse(req.body);
78
+ if (!result.success) {
79
+ return res.status(400).json({ error: result.error.issues[0].message });
80
+ }
81
+
82
+ const updated = modelProviders.update(req.params.id, result.data);
83
+ res.json(redactAuthToken(updated));
84
+ } catch (error) {
85
+ if (error.message === 'Cannot delete built-in provider') {
86
+ return res.status(403).json({ error: error.message });
87
+ }
88
+ res.status(500).json({ error: error.message });
89
+ }
90
+ });
91
+
92
+ // DELETE /api/providers/:id - Delete provider
93
+ router.delete('/:id', (req, res) => {
94
+ try {
95
+ const provider = modelProviders.getById(req.params.id);
96
+ if (!provider) {
97
+ return res.status(404).json({ error: ERR_PROVIDER_NOT_FOUND });
98
+ }
99
+
100
+ modelProviders.delete(req.params.id);
101
+ res.status(204).send();
102
+ } catch (error) {
103
+ if (error.message === 'Cannot delete built-in provider') {
104
+ return res.status(403).json({ error: error.message });
105
+ }
106
+ res.status(500).json({ error: error.message });
107
+ }
108
+ });
109
+
110
+ // POST /api/providers/test - Test provider configuration (before saving)
111
+ router.post('/test', async (req, res) => {
112
+ const result = TestConnectionRequest.safeParse(req.body);
113
+ if (!result.success) {
114
+ return res.status(400).json({ error: result.error.issues[0].message });
115
+ }
116
+
117
+ try {
118
+ const testResult = await testProviderConnection(result.data);
119
+ res.json(testResult);
120
+ } catch (error) {
121
+ res.status(500).json({
122
+ success: false,
123
+ message: error.message || 'Internal server error',
124
+ details: {
125
+ type: error.name,
126
+ },
127
+ });
128
+ }
129
+ });
130
+
131
+ // POST /api/providers/:id/test - Test existing provider connection
132
+ router.post('/:id/test', async (req, res) => {
133
+ try {
134
+ const provider = modelProviders.getById(req.params.id);
135
+ if (!provider) {
136
+ return res.status(404).json({ error: ERR_PROVIDER_NOT_FOUND });
137
+ }
138
+
139
+ // Pick the sonnet-tiered model (if any) as the test model, falling back to any first model
140
+ const sonnetModel = provider.models?.find((m) => m.tier === 'sonnet');
141
+ const testConfig = {
142
+ baseUrl: provider.baseUrl,
143
+ authToken: provider.authToken,
144
+ defaultSonnetModel: sonnetModel?.modelId,
145
+ apiTimeoutMs: provider.apiTimeoutMs,
146
+ };
147
+
148
+ const testResult = await testProviderConnection(testConfig);
149
+ res.json(testResult);
150
+ } catch (error) {
151
+ res.status(500).json({
152
+ success: false,
153
+ message: error.message || 'Internal server error',
154
+ details: {
155
+ type: error.name,
156
+ },
157
+ });
158
+ }
159
+ });
160
+
161
+ // GET /api/providers/:id/models - List models for provider
162
+ router.get('/:id/models', (req, res) => {
163
+ try {
164
+ const provider = modelProviders.getById(req.params.id);
165
+ if (!provider) {
166
+ return res.status(404).json({ error: ERR_PROVIDER_NOT_FOUND });
167
+ }
168
+
169
+ const models = modelProviders.getModels(req.params.id);
170
+ res.json(models);
171
+ } catch (error) {
172
+ res.status(500).json({ error: error.message });
173
+ }
174
+ });
175
+
176
+ // POST /api/providers/:id/models - Add model to provider
177
+ router.post('/:id/models', (req, res) => {
178
+ try {
179
+ const provider = modelProviders.getById(req.params.id);
180
+ if (!provider) {
181
+ return res.status(404).json({ error: ERR_PROVIDER_NOT_FOUND });
182
+ }
183
+
184
+ const result = CreateProviderModelRequest.safeParse(req.body);
185
+ if (!result.success) {
186
+ return res.status(400).json({ error: result.error.issues[0].message });
187
+ }
188
+
189
+ const model = modelProviders.addModel(req.params.id, result.data);
190
+ res.status(201).json(model);
191
+ } catch (error) {
192
+ res.status(500).json({ error: error.message });
193
+ }
194
+ });
195
+
196
+ // PATCH /api/providers/:id/models/:modelId - Update model
197
+ router.patch('/:id/models/:modelId', (req, res) => {
198
+ try {
199
+ const provider = modelProviders.getById(req.params.id);
200
+ if (!provider) {
201
+ return res.status(404).json({ error: ERR_PROVIDER_NOT_FOUND });
202
+ }
203
+
204
+ const model = modelProviders.getModelById(req.params.modelId);
205
+ if (!model) {
206
+ return res.status(404).json({ error: 'Model not found' });
207
+ }
208
+
209
+ if (model.providerId !== req.params.id) {
210
+ return res.status(400).json({ error: 'Model does not belong to this provider' });
211
+ }
212
+
213
+ const result = CreateProviderModelRequest.partial().safeParse(req.body);
214
+ if (!result.success) {
215
+ return res.status(400).json({ error: result.error.issues[0].message });
216
+ }
217
+
218
+ const updated = modelProviders.updateModel(req.params.modelId, result.data);
219
+ res.json(updated);
220
+ } catch (error) {
221
+ res.status(500).json({ error: error.message });
222
+ }
223
+ });
224
+
225
+ // DELETE /api/providers/:id/models/:modelId - Remove model
226
+ router.delete('/:providerId/models/:modelId', (req, res) => {
227
+ try {
228
+ const provider = modelProviders.getById(req.params.providerId);
229
+ if (!provider) {
230
+ return res.status(404).json({ error: ERR_PROVIDER_NOT_FOUND });
231
+ }
232
+
233
+ const model = modelProviders.getModelById(req.params.modelId);
234
+ if (!model) {
235
+ return res.status(404).json({ error: 'Model not found' });
236
+ }
237
+
238
+ if (model.providerId !== req.params.providerId) {
239
+ return res.status(400).json({ error: 'Model does not belong to this provider' });
240
+ }
241
+
242
+ modelProviders.removeModel(req.params.modelId);
243
+ res.status(204).send();
244
+ } catch (error) {
245
+ res.status(500).json({ error: error.message });
246
+ }
247
+ });
248
+
249
+ export default router;