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,86 @@
1
+ import { Router } from 'express';
2
+ import { workLogs } from '../database.js';
3
+ import { broadcastToSession } from '../websocket.js';
4
+ import { WS_MESSAGE_TYPES } from '../../../shared/src/index.js';
5
+ import { textAccumulators, thinkingAccumulators } from '../services/streamEventHandler.js';
6
+ import { requireSession } from '../middleware/sessionLookup.js';
7
+
8
+ const router = Router();
9
+
10
+ // GET /api/sessions/:id/work-logs - Get work logs for session
11
+ router.get('/:id/work-logs', requireSession, (req, res) => {
12
+ // Return work logs grouped by message ID
13
+ const grouped = workLogs.getBySessionIdGrouped(req.params.id);
14
+ res.json(grouped);
15
+ });
16
+
17
+ // GET /api/sessions/:id/streaming-state - Get current streaming snapshot for a running session
18
+ // Returns recent pending work logs, accumulated partial text, and thinking
19
+ router.get('/:id/streaming-state', requireSession, (req, res) => {
20
+ const sessionId = req.params.id;
21
+ const pendingWorkLogs = workLogs.getRecentPendingBySessionId(sessionId);
22
+ const partialText = textAccumulators.get(sessionId) || '';
23
+ const thinking = thinkingAccumulators.get(sessionId) || null;
24
+
25
+ res.json({
26
+ workLogs: pendingWorkLogs,
27
+ partialText,
28
+ thinking,
29
+ });
30
+ });
31
+
32
+ // POST /api/sessions/:id/work-logs - Create work log (for testing)
33
+ router.post('/:id/work-logs', requireSession, (req, res) => {
34
+ const { type, content, toolName, messageId } = req.body;
35
+ if (!type || !content) {
36
+ return res.status(400).json({ error: 'Type and content are required' });
37
+ }
38
+
39
+ const log = workLogs.create(req.params.id, type, content, { messageId: messageId || null, toolName: toolName || null });
40
+
41
+ // Broadcast to session subscribers
42
+ broadcastToSession(req.params.id, WS_MESSAGE_TYPES.SESSION_WORK_LOG, {
43
+ sessionId: req.params.id,
44
+ log,
45
+ });
46
+
47
+ res.status(201).json(log);
48
+ });
49
+
50
+ // POST /api/sessions/:id/partial-text - Set partial text (for testing)
51
+ router.post('/:id/partial-text', requireSession, (req, res) => {
52
+ const { text } = req.body;
53
+ if (typeof text !== 'string') {
54
+ return res.status(400).json({ error: 'Text must be a string' });
55
+ }
56
+
57
+ textAccumulators.set(req.params.id, text);
58
+
59
+ // Broadcast to session subscribers
60
+ broadcastToSession(req.params.id, WS_MESSAGE_TYPES.SESSION_PARTIAL, {
61
+ sessionId: req.params.id,
62
+ text,
63
+ });
64
+
65
+ res.status(201).json({ text });
66
+ });
67
+
68
+ // POST /api/sessions/:id/thinking - Set thinking (for testing)
69
+ router.post('/:id/thinking', requireSession, (req, res) => {
70
+ const { thinking } = req.body;
71
+ if (typeof thinking !== 'string') {
72
+ return res.status(400).json({ error: 'Thinking must be a string' });
73
+ }
74
+
75
+ thinkingAccumulators.set(req.params.id, thinking);
76
+
77
+ // Broadcast to session subscribers
78
+ broadcastToSession(req.params.id, WS_MESSAGE_TYPES.SESSION_THINKING_PARTIAL, {
79
+ sessionId: req.params.id,
80
+ thinking,
81
+ });
82
+
83
+ res.status(201).json({ thinking });
84
+ });
85
+
86
+ export default router;
@@ -0,0 +1,269 @@
1
+ import { Router } from 'express';
2
+ import { readFileSync, existsSync } from 'fs';
3
+ import { extname, resolve, normalize } from 'path';
4
+ import { sessions, messages, projects, commandRuns, sessionSummaries } from '../database.js';
5
+ import { getChanges, getChangesBranch } from '../services/diffService.js';
6
+ import * as gitService from '../services/gitService.js';
7
+ import { requireSession, requireSessionAndProject } from '../middleware/sessionLookup.js';
8
+ import { commandRunner } from '../services/commandRunner.js';
9
+
10
+ // Import sub-routers
11
+ import notesRouter from './sessions-notes.js';
12
+ import conversationsRouter from './sessions-conversations.js';
13
+ import commandsRouter from './sessions-commands.js';
14
+ import patchRouter from './sessions-patch.js';
15
+ import archiveRouter from './sessions-archive.js';
16
+ import lifecycleRouter from './sessions-lifecycle.js';
17
+ import streamingRouter from './sessions-streaming.js';
18
+ import messagesRouter from './sessions-messages.js';
19
+ import draftRouter from './sessions-draft.js';
20
+
21
+ const router = Router();
22
+
23
+ // Mount sub-routers
24
+ router.use('/', notesRouter);
25
+ router.use('/', conversationsRouter);
26
+ router.use('/', commandsRouter);
27
+ router.use('/', patchRouter);
28
+ router.use('/', archiveRouter);
29
+ router.use('/', lifecycleRouter);
30
+ router.use('/', streamingRouter);
31
+ router.use('/', messagesRouter);
32
+ router.use('/', draftRouter);
33
+
34
+ // TTL cache for files-count endpoint (60 second TTL)
35
+ const filesCountCache = new Map();
36
+ const FILES_COUNT_CACHE_TTL = 60_000; // 60 seconds
37
+
38
+ /**
39
+ * Get cached files count or null if expired/missing
40
+ * @param {string} sessionId
41
+ * @returns {{ count: number } | null}
42
+ */
43
+ function getCachedFilesCount(sessionId) {
44
+ const cached = filesCountCache.get(sessionId);
45
+ if (cached && (Date.now() - cached.timestamp) < FILES_COUNT_CACHE_TTL) {
46
+ return { count: cached.count };
47
+ }
48
+ return null;
49
+ }
50
+
51
+ /**
52
+ * Set files count in cache
53
+ * @param {string} sessionId
54
+ * @param {number} count
55
+ */
56
+ function setCachedFilesCount(sessionId, count) {
57
+ filesCountCache.set(sessionId, { count, timestamp: Date.now() });
58
+ }
59
+
60
+ /**
61
+ * Invalidate files count cache for a session
62
+ * @param {string} sessionId
63
+ */
64
+ export function invalidateFilesCountCache(sessionId) {
65
+ filesCountCache.delete(sessionId);
66
+ }
67
+
68
+ // GET /api/sessions - Get all active/waiting sessions across all projects
69
+ router.get('/', (req, res) => {
70
+ const activeSessions = sessions.getActiveAndWaiting();
71
+ res.json(activeSessions);
72
+ });
73
+
74
+ // GET /api/sessions/scheduled - Get all scheduled sessions (optionally filtered by project)
75
+ router.get('/scheduled', (req, res) => {
76
+ const { projectId } = req.query;
77
+ const scheduledSessions = sessions.getScheduledSessions(projectId || null);
78
+ res.json(scheduledSessions);
79
+ });
80
+
81
+ // POST /api/sessions/summaries/batch - Get summaries for multiple sessions in one request
82
+ // Must be registered before /:id routes to avoid Express matching 'summaries' as an :id param
83
+ router.post('/summaries/batch', (req, res) => {
84
+ const { ids } = req.body;
85
+ if (!Array.isArray(ids) || ids.length === 0) {
86
+ return res.status(400).json({ error: 'ids array is required and must not be empty' });
87
+ }
88
+
89
+ const summaryList = sessionSummaries.getBySessionIds(ids);
90
+
91
+ // Build a map of sessionId -> summary (or null if not found)
92
+ const result = {};
93
+ for (const id of ids) {
94
+ result[id] = null;
95
+ }
96
+ for (const summary of summaryList) {
97
+ result[summary.sessionId] = summary;
98
+ }
99
+
100
+ res.json(result);
101
+ });
102
+
103
+ // GET /api/sessions/:id - Get session details
104
+ // Includes latestCommandRuns (merged from DB completed runs + in-memory running commands)
105
+ router.get('/:id', requireSession, (req, res) => {
106
+ // Add hasResponses flag to indicate if session has ever received assistant responses
107
+ // This is used by the frontend to determine if a session is a draft
108
+ const allMessages = messages.getBySessionId(req.params.id);
109
+ const hasResponses = allMessages.some(msg => msg.role === 'assistant');
110
+
111
+ // Get command run statuses (latest run per button for this session)
112
+ // Completed runs from DB
113
+ const dbRuns = commandRuns.getLatestRunsForSession(req.params.id);
114
+ // Currently running commands from memory - filter all runs for this session
115
+ const allRunning = commandRunner.getRunsBySession(req.params.id);
116
+ const runningRuns = allRunning.filter(run => run.status === 'running');
117
+
118
+ // Build map of buttonId -> run data
119
+ // Running commands take precedence over completed ones (more current state)
120
+ const runsByButton = {};
121
+
122
+ // First add DB runs (completed)
123
+ for (const run of dbRuns) {
124
+ runsByButton[run.buttonId] = {
125
+ buttonId: run.buttonId,
126
+ status: run.status,
127
+ exitCode: run.exitCode,
128
+ runId: run.id,
129
+ startedAt: run.startedAt,
130
+ completedAt: run.completedAt,
131
+ output: run.output || null,
132
+ };
133
+ }
134
+
135
+ // Then overlay running commands (takes precedence)
136
+ for (const run of runningRuns) {
137
+ runsByButton[run.buttonId] = {
138
+ buttonId: run.buttonId,
139
+ status: 'running',
140
+ exitCode: null,
141
+ runId: run.runId,
142
+ startedAt: run.startedAt,
143
+ };
144
+ }
145
+
146
+ const latestCommandRuns = Object.values(runsByButton);
147
+
148
+ res.json({ ...req.session_, hasResponses, latestCommandRuns });
149
+ });
150
+
151
+ // GET /api/sessions/:id/changes - Get git changes for session
152
+ // Query params:
153
+ // compareMode: 'local' (default) or 'branch' - determines what to compare against
154
+ // branch: branch ref to compare against (e.g., 'origin/main') - used when compareMode='branch'
155
+ router.get('/:id/changes', requireSessionAndProject, async (req, res) => {
156
+ try {
157
+ const { compareMode = 'local', branch } = req.query;
158
+
159
+ let changes;
160
+ if (compareMode === 'branch' && branch) {
161
+ // Get changes compared to a specific branch
162
+ changes = await getChangesBranch(req.workingDirectory, branch);
163
+ } else {
164
+ // Default: get local changes (staged, unstaged, untracked)
165
+ changes = await getChanges(req.workingDirectory);
166
+ }
167
+
168
+ res.json(changes);
169
+ } catch (error) {
170
+ res.status(500).json({ error: error.message });
171
+ }
172
+ });
173
+
174
+ // Image MIME types for the file endpoint
175
+ const IMAGE_MIME_TYPES = {
176
+ '.png': 'image/png',
177
+ '.jpg': 'image/jpeg',
178
+ '.jpeg': 'image/jpeg',
179
+ '.gif': 'image/gif',
180
+ '.webp': 'image/webp',
181
+ '.svg': 'image/svg+xml',
182
+ '.bmp': 'image/bmp',
183
+ '.ico': 'image/x-icon',
184
+ };
185
+
186
+ // GET /api/sessions/:id/file - Get a file from the session's working directory
187
+ // Used for displaying images in the diff viewer
188
+ router.get('/:id/file', requireSessionAndProject, (req, res) => {
189
+ const { path: filePath } = req.query;
190
+ if (!filePath) {
191
+ return res.status(400).json({ error: 'path query parameter is required' });
192
+ }
193
+
194
+ // Security: ensure the requested path is within the working directory
195
+ const fullPath = resolve(req.workingDirectory, filePath);
196
+ const normalizedDir = normalize(req.workingDirectory);
197
+ if (!fullPath.startsWith(normalizedDir)) {
198
+ return res.status(403).json({ error: 'Access denied: path outside working directory' });
199
+ }
200
+
201
+ if (!existsSync(fullPath)) {
202
+ return res.status(404).json({ error: 'File not found' });
203
+ }
204
+
205
+ try {
206
+ const ext = extname(fullPath).toLowerCase();
207
+ const mimeType = IMAGE_MIME_TYPES[ext];
208
+
209
+ if (!mimeType) {
210
+ return res.status(400).json({ error: 'Only image files are supported' });
211
+ }
212
+
213
+ const fileBuffer = readFileSync(fullPath);
214
+ const base64 = fileBuffer.toString('base64');
215
+
216
+ res.json({
217
+ data: base64,
218
+ mimeType,
219
+ filename: filePath,
220
+ });
221
+ } catch (error) {
222
+ res.status(500).json({ error: error.message });
223
+ }
224
+ });
225
+
226
+ // GET /api/sessions/:id/default-branch - Get the default branch for branch comparison
227
+ router.get('/:id/default-branch', requireSessionAndProject, async (req, res) => {
228
+ try {
229
+ const branch = await gitService.getOriginDefaultBranch(req.workingDirectory);
230
+ res.json({ branch });
231
+ } catch (error) {
232
+ res.status(500).json({ error: error.message });
233
+ }
234
+ });
235
+
236
+ // GET /api/sessions/:id/files-count - Get count of modified files
237
+ // Uses a 60-second TTL cache to avoid expensive git operations on every request
238
+ router.get('/:id/files-count', requireSession, async (req, res) => {
239
+ // Check cache first
240
+ const cached = getCachedFilesCount(req.params.id);
241
+ if (cached) {
242
+ return res.json(cached);
243
+ }
244
+
245
+ const project = projects.getById(req.session_.projectId);
246
+ if (!project) {
247
+ return res.status(404).json({ error: 'Project not found' });
248
+ }
249
+
250
+ // Use gitWorktree if set, otherwise use the project's working directory
251
+ const directory = req.session_.gitWorktree || project.workingDirectory;
252
+
253
+ try {
254
+ // Get the default branch to compare against
255
+ const defaultBranch = await gitService.getOriginDefaultBranch(directory);
256
+ const count = await gitService.getModifiedFilesCount(directory, defaultBranch);
257
+
258
+ // Cache the result
259
+ setCachedFilesCount(req.params.id, count);
260
+
261
+ res.json({ count });
262
+ } catch (error) {
263
+ res.status(500).json({ error: error.message, count: 0 });
264
+ }
265
+ });
266
+
267
+ // PATCH /:id and PATCH /:id/pending-prompt are handled by sessions-patch.js sub-router
268
+
269
+ export default router;
@@ -0,0 +1,194 @@
1
+ import { Router } from 'express';
2
+ import { settings } from '../db/index.js';
3
+ import { DEFAULT_SESSION_TITLE_PROMPT } from '../services/summaryService.js';
4
+
5
+ const router = Router();
6
+
7
+ /**
8
+ * GET /api/settings/token-weights
9
+ * Get current token cost weights
10
+ */
11
+ router.get('/token-weights', (req, res) => {
12
+ try {
13
+ const weights = settings.getTokenCostWeights();
14
+ res.json(weights);
15
+ } catch (error) {
16
+ console.error('Error getting token weights:', error);
17
+ res.status(500).json({ error: 'Failed to get token weights' });
18
+ }
19
+ });
20
+
21
+ /**
22
+ * PUT /api/settings/token-weights
23
+ * Update token cost weights
24
+ */
25
+ router.put('/token-weights', (req, res) => {
26
+ try {
27
+ const { input, output, cacheRead, cacheCreation } = req.body;
28
+
29
+ // Validate required fields are present and are numbers
30
+ if (typeof input !== 'number' || typeof output !== 'number' ||
31
+ typeof cacheRead !== 'number' || typeof cacheCreation !== 'number') {
32
+ return res.status(400).json({
33
+ error: 'All weights (input, output, cacheRead, cacheCreation) must be numbers'
34
+ });
35
+ }
36
+
37
+ // Validate weights are positive
38
+ if (input < 0 || output < 0 || cacheRead < 0 || cacheCreation < 0) {
39
+ return res.status(400).json({
40
+ error: 'All weights must be non-negative numbers'
41
+ });
42
+ }
43
+
44
+ const updatedWeights = settings.setTokenCostWeights({
45
+ input,
46
+ output,
47
+ cacheRead,
48
+ cacheCreation,
49
+ });
50
+
51
+ res.json(updatedWeights);
52
+ } catch (error) {
53
+ console.error('Error updating token weights:', error);
54
+ res.status(500).json({ error: 'Failed to update token weights' });
55
+ }
56
+ });
57
+
58
+ /**
59
+ * DELETE /api/settings/token-weights
60
+ * Reset token cost weights to defaults
61
+ */
62
+ router.delete('/token-weights', (req, res) => {
63
+ try {
64
+ const defaults = settings.resetTokenCostWeights();
65
+ res.json(defaults);
66
+ } catch (error) {
67
+ console.error('Error resetting token weights:', error);
68
+ res.status(500).json({ error: 'Failed to reset token weights' });
69
+ }
70
+ });
71
+
72
+ /**
73
+ * GET /api/settings/summary
74
+ * Get summary settings
75
+ */
76
+ router.get('/summary', (req, res) => {
77
+ try {
78
+ const summarySettings = settings.getSummarySettings();
79
+ // Include the default prompt for UI display/editing
80
+ res.json({
81
+ ...summarySettings,
82
+ defaultSessionTitlePrompt: DEFAULT_SESSION_TITLE_PROMPT,
83
+ });
84
+ } catch (error) {
85
+ console.error('Error getting summary settings:', error);
86
+ res.status(500).json({ error: 'Failed to get summary settings' });
87
+ }
88
+ });
89
+
90
+ /**
91
+ * PUT /api/settings/summary
92
+ * Update summary settings
93
+ */
94
+ router.put('/summary', (req, res) => {
95
+ try {
96
+ const { disableSessionSummaries, sessionTitlePrompt } = req.body;
97
+
98
+ // Validate that all required fields are present
99
+ if (typeof disableSessionSummaries !== 'boolean' ||
100
+ typeof sessionTitlePrompt !== 'string') {
101
+ return res.status(400).json({
102
+ error: 'Invalid summary settings. disableSessionSummaries must be a boolean, sessionTitlePrompt must be a string'
103
+ });
104
+ }
105
+
106
+ const updatedSettings = settings.setSummarySettings({
107
+ disableSessionSummaries,
108
+ sessionTitlePrompt,
109
+ });
110
+
111
+ // Include the default prompt for UI display/editing
112
+ res.json({
113
+ ...updatedSettings,
114
+ defaultSessionTitlePrompt: DEFAULT_SESSION_TITLE_PROMPT,
115
+ });
116
+ } catch (error) {
117
+ console.error('Error updating summary settings:', error);
118
+ res.status(500).json({ error: 'Failed to update summary settings' });
119
+ }
120
+ });
121
+
122
+ /**
123
+ * DELETE /api/settings/summary
124
+ * Reset summary settings to defaults
125
+ */
126
+ router.delete('/summary', (req, res) => {
127
+ try {
128
+ const defaults = settings.resetSummarySettings();
129
+ // Include the default prompt for UI display/editing
130
+ res.json({
131
+ ...defaults,
132
+ defaultSessionTitlePrompt: DEFAULT_SESSION_TITLE_PROMPT,
133
+ });
134
+ } catch (error) {
135
+ console.error('Error resetting summary settings:', error);
136
+ res.status(500).json({ error: 'Failed to reset summary settings' });
137
+ }
138
+ });
139
+
140
+ /**
141
+ * GET /api/settings/general
142
+ * Get general settings (includes privacy settings)
143
+ */
144
+ router.get('/general', (req, res) => {
145
+ try {
146
+ const generalSettings = settings.getGeneralSettings();
147
+ res.json(generalSettings);
148
+ } catch (error) {
149
+ console.error('Error getting general settings:', error);
150
+ res.status(500).json({ error: 'Failed to get general settings' });
151
+ }
152
+ });
153
+
154
+ /**
155
+ * PUT /api/settings/general
156
+ * Update general settings
157
+ */
158
+ router.put('/general', (req, res) => {
159
+ try {
160
+ const { disableAnalytics } = req.body;
161
+
162
+ // Validate that disableAnalytics is a boolean
163
+ if (typeof disableAnalytics !== 'boolean') {
164
+ return res.status(400).json({
165
+ error: 'Invalid general settings. disableAnalytics must be a boolean'
166
+ });
167
+ }
168
+
169
+ const updatedSettings = settings.setGeneralSettings({
170
+ disableAnalytics,
171
+ });
172
+
173
+ res.json(updatedSettings);
174
+ } catch (error) {
175
+ console.error('Error updating general settings:', error);
176
+ res.status(500).json({ error: 'Failed to update general settings' });
177
+ }
178
+ });
179
+
180
+ /**
181
+ * DELETE /api/settings/general
182
+ * Reset general settings to defaults
183
+ */
184
+ router.delete('/general', (req, res) => {
185
+ try {
186
+ const defaults = settings.resetGeneralSettings();
187
+ res.json(defaults);
188
+ } catch (error) {
189
+ console.error('Error resetting general settings:', error);
190
+ res.status(500).json({ error: 'Failed to reset general settings' });
191
+ }
192
+ });
193
+
194
+ export default router;
@@ -0,0 +1,63 @@
1
+ import { Router } from 'express';
2
+ import { sessionTemplates } from '../database.js';
3
+ import { CreateSessionTemplateRequest, UpdateSessionTemplateRequest } from '../../../shared/src/contracts/templates.js';
4
+
5
+ const router = Router();
6
+
7
+ // GET /api/templates - List all global templates
8
+ router.get('/', (_req, res) => {
9
+ const templates = sessionTemplates.getGlobal();
10
+ res.json(templates);
11
+ });
12
+
13
+ // POST /api/templates - Create global template
14
+ router.post('/', (req, res) => {
15
+ const result = CreateSessionTemplateRequest.safeParse(req.body);
16
+ if (!result.success) {
17
+ return res.status(400).json({ error: result.error.issues[0].message });
18
+ }
19
+
20
+ const template = sessionTemplates.create({
21
+ projectId: null, // Global template
22
+ ...result.data,
23
+ });
24
+ res.status(201).json(template);
25
+ });
26
+
27
+ // GET /api/templates/:id - Get template by ID
28
+ router.get('/:id', (req, res) => {
29
+ const template = sessionTemplates.getById(req.params.id);
30
+ if (!template) {
31
+ return res.status(404).json({ error: 'Template not found' });
32
+ }
33
+ res.json(template);
34
+ });
35
+
36
+ // PATCH /api/templates/:id - Update template
37
+ router.patch('/:id', (req, res) => {
38
+ const template = sessionTemplates.getById(req.params.id);
39
+ if (!template) {
40
+ return res.status(404).json({ error: 'Template not found' });
41
+ }
42
+
43
+ const result = UpdateSessionTemplateRequest.safeParse(req.body);
44
+ if (!result.success) {
45
+ return res.status(400).json({ error: result.error.issues[0].message });
46
+ }
47
+
48
+ const updated = sessionTemplates.update(req.params.id, result.data);
49
+ res.json(updated);
50
+ });
51
+
52
+ // DELETE /api/templates/:id - Delete template
53
+ router.delete('/:id', (req, res) => {
54
+ const template = sessionTemplates.getById(req.params.id);
55
+ if (!template) {
56
+ return res.status(404).json({ error: 'Template not found' });
57
+ }
58
+
59
+ sessionTemplates.delete(req.params.id);
60
+ res.status(204).send();
61
+ });
62
+
63
+ export default router;
@@ -0,0 +1,51 @@
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import { fileURLToPath } from 'url';
4
+ import { dirname, join } from 'path';
5
+ import apiRouter from './api/index.js';
6
+ import { MAX_JSON_SIZE } from '../../shared/src/index.js';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = dirname(__filename);
10
+
11
+ /**
12
+ * Create Express application
13
+ * @param {Object} options
14
+ * @param {boolean} options.production - Enable production mode
15
+ * @returns {express.Application}
16
+ */
17
+ export function createApp(options = {}) {
18
+ const app = express();
19
+
20
+ // CORS (allow all in dev)
21
+ app.use(cors());
22
+
23
+ // Body parsing
24
+ app.use(express.json({ limit: MAX_JSON_SIZE }));
25
+ app.use(express.urlencoded({ extended: true, limit: MAX_JSON_SIZE }));
26
+
27
+ // Multipart form data parsing is handled per-route with specific middleware
28
+ // to avoid conflicts between upload.single() and upload.array() across different endpoints
29
+
30
+ // API routes
31
+ app.use('/api', apiRouter);
32
+
33
+ // Static files and SPA fallback (production only - in dev, Vite serves frontend)
34
+ if (options.production) {
35
+ const staticPath = join(__dirname, '../../web/dist');
36
+ app.use(express.static(staticPath));
37
+
38
+ // SPA fallback for client-side routing
39
+ app.get('/*', (_req, res) => {
40
+ res.sendFile(join(staticPath, 'index.html'));
41
+ });
42
+ }
43
+
44
+ // Error handler
45
+ app.use((err, _req, res, _next) => {
46
+ console.error('Server error:', err);
47
+ res.status(500).json({ error: err.message || 'Internal server error' });
48
+ });
49
+
50
+ return app;
51
+ }