@myrialabs/clopen 0.0.1

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 (456) hide show
  1. package/.env.example +6 -0
  2. package/.github/workflows/release.yml +60 -0
  3. package/.github/workflows/test.yml +40 -0
  4. package/CONTRIBUTING.md +499 -0
  5. package/LICENSE +21 -0
  6. package/README.md +209 -0
  7. package/backend/index.ts +156 -0
  8. package/backend/lib/chat/helpers.ts +42 -0
  9. package/backend/lib/chat/index.ts +2 -0
  10. package/backend/lib/chat/stream-manager.ts +1126 -0
  11. package/backend/lib/database/README.md +77 -0
  12. package/backend/lib/database/index.ts +119 -0
  13. package/backend/lib/database/migrations/001_create_projects_table.ts +31 -0
  14. package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +33 -0
  15. package/backend/lib/database/migrations/003_create_messages_table.ts +32 -0
  16. package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -0
  17. package/backend/lib/database/migrations/005_create_settings_table.ts +24 -0
  18. package/backend/lib/database/migrations/006_add_user_to_messages.ts +58 -0
  19. package/backend/lib/database/migrations/007_create_stream_states_table.ts +41 -0
  20. package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +62 -0
  21. package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -0
  22. package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -0
  23. package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -0
  24. package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -0
  25. package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -0
  26. package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -0
  27. package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -0
  28. package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -0
  29. package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -0
  30. package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -0
  31. package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -0
  32. package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -0
  33. package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -0
  34. package/backend/lib/database/migrations/index.ts +154 -0
  35. package/backend/lib/database/queries/checkpoint-queries.ts +87 -0
  36. package/backend/lib/database/queries/engine-queries.ts +75 -0
  37. package/backend/lib/database/queries/index.ts +9 -0
  38. package/backend/lib/database/queries/message-queries.ts +472 -0
  39. package/backend/lib/database/queries/project-queries.ts +117 -0
  40. package/backend/lib/database/queries/session-queries.ts +271 -0
  41. package/backend/lib/database/queries/settings-queries.ts +34 -0
  42. package/backend/lib/database/queries/snapshot-queries.ts +326 -0
  43. package/backend/lib/database/queries/utils-queries.ts +59 -0
  44. package/backend/lib/database/seeders/index.ts +13 -0
  45. package/backend/lib/database/seeders/settings_seeder.ts +84 -0
  46. package/backend/lib/database/utils/connection.ts +174 -0
  47. package/backend/lib/database/utils/index.ts +4 -0
  48. package/backend/lib/database/utils/migration-runner.ts +118 -0
  49. package/backend/lib/database/utils/seeder-runner.ts +121 -0
  50. package/backend/lib/engine/adapters/claude/environment.ts +164 -0
  51. package/backend/lib/engine/adapters/claude/error-handler.ts +60 -0
  52. package/backend/lib/engine/adapters/claude/index.ts +1 -0
  53. package/backend/lib/engine/adapters/claude/path-utils.ts +38 -0
  54. package/backend/lib/engine/adapters/claude/stream.ts +177 -0
  55. package/backend/lib/engine/adapters/opencode/index.ts +2 -0
  56. package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -0
  57. package/backend/lib/engine/adapters/opencode/server.ts +104 -0
  58. package/backend/lib/engine/adapters/opencode/stream.ts +755 -0
  59. package/backend/lib/engine/index.ts +196 -0
  60. package/backend/lib/engine/types.ts +58 -0
  61. package/backend/lib/files/file-operations.ts +478 -0
  62. package/backend/lib/files/file-reading.ts +308 -0
  63. package/backend/lib/files/file-watcher.ts +383 -0
  64. package/backend/lib/files/path-browsing.ts +382 -0
  65. package/backend/lib/git/git-executor.ts +88 -0
  66. package/backend/lib/git/git-parser.ts +411 -0
  67. package/backend/lib/git/git-service.ts +505 -0
  68. package/backend/lib/mcp/README.md +1144 -0
  69. package/backend/lib/mcp/config.ts +316 -0
  70. package/backend/lib/mcp/index.ts +35 -0
  71. package/backend/lib/mcp/project-context.ts +236 -0
  72. package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -0
  73. package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -0
  74. package/backend/lib/mcp/servers/browser-automation/index.ts +791 -0
  75. package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -0
  76. package/backend/lib/mcp/servers/helper.ts +143 -0
  77. package/backend/lib/mcp/servers/index.ts +45 -0
  78. package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -0
  79. package/backend/lib/mcp/servers/weather/index.ts +31 -0
  80. package/backend/lib/mcp/stdio-server.ts +103 -0
  81. package/backend/lib/mcp/types.ts +65 -0
  82. package/backend/lib/preview/browser/browser-audio-capture.ts +86 -0
  83. package/backend/lib/preview/browser/browser-console-manager.ts +263 -0
  84. package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -0
  85. package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -0
  86. package/backend/lib/preview/browser/browser-mcp-control.ts +415 -0
  87. package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -0
  88. package/backend/lib/preview/browser/browser-navigation-tracker.ts +104 -0
  89. package/backend/lib/preview/browser/browser-pool.ts +357 -0
  90. package/backend/lib/preview/browser/browser-preview-service.ts +882 -0
  91. package/backend/lib/preview/browser/browser-tab-manager.ts +935 -0
  92. package/backend/lib/preview/browser/browser-video-capture.ts +695 -0
  93. package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -0
  94. package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -0
  95. package/backend/lib/preview/browser/scripts/video-stream.ts +438 -0
  96. package/backend/lib/preview/browser/types.ts +359 -0
  97. package/backend/lib/preview/index.ts +24 -0
  98. package/backend/lib/project/index.ts +2 -0
  99. package/backend/lib/project/status-manager.ts +182 -0
  100. package/backend/lib/shared/index.ts +2 -0
  101. package/backend/lib/shared/port-utils.ts +25 -0
  102. package/backend/lib/shared/process-manager.ts +281 -0
  103. package/backend/lib/snapshot/blob-store.ts +227 -0
  104. package/backend/lib/snapshot/gitignore.ts +307 -0
  105. package/backend/lib/snapshot/helpers.ts +397 -0
  106. package/backend/lib/snapshot/snapshot-service.ts +483 -0
  107. package/backend/lib/terminal/helpers.ts +14 -0
  108. package/backend/lib/terminal/index.ts +8 -0
  109. package/backend/lib/terminal/pty-manager.ts +4 -0
  110. package/backend/lib/terminal/pty-session-manager.ts +387 -0
  111. package/backend/lib/terminal/shell-utils.ts +313 -0
  112. package/backend/lib/terminal/stream-manager.ts +293 -0
  113. package/backend/lib/tunnel/global-tunnel-manager.ts +243 -0
  114. package/backend/lib/tunnel/project-tunnel-manager.ts +311 -0
  115. package/backend/lib/user/helpers.ts +87 -0
  116. package/backend/lib/utils/ws.ts +944 -0
  117. package/backend/lib/vite-dev.ts +353 -0
  118. package/backend/middleware/cors.ts +15 -0
  119. package/backend/middleware/error-handler.ts +49 -0
  120. package/backend/middleware/logger.ts +9 -0
  121. package/backend/types/api.ts +24 -0
  122. package/backend/ws/README.md +1505 -0
  123. package/backend/ws/chat/background.ts +198 -0
  124. package/backend/ws/chat/index.ts +21 -0
  125. package/backend/ws/chat/stream.ts +707 -0
  126. package/backend/ws/engine/claude/accounts.ts +401 -0
  127. package/backend/ws/engine/claude/index.ts +13 -0
  128. package/backend/ws/engine/claude/status.ts +43 -0
  129. package/backend/ws/engine/index.ts +14 -0
  130. package/backend/ws/engine/opencode/index.ts +11 -0
  131. package/backend/ws/engine/opencode/status.ts +30 -0
  132. package/backend/ws/engine/utils.ts +36 -0
  133. package/backend/ws/files/index.ts +30 -0
  134. package/backend/ws/files/read.ts +189 -0
  135. package/backend/ws/files/search.ts +453 -0
  136. package/backend/ws/files/watch.ts +124 -0
  137. package/backend/ws/files/write.ts +143 -0
  138. package/backend/ws/git/branch.ts +106 -0
  139. package/backend/ws/git/commit.ts +39 -0
  140. package/backend/ws/git/conflict.ts +68 -0
  141. package/backend/ws/git/diff.ts +69 -0
  142. package/backend/ws/git/index.ts +24 -0
  143. package/backend/ws/git/log.ts +41 -0
  144. package/backend/ws/git/remote.ts +214 -0
  145. package/backend/ws/git/staging.ts +84 -0
  146. package/backend/ws/git/status.ts +90 -0
  147. package/backend/ws/index.ts +69 -0
  148. package/backend/ws/mcp/index.ts +61 -0
  149. package/backend/ws/messages/crud.ts +74 -0
  150. package/backend/ws/messages/index.ts +14 -0
  151. package/backend/ws/preview/browser/cleanup.ts +129 -0
  152. package/backend/ws/preview/browser/console.ts +114 -0
  153. package/backend/ws/preview/browser/interact.ts +513 -0
  154. package/backend/ws/preview/browser/mcp.ts +129 -0
  155. package/backend/ws/preview/browser/native-ui.ts +235 -0
  156. package/backend/ws/preview/browser/stats.ts +55 -0
  157. package/backend/ws/preview/browser/tab-info.ts +126 -0
  158. package/backend/ws/preview/browser/tab.ts +166 -0
  159. package/backend/ws/preview/browser/webcodecs.ts +293 -0
  160. package/backend/ws/preview/index.ts +146 -0
  161. package/backend/ws/projects/crud.ts +113 -0
  162. package/backend/ws/projects/index.ts +25 -0
  163. package/backend/ws/projects/presence.ts +46 -0
  164. package/backend/ws/projects/status.ts +116 -0
  165. package/backend/ws/sessions/crud.ts +327 -0
  166. package/backend/ws/sessions/index.ts +33 -0
  167. package/backend/ws/settings/crud.ts +112 -0
  168. package/backend/ws/settings/index.ts +14 -0
  169. package/backend/ws/snapshot/index.ts +17 -0
  170. package/backend/ws/snapshot/restore.ts +173 -0
  171. package/backend/ws/snapshot/timeline.ts +141 -0
  172. package/backend/ws/system/index.ts +14 -0
  173. package/backend/ws/system/operations.ts +49 -0
  174. package/backend/ws/terminal/index.ts +40 -0
  175. package/backend/ws/terminal/persistence.ts +153 -0
  176. package/backend/ws/terminal/session.ts +382 -0
  177. package/backend/ws/terminal/stream.ts +79 -0
  178. package/backend/ws/tunnel/index.ts +14 -0
  179. package/backend/ws/tunnel/operations.ts +91 -0
  180. package/backend/ws/types.ts +20 -0
  181. package/backend/ws/user/crud.ts +156 -0
  182. package/backend/ws/user/index.ts +14 -0
  183. package/bin/clopen.ts +307 -0
  184. package/bun.lock +1352 -0
  185. package/frontend/App.svelte +34 -0
  186. package/frontend/app.css +313 -0
  187. package/frontend/lib/app-environment.ts +10 -0
  188. package/frontend/lib/components/chat/ChatInterface.svelte +407 -0
  189. package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +57 -0
  190. package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +224 -0
  191. package/frontend/lib/components/chat/formatters/TextMessage.svelte +395 -0
  192. package/frontend/lib/components/chat/formatters/Tools.svelte +70 -0
  193. package/frontend/lib/components/chat/formatters/index.ts +3 -0
  194. package/frontend/lib/components/chat/input/ChatInput.svelte +421 -0
  195. package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -0
  196. package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -0
  197. package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -0
  198. package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -0
  199. package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -0
  200. package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -0
  201. package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -0
  202. package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -0
  203. package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -0
  204. package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -0
  205. package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -0
  206. package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -0
  207. package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -0
  208. package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -0
  209. package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -0
  210. package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -0
  211. package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -0
  212. package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -0
  213. package/frontend/lib/components/chat/shared/index.ts +2 -0
  214. package/frontend/lib/components/chat/shared/utils.ts +116 -0
  215. package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -0
  216. package/frontend/lib/components/chat/tools/BashTool.svelte +46 -0
  217. package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -0
  218. package/frontend/lib/components/chat/tools/EditTool.svelte +48 -0
  219. package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +32 -0
  220. package/frontend/lib/components/chat/tools/GlobTool.svelte +51 -0
  221. package/frontend/lib/components/chat/tools/GrepTool.svelte +90 -0
  222. package/frontend/lib/components/chat/tools/KillShellTool.svelte +26 -0
  223. package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +31 -0
  224. package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +38 -0
  225. package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +34 -0
  226. package/frontend/lib/components/chat/tools/ReadTool.svelte +41 -0
  227. package/frontend/lib/components/chat/tools/TaskTool.svelte +64 -0
  228. package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +75 -0
  229. package/frontend/lib/components/chat/tools/WebFetchTool.svelte +35 -0
  230. package/frontend/lib/components/chat/tools/WebSearchTool.svelte +84 -0
  231. package/frontend/lib/components/chat/tools/WriteTool.svelte +33 -0
  232. package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +79 -0
  233. package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +408 -0
  234. package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -0
  235. package/frontend/lib/components/chat/tools/components/InfoLine.svelte +19 -0
  236. package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +27 -0
  237. package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +54 -0
  238. package/frontend/lib/components/chat/tools/components/index.ts +7 -0
  239. package/frontend/lib/components/chat/tools/index.ts +26 -0
  240. package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +249 -0
  241. package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -0
  242. package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -0
  243. package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -0
  244. package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +87 -0
  245. package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -0
  246. package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -0
  247. package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -0
  248. package/frontend/lib/components/checkpoint/timeline/config.ts +44 -0
  249. package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -0
  250. package/frontend/lib/components/checkpoint/timeline/types.ts +65 -0
  251. package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -0
  252. package/frontend/lib/components/common/Alert.svelte +139 -0
  253. package/frontend/lib/components/common/AvatarBubble.svelte +56 -0
  254. package/frontend/lib/components/common/Button.svelte +71 -0
  255. package/frontend/lib/components/common/Card.svelte +102 -0
  256. package/frontend/lib/components/common/Checkbox.svelte +48 -0
  257. package/frontend/lib/components/common/Dialog.svelte +249 -0
  258. package/frontend/lib/components/common/FolderBrowser.svelte +843 -0
  259. package/frontend/lib/components/common/Icon.svelte +58 -0
  260. package/frontend/lib/components/common/Input.svelte +72 -0
  261. package/frontend/lib/components/common/Lightbox.svelte +233 -0
  262. package/frontend/lib/components/common/LoadingScreen.svelte +52 -0
  263. package/frontend/lib/components/common/LoadingSpinner.svelte +48 -0
  264. package/frontend/lib/components/common/Modal.svelte +177 -0
  265. package/frontend/lib/components/common/ModalProvider.svelte +28 -0
  266. package/frontend/lib/components/common/ModelSelector.svelte +110 -0
  267. package/frontend/lib/components/common/MonacoEditor.svelte +569 -0
  268. package/frontend/lib/components/common/NotificationToast.svelte +113 -0
  269. package/frontend/lib/components/common/PageTemplate.svelte +76 -0
  270. package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -0
  271. package/frontend/lib/components/common/Select.svelte +98 -0
  272. package/frontend/lib/components/common/Textarea.svelte +80 -0
  273. package/frontend/lib/components/common/ThemeToggle.svelte +44 -0
  274. package/frontend/lib/components/common/lucide-icons.ts +1642 -0
  275. package/frontend/lib/components/common/material-icons.ts +1082 -0
  276. package/frontend/lib/components/common/xterm/XTerm.svelte +796 -0
  277. package/frontend/lib/components/common/xterm/index.ts +16 -0
  278. package/frontend/lib/components/common/xterm/terminal-config.ts +68 -0
  279. package/frontend/lib/components/common/xterm/types.ts +31 -0
  280. package/frontend/lib/components/common/xterm/xterm-service.ts +353 -0
  281. package/frontend/lib/components/files/FileNode.svelte +384 -0
  282. package/frontend/lib/components/files/FileTree.svelte +681 -0
  283. package/frontend/lib/components/files/FileViewer.svelte +728 -0
  284. package/frontend/lib/components/files/SearchResults.svelte +303 -0
  285. package/frontend/lib/components/git/BranchManager.svelte +458 -0
  286. package/frontend/lib/components/git/ChangesSection.svelte +107 -0
  287. package/frontend/lib/components/git/CommitForm.svelte +76 -0
  288. package/frontend/lib/components/git/ConflictResolver.svelte +158 -0
  289. package/frontend/lib/components/git/DiffViewer.svelte +364 -0
  290. package/frontend/lib/components/git/FileChangeItem.svelte +97 -0
  291. package/frontend/lib/components/git/GitButton.svelte +33 -0
  292. package/frontend/lib/components/git/GitLog.svelte +361 -0
  293. package/frontend/lib/components/git/GitModal.svelte +80 -0
  294. package/frontend/lib/components/history/HistoryModal.svelte +563 -0
  295. package/frontend/lib/components/history/HistoryView.svelte +615 -0
  296. package/frontend/lib/components/index.ts +34 -0
  297. package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -0
  298. package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -0
  299. package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +757 -0
  300. package/frontend/lib/components/preview/browser/components/Container.svelte +450 -0
  301. package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -0
  302. package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -0
  303. package/frontend/lib/components/preview/browser/components/Toolbar.svelte +339 -0
  304. package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +36 -0
  305. package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -0
  306. package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -0
  307. package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -0
  308. package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -0
  309. package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -0
  310. package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -0
  311. package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -0
  312. package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -0
  313. package/frontend/lib/components/preview/index.ts +2 -0
  314. package/frontend/lib/components/settings/SettingsModal.svelte +235 -0
  315. package/frontend/lib/components/settings/SettingsView.svelte +36 -0
  316. package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -0
  317. package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -0
  318. package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -0
  319. package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -0
  320. package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -0
  321. package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -0
  322. package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -0
  323. package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -0
  324. package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -0
  325. package/frontend/lib/components/settings/user/UserSettings.svelte +197 -0
  326. package/frontend/lib/components/terminal/Terminal.svelte +368 -0
  327. package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -0
  328. package/frontend/lib/components/terminal/TerminalView.svelte +55 -0
  329. package/frontend/lib/components/tunnel/TunnelActive.svelte +142 -0
  330. package/frontend/lib/components/tunnel/TunnelButton.svelte +54 -0
  331. package/frontend/lib/components/tunnel/TunnelInactive.svelte +284 -0
  332. package/frontend/lib/components/tunnel/TunnelModal.svelte +47 -0
  333. package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -0
  334. package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -0
  335. package/frontend/lib/components/workspace/MobileNavigator.svelte +403 -0
  336. package/frontend/lib/components/workspace/PanelContainer.svelte +100 -0
  337. package/frontend/lib/components/workspace/PanelHeader.svelte +505 -0
  338. package/frontend/lib/components/workspace/ViewMenu.svelte +162 -0
  339. package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -0
  340. package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -0
  341. package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -0
  342. package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -0
  343. package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +85 -0
  344. package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -0
  345. package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -0
  346. package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -0
  347. package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -0
  348. package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -0
  349. package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -0
  350. package/frontend/lib/constants/preview.ts +45 -0
  351. package/frontend/lib/services/chat/chat.service.ts +704 -0
  352. package/frontend/lib/services/chat/index.ts +7 -0
  353. package/frontend/lib/services/notification/global-stream-monitor.ts +86 -0
  354. package/frontend/lib/services/notification/index.ts +8 -0
  355. package/frontend/lib/services/notification/push.service.ts +144 -0
  356. package/frontend/lib/services/notification/sound.service.ts +127 -0
  357. package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -0
  358. package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -0
  359. package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -0
  360. package/frontend/lib/services/preview/index.ts +23 -0
  361. package/frontend/lib/services/project/index.ts +8 -0
  362. package/frontend/lib/services/project/status.service.ts +159 -0
  363. package/frontend/lib/services/snapshot/snapshot.service.ts +47 -0
  364. package/frontend/lib/services/terminal/background/index.ts +130 -0
  365. package/frontend/lib/services/terminal/background/session-restore.ts +274 -0
  366. package/frontend/lib/services/terminal/background/stream-manager.ts +286 -0
  367. package/frontend/lib/services/terminal/index.ts +14 -0
  368. package/frontend/lib/services/terminal/persistence.service.ts +260 -0
  369. package/frontend/lib/services/terminal/project.service.ts +953 -0
  370. package/frontend/lib/services/terminal/session.service.ts +364 -0
  371. package/frontend/lib/services/terminal/terminal.service.ts +369 -0
  372. package/frontend/lib/stores/core/app.svelte.ts +117 -0
  373. package/frontend/lib/stores/core/files.svelte.ts +73 -0
  374. package/frontend/lib/stores/core/presence.svelte.ts +48 -0
  375. package/frontend/lib/stores/core/projects.svelte.ts +317 -0
  376. package/frontend/lib/stores/core/sessions.svelte.ts +383 -0
  377. package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -0
  378. package/frontend/lib/stores/features/models.svelte.ts +89 -0
  379. package/frontend/lib/stores/features/settings.svelte.ts +87 -0
  380. package/frontend/lib/stores/features/terminal.svelte.ts +701 -0
  381. package/frontend/lib/stores/features/tunnel.svelte.ts +161 -0
  382. package/frontend/lib/stores/features/user.svelte.ts +96 -0
  383. package/frontend/lib/stores/ui/chat-input.svelte.ts +57 -0
  384. package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -0
  385. package/frontend/lib/stores/ui/dialog.svelte.ts +59 -0
  386. package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -0
  387. package/frontend/lib/stores/ui/notification.svelte.ts +166 -0
  388. package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -0
  389. package/frontend/lib/stores/ui/theme.svelte.ts +179 -0
  390. package/frontend/lib/stores/ui/workspace.svelte.ts +754 -0
  391. package/frontend/lib/types/native-ui.ts +73 -0
  392. package/frontend/lib/utils/chat/date-separator.ts +39 -0
  393. package/frontend/lib/utils/chat/message-grouper.ts +219 -0
  394. package/frontend/lib/utils/chat/message-processor.ts +135 -0
  395. package/frontend/lib/utils/chat/tool-handler.ts +161 -0
  396. package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -0
  397. package/frontend/lib/utils/click-outside.ts +20 -0
  398. package/frontend/lib/utils/context-manager.ts +257 -0
  399. package/frontend/lib/utils/file-icon-mappings.ts +769 -0
  400. package/frontend/lib/utils/folder-icon-mappings.ts +1030 -0
  401. package/frontend/lib/utils/git-status.ts +68 -0
  402. package/frontend/lib/utils/platform.ts +113 -0
  403. package/frontend/lib/utils/port-check.ts +65 -0
  404. package/frontend/lib/utils/terminalFormatter.ts +207 -0
  405. package/frontend/lib/utils/theme.ts +6 -0
  406. package/frontend/lib/utils/tree-visualizer.ts +320 -0
  407. package/frontend/lib/utils/ws.ts +44 -0
  408. package/frontend/main.ts +13 -0
  409. package/index.html +70 -0
  410. package/package.json +111 -0
  411. package/scripts/generate-icons.ts +87 -0
  412. package/scripts/pre-publish-check.sh +142 -0
  413. package/scripts/setup-hooks.sh +134 -0
  414. package/scripts/validate-branch-name.sh +47 -0
  415. package/scripts/validate-commit-msg.sh +42 -0
  416. package/shared/constants/engines.ts +134 -0
  417. package/shared/types/database/connection.ts +16 -0
  418. package/shared/types/database/index.ts +6 -0
  419. package/shared/types/database/schema.ts +141 -0
  420. package/shared/types/engine/index.ts +45 -0
  421. package/shared/types/filesystem/index.ts +22 -0
  422. package/shared/types/git.ts +171 -0
  423. package/shared/types/messaging/index.ts +239 -0
  424. package/shared/types/messaging/tool.ts +526 -0
  425. package/shared/types/network/api.ts +18 -0
  426. package/shared/types/network/index.ts +5 -0
  427. package/shared/types/stores/app.ts +23 -0
  428. package/shared/types/stores/dialog.ts +21 -0
  429. package/shared/types/stores/index.ts +3 -0
  430. package/shared/types/stores/settings.ts +15 -0
  431. package/shared/types/terminal/index.ts +44 -0
  432. package/shared/types/ui/components.ts +61 -0
  433. package/shared/types/ui/icons.ts +23 -0
  434. package/shared/types/ui/index.ts +22 -0
  435. package/shared/types/ui/notifications.ts +14 -0
  436. package/shared/types/ui/theme.ts +12 -0
  437. package/shared/types/websocket/index.ts +43 -0
  438. package/shared/types/window.d.ts +13 -0
  439. package/shared/utils/anonymous-user.ts +168 -0
  440. package/shared/utils/async.ts +10 -0
  441. package/shared/utils/diff-calculator.ts +184 -0
  442. package/shared/utils/file-type-detection.ts +166 -0
  443. package/shared/utils/logger.ts +158 -0
  444. package/shared/utils/message-formatter.ts +79 -0
  445. package/shared/utils/path.ts +47 -0
  446. package/shared/utils/ws-client.ts +768 -0
  447. package/shared/utils/ws-server.ts +660 -0
  448. package/static/audio/notification.ogg +0 -0
  449. package/static/favicon.svg +8 -0
  450. package/static/fonts/dm-sans/dm-sans-italic-latin-ext.woff2 +0 -0
  451. package/static/fonts/dm-sans/dm-sans-italic-latin.woff2 +0 -0
  452. package/static/fonts/dm-sans/dm-sans-normal-latin-ext.woff2 +0 -0
  453. package/static/fonts/dm-sans/dm-sans-normal-latin.woff2 +0 -0
  454. package/static/fonts/dm-sans.css +96 -0
  455. package/svelte.config.js +20 -0
  456. package/vite.config.ts +33 -0
@@ -0,0 +1,660 @@
1
+ /**
2
+ * WebSocket Server Core Library - Optimized
3
+ *
4
+ * High-performance WebSocket routing with:
5
+ * - Zero boilerplate configuration
6
+ * - 100% type inference from backend to frontend
7
+ * - Singleton TextEncoder/Decoder for performance
8
+ * - Pre-computed binary action detection
9
+ * - Context management (user/project)
10
+ * - TypeBox schema validation (bundled with Elysia)
11
+ */
12
+
13
+ import type { Elysia } from 'elysia';
14
+ import { t, type TSchema, type Static } from 'elysia';
15
+ import { Value } from '@sinclair/typebox/value';
16
+ import { debug } from './logger';
17
+
18
+ // ============================================================================
19
+ // Singleton Encoders (Performance Optimization)
20
+ // ============================================================================
21
+
22
+ /** Singleton TextEncoder - reused across all encode operations */
23
+ const textEncoder = new TextEncoder();
24
+
25
+ /** Singleton TextDecoder - reused across all decode operations */
26
+ const textDecoder = new TextDecoder();
27
+
28
+ // ============================================================================
29
+ // Pre-computed Binary Actions (Performance Optimization)
30
+ // ============================================================================
31
+
32
+ /** Actions known to contain binary data - skip containsBinary() check */
33
+ const BINARY_ACTIONS = new Set<string>([
34
+ 'preview:frame',
35
+ 'file:upload',
36
+ 'file:download',
37
+ 'terminal:binary'
38
+ ]);
39
+
40
+ /**
41
+ * Register an action as binary (for custom binary handlers)
42
+ */
43
+ export function registerBinaryAction(action: string): void {
44
+ BINARY_ACTIONS.add(action);
45
+ }
46
+
47
+ // ============================================================================
48
+ // WebSocket Connection Interface
49
+ // ============================================================================
50
+
51
+ /**
52
+ * WebSocket Connection with Context
53
+ * Extends Elysia WebSocket with custom context properties
54
+ */
55
+ export interface WSConnection {
56
+ /** WebSocket ready state (0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED) */
57
+ readyState: number;
58
+ /** Send message to client */
59
+ send: (data: string | ArrayBufferLike | Blob | ArrayBufferView) => void;
60
+ /** Close connection */
61
+ close: (code?: number, reason?: string) => void;
62
+ }
63
+
64
+ // ============================================================================
65
+ // Route Configuration Types
66
+ // ============================================================================
67
+
68
+ /**
69
+ * WebSocket route configuration
70
+ */
71
+ interface RouteConfig<TData extends TSchema> {
72
+ /** Incoming data schema (validates client → server) */
73
+ data: TData;
74
+ }
75
+
76
+ /**
77
+ * HTTP-like route configuration for request-response pattern
78
+ */
79
+ interface HTTPRouteConfig<
80
+ TData extends TSchema,
81
+ TResponse extends TSchema
82
+ > {
83
+ /** Request data schema (optional) */
84
+ data?: TData;
85
+ /** Response data schema (required) */
86
+ response: TResponse;
87
+ }
88
+
89
+ /**
90
+ * HTTP route handler callback
91
+ */
92
+ type HTTPHandler<
93
+ TData extends TSchema,
94
+ TResponse extends TSchema
95
+ > = (params: {
96
+ conn: WSConnection;
97
+ data: TData extends TSchema ? Static<TData> : never;
98
+ }) => Promise<Static<TResponse>> | Static<TResponse>;
99
+
100
+ /**
101
+ * WebSocket route handler callback
102
+ */
103
+ type RouteHandler<TData extends TSchema> = (params: {
104
+ conn: WSConnection;
105
+ data: Static<TData>;
106
+ }) => void | Promise<void>;
107
+
108
+ /**
109
+ * Internal route definition
110
+ */
111
+ interface Route {
112
+ action: string;
113
+ dataSchema: TSchema;
114
+ handler: (params: { conn: WSConnection; data: any }) => void | Promise<void>;
115
+ }
116
+
117
+ /**
118
+ * Internal HTTP route definition
119
+ */
120
+ interface HTTPRoute {
121
+ action: string;
122
+ dataSchema?: TSchema;
123
+ responseSchema: TSchema;
124
+ handler: (params: { conn: WSConnection; data: any }) => Promise<any> | any;
125
+ }
126
+
127
+ // ============================================================================
128
+ // Binary Message Utilities (Optimized)
129
+ // ============================================================================
130
+
131
+ /**
132
+ * Check if payload contains binary data (Uint8Array or ArrayBuffer)
133
+ * Optimized with early return and iterative approach
134
+ */
135
+ function containsBinary(obj: any): boolean {
136
+ if (obj instanceof Uint8Array || obj instanceof ArrayBuffer) return true;
137
+ if (typeof obj !== 'object' || obj === null) return false;
138
+
139
+ // Use stack-based iteration instead of recursion for deep objects
140
+ const stack = [obj];
141
+ while (stack.length > 0) {
142
+ const current = stack.pop();
143
+ for (const value of Object.values(current)) {
144
+ if (value instanceof Uint8Array || value instanceof ArrayBuffer) return true;
145
+ if (typeof value === 'object' && value !== null) {
146
+ stack.push(value);
147
+ }
148
+ }
149
+ }
150
+ return false;
151
+ }
152
+
153
+ /**
154
+ * Fast check using pre-computed binary actions
155
+ */
156
+ function isBinaryAction(action: string, payload: any): boolean {
157
+ if (BINARY_ACTIONS.has(action)) return true;
158
+ return containsBinary(payload);
159
+ }
160
+
161
+ /**
162
+ * Extract binary field and metadata from payload
163
+ */
164
+ function extractBinaryFields(payload: any): { binaryData: Uint8Array; metadata: Record<string, any> } {
165
+ const metadata: Record<string, any> = {};
166
+ let binaryData: Uint8Array = new Uint8Array(0);
167
+
168
+ for (const [key, value] of Object.entries(payload)) {
169
+ if (value instanceof Uint8Array) {
170
+ binaryData = value;
171
+ } else if (value instanceof ArrayBuffer) {
172
+ binaryData = new Uint8Array(value);
173
+ } else {
174
+ metadata[key] = value;
175
+ }
176
+ }
177
+
178
+ return { binaryData, metadata };
179
+ }
180
+
181
+ /**
182
+ * Encode a binary message with action and metadata
183
+ *
184
+ * Binary Message Format:
185
+ * ┌─────────────────┬────────────────┬─────────────────┬──────────────┬─────────────┐
186
+ * │ Action Length │ Action String │ Metadata Length │ Metadata JSON│ Binary Data │
187
+ * │ (1 byte) │ (N bytes) │ (4 bytes) │ (M bytes) │ (rest) │
188
+ * └─────────────────┴────────────────┴─────────────────┴──────────────┴─────────────┘
189
+ */
190
+ function encodeBinaryMessage(action: string, payload: any): ArrayBuffer {
191
+ const { binaryData, metadata } = extractBinaryFields(payload);
192
+
193
+ const actionBytes = textEncoder.encode(action);
194
+ const metaBytes = textEncoder.encode(JSON.stringify(metadata));
195
+
196
+ // Calculate total length
197
+ const totalLength = 1 + actionBytes.length + 4 + metaBytes.length + binaryData.length;
198
+ const buffer = new ArrayBuffer(totalLength);
199
+ const view = new DataView(buffer);
200
+ const uint8 = new Uint8Array(buffer);
201
+
202
+ let offset = 0;
203
+
204
+ // Action length (1 byte, max 255 characters)
205
+ view.setUint8(offset, actionBytes.length);
206
+ offset += 1;
207
+
208
+ // Action string
209
+ uint8.set(actionBytes, offset);
210
+ offset += actionBytes.length;
211
+
212
+ // Metadata length (4 bytes, big-endian)
213
+ view.setUint32(offset, metaBytes.length);
214
+ offset += 4;
215
+
216
+ // Metadata JSON
217
+ uint8.set(metaBytes, offset);
218
+ offset += metaBytes.length;
219
+
220
+ // Binary data (rest of buffer)
221
+ uint8.set(binaryData, offset);
222
+
223
+ return buffer;
224
+ }
225
+
226
+ /**
227
+ * Decode a binary message back to action and payload
228
+ */
229
+ function decodeBinaryMessage(buffer: ArrayBuffer): { action: string; payload: any } {
230
+ const view = new DataView(buffer);
231
+ const uint8 = new Uint8Array(buffer);
232
+
233
+ let offset = 0;
234
+
235
+ // Read action length (1 byte)
236
+ const actionLength = view.getUint8(offset);
237
+ offset += 1;
238
+
239
+ // Read action string
240
+ const actionBytes = uint8.slice(offset, offset + actionLength);
241
+ const action = textDecoder.decode(actionBytes);
242
+ offset += actionLength;
243
+
244
+ // Read metadata length (4 bytes)
245
+ const metaLength = view.getUint32(offset);
246
+ offset += 4;
247
+
248
+ // Read metadata JSON
249
+ const metaBytes = uint8.slice(offset, offset + metaLength);
250
+ const metadata = JSON.parse(textDecoder.decode(metaBytes));
251
+ offset += metaLength;
252
+
253
+ // Read binary data (rest of buffer)
254
+ const binaryData = uint8.slice(offset);
255
+
256
+ // Reconstruct payload with binary data
257
+ const payload = {
258
+ ...metadata,
259
+ data: binaryData // Binary field always named 'data'
260
+ };
261
+
262
+ return { action, payload };
263
+ }
264
+
265
+ // ============================================================================
266
+ // WebSocket Router
267
+ // ============================================================================
268
+
269
+ /**
270
+ * WebSocket Router with Builder Pattern
271
+ *
272
+ * Includes built-in context management for user/project tracking.
273
+ */
274
+ export class WSRouter<
275
+ TClient extends Record<string, any> = {},
276
+ TServer extends Record<string, any> = {}
277
+ > {
278
+ private routes = new Map<string, Route>();
279
+ private httpRoutes = new Map<string, HTTPRoute>();
280
+ private eventSchemas = new Map<string, TSchema>();
281
+
282
+ constructor() {
283
+ // Register built-in context management route
284
+ this.registerContextHandler();
285
+ }
286
+
287
+ /**
288
+ * Register built-in ws:set-context handler
289
+ * Allows frontend to sync user/project context
290
+ */
291
+ private registerContextHandler(): void {
292
+ this.httpRoutes.set('ws:set-context', {
293
+ action: 'ws:set-context',
294
+ dataSchema: t.Object({
295
+ userId: t.Optional(t.Union([t.String(), t.Null()])),
296
+ projectId: t.Optional(t.Union([t.String(), t.Null()]))
297
+ }),
298
+ responseSchema: t.Object({
299
+ userId: t.Union([t.String(), t.Null()]),
300
+ projectId: t.Union([t.String(), t.Null()])
301
+ }),
302
+ handler: async ({ conn, data }) => {
303
+ // Import ws server to update context
304
+ const { ws: wsServer } = await import('$backend/lib/utils/ws');
305
+
306
+ if (data.userId !== undefined) {
307
+ wsServer.setUser(conn, data.userId);
308
+ }
309
+ if (data.projectId !== undefined) {
310
+ wsServer.setProject(conn, data.projectId);
311
+ }
312
+
313
+ // Read back from connectionState (single source of truth)
314
+ const state = wsServer.getConnectionState(conn);
315
+ return {
316
+ userId: state?.userId ?? null,
317
+ projectId: state?.projectId ?? null
318
+ };
319
+ }
320
+ });
321
+ }
322
+
323
+ /**
324
+ * Register a WebSocket route handler
325
+ *
326
+ * @param action - Action name (e.g., 'terminal:init', 'chat:send')
327
+ * @param config - Route configuration with data schema
328
+ * @param handler - Handler callback receiving { conn, data }
329
+ */
330
+ on<TAction extends string, TData extends TSchema>(
331
+ action: TAction,
332
+ config: RouteConfig<TData>,
333
+ handler: RouteHandler<TData>
334
+ ): WSRouter<TClient & { [K in TAction]: Static<TData> }, TServer> {
335
+ // Validate action format
336
+ if (!action || typeof action !== 'string') {
337
+ throw new Error(`Invalid action name: ${action}`);
338
+ }
339
+
340
+ // Check for duplicate routes
341
+ if (this.routes.has(action)) {
342
+ throw new Error(`Route already exists: ${action}`);
343
+ }
344
+
345
+ // Store route definition
346
+ this.routes.set(action, {
347
+ action,
348
+ dataSchema: config.data,
349
+ handler: handler as any
350
+ });
351
+
352
+ return this as any;
353
+ }
354
+
355
+ /**
356
+ * Register an HTTP-like route handler for request-response pattern
357
+ *
358
+ * This method provides a simplified API for request-response interactions,
359
+ * automatically wrapping responses in { success, data?, error? } format.
360
+ *
361
+ * The handler should:
362
+ * - Return data directly (will be wrapped as { success: true, data })
363
+ * - Throw errors (will be wrapped as { success: false, error: message })
364
+ *
365
+ * Response schema should ONLY define the data structure, not the wrapper.
366
+ */
367
+ http<
368
+ TAction extends string,
369
+ TData extends TSchema = never,
370
+ TResponse extends TSchema = any
371
+ >(
372
+ action: TAction,
373
+ config: HTTPRouteConfig<TData, TResponse>,
374
+ handler: HTTPHandler<TData, TResponse>
375
+ ): WSRouter<
376
+ TClient & {
377
+ [K in TAction]: {
378
+ data: TData extends TSchema ? Static<TData> : undefined;
379
+ };
380
+ },
381
+ TServer & {
382
+ [K in `${TAction}:response`]: {
383
+ success: boolean;
384
+ data?: Static<TResponse>;
385
+ error?: string;
386
+ };
387
+ }
388
+ > {
389
+ // Validate action format
390
+ if (!action || typeof action !== 'string') {
391
+ throw new Error(`Invalid action name: ${action}`);
392
+ }
393
+
394
+ // Check for duplicate routes
395
+ if (this.httpRoutes.has(action)) {
396
+ throw new Error(`HTTP route already exists: ${action}`);
397
+ }
398
+
399
+ // Store HTTP route definition
400
+ this.httpRoutes.set(action, {
401
+ action,
402
+ dataSchema: config.data,
403
+ responseSchema: config.response,
404
+ handler: handler as any
405
+ });
406
+
407
+ return this as any;
408
+ }
409
+
410
+ /**
411
+ * Register SERVER → CLIENT event schema (independent)
412
+ *
413
+ * Declares event schemas that server can emit to clients.
414
+ * Events are independent from actions and can be emitted from anywhere using ws.emit()
415
+ */
416
+ emit<TEvent extends string, TEventSchema extends TSchema>(
417
+ event: TEvent,
418
+ schema: TEventSchema
419
+ ): WSRouter<
420
+ TClient,
421
+ TServer & { [K in TEvent]: Static<TEventSchema> }
422
+ > {
423
+ // Validate event format
424
+ if (!event || typeof event !== 'string') {
425
+ throw new Error(`Invalid event name: ${event}`);
426
+ }
427
+
428
+ // Check for duplicate event schemas
429
+ if (this.eventSchemas.has(event)) {
430
+ throw new Error(`Event schema already exists: ${event}`);
431
+ }
432
+
433
+ // Store event schema for type inference
434
+ this.eventSchemas.set(event, schema);
435
+
436
+ return this as any;
437
+ }
438
+
439
+ /**
440
+ * Merge another router into this one
441
+ */
442
+ merge<TC extends Record<string, any>, TS extends Record<string, any>>(
443
+ router: WSRouter<TC, TS>
444
+ ): WSRouter<TClient & TC, TServer & TS> {
445
+ // Copy all routes from the other router
446
+ for (const [action, route] of router.routes.entries()) {
447
+ if (this.routes.has(action)) {
448
+ throw new Error(`Route conflict during merge: ${action}`);
449
+ }
450
+ this.routes.set(action, route);
451
+ }
452
+
453
+ // Copy all HTTP routes from the other router (except built-in)
454
+ for (const [action, route] of router.httpRoutes.entries()) {
455
+ if (action === 'ws:set-context') continue; // Skip built-in
456
+ if (this.httpRoutes.has(action)) {
457
+ throw new Error(`HTTP route conflict during merge: ${action}`);
458
+ }
459
+ this.httpRoutes.set(action, route);
460
+ }
461
+
462
+ // Copy all event schemas from the other router
463
+ for (const [event, schema] of router.eventSchemas.entries()) {
464
+ if (this.eventSchemas.has(event)) {
465
+ throw new Error(`Event schema conflict during merge: ${event}`);
466
+ }
467
+ this.eventSchemas.set(event, schema);
468
+ }
469
+
470
+ return this as any;
471
+ }
472
+
473
+ /**
474
+ * Handle HTTP-like request-response message
475
+ * @internal
476
+ */
477
+ private async handleHTTPMessage(conn: any, action: string, payload: any, route: HTTPRoute) {
478
+ const responseAction = `${action}:response`;
479
+ // Extract requestId from payload to match request with response
480
+ const requestId = payload?.requestId;
481
+
482
+ try {
483
+ // Extract data from payload
484
+ const data = payload?.data || {};
485
+
486
+ // Validate request data schema (if exists)
487
+ let validatedData = {};
488
+ if (route.dataSchema) {
489
+ try {
490
+ // Check for binary BEFORE validation
491
+ const hasBinary = containsBinary(data);
492
+ validatedData = Value.Decode(route.dataSchema, data);
493
+
494
+ // Restore binary data if it was converted
495
+ if (hasBinary && data.data instanceof Uint8Array) {
496
+ (validatedData as any).data = data.data;
497
+ }
498
+ } catch (err) {
499
+ throw new Error(
500
+ `Request validation failed: ${err instanceof Error ? err.message : 'Unknown error'}`
501
+ );
502
+ }
503
+ }
504
+
505
+ // Execute handler (can throw)
506
+ const result = await route.handler({
507
+ conn: conn,
508
+ data: validatedData
509
+ });
510
+
511
+ // Validate response data schema
512
+ let validatedResponse;
513
+ try {
514
+ validatedResponse = Value.Decode(route.responseSchema, result);
515
+ } catch (err) {
516
+ debug.error('websocket', `Response validation failed for ${action}:`, err);
517
+ throw new Error(
518
+ `Response validation failed: ${err instanceof Error ? err.message : 'Unknown error'}`
519
+ );
520
+ }
521
+
522
+ // Check if response contains binary data
523
+ if (isBinaryAction(responseAction, validatedResponse)) {
524
+ // For binary responses, wrap in success envelope with requestId
525
+ const wrappedResponse = { success: true, data: validatedResponse, requestId };
526
+ const binaryMessage = encodeBinaryMessage(responseAction, wrappedResponse);
527
+ conn.send(Buffer.from(binaryMessage));
528
+ debug.log('websocket', 'HTTP success (binary):', action);
529
+ } else {
530
+ // For JSON responses, wrap in success envelope with requestId
531
+ const wrappedResponse = { success: true, data: validatedResponse, requestId };
532
+ conn.send(JSON.stringify({ action: responseAction, payload: wrappedResponse }));
533
+ debug.log('websocket', 'HTTP success (JSON):', action);
534
+ }
535
+ } catch (err) {
536
+ // Catch ANY error and send wrapped in { success: false, error, requestId }
537
+ const errorMessage = err instanceof Error ? err.message : String(err);
538
+
539
+ const errorResponse = { success: false, error: errorMessage, requestId };
540
+ conn.send(JSON.stringify({ action: responseAction, payload: errorResponse }));
541
+ debug.error('websocket', `HTTP error [${action}]:`, errorMessage);
542
+ }
543
+ }
544
+
545
+ /**
546
+ * Handle incoming WebSocket message (JSON or Binary)
547
+ * @internal
548
+ */
549
+ private async handleMessage(conn: any, message: any) {
550
+ try {
551
+ let action: string;
552
+ let payload: any;
553
+
554
+ // Check if message is binary (ArrayBuffer or Buffer)
555
+ if (message instanceof ArrayBuffer) {
556
+ // Decode binary message
557
+ const decoded = decodeBinaryMessage(message);
558
+ action = decoded.action;
559
+ payload = decoded.payload;
560
+ } else if (Buffer.isBuffer(message)) {
561
+ // Node.js Buffer - convert to ArrayBuffer
562
+ const arrayBuffer = new ArrayBuffer(message.byteLength);
563
+ new Uint8Array(arrayBuffer).set(message);
564
+ const decoded = decodeBinaryMessage(arrayBuffer);
565
+ action = decoded.action;
566
+ payload = decoded.payload;
567
+ } else {
568
+ // Parse JSON message
569
+ const parsed = typeof message === 'string' ? JSON.parse(message) : message;
570
+ action = parsed.action;
571
+ payload = parsed.payload;
572
+ }
573
+
574
+ if (!action || typeof action !== 'string') {
575
+ debug.error('websocket', 'Invalid message format');
576
+ return;
577
+ }
578
+
579
+ // Check if this is an HTTP route
580
+ const httpRoute = this.httpRoutes.get(action);
581
+ if (httpRoute) {
582
+ await this.handleHTTPMessage(conn, action, payload, httpRoute);
583
+ return;
584
+ }
585
+
586
+ // Find regular WebSocket route
587
+ const route = this.routes.get(action);
588
+ if (!route) {
589
+ debug.warn('websocket', `Unknown action: ${action}`);
590
+ return;
591
+ }
592
+
593
+ // Validate payload against schema
594
+ try {
595
+ // Use TypeBox Value.Decode (bundled with Elysia)
596
+ const validatedData = Value.Decode(route.dataSchema, payload);
597
+
598
+ // Execute handler
599
+ await route.handler({ conn: conn, data: validatedData });
600
+ } catch (err) {
601
+ debug.error('websocket', `Data validation failed for ${action}:`, err);
602
+ }
603
+ } catch (err) {
604
+ debug.error('websocket', 'Message handling error:', err);
605
+ }
606
+ }
607
+
608
+ /**
609
+ * Convert router to Elysia plugin
610
+ */
611
+ asPlugin(path: string = '/ws') {
612
+ return (app: Elysia) => {
613
+ return app.ws(path, {
614
+ message: (conn, message) => {
615
+ this.handleMessage(conn, message);
616
+ },
617
+ open: async (conn) => {
618
+ debug.log('websocket', 'Client connected');
619
+
620
+ // Register connection with ws singleton
621
+ try {
622
+ const { ws: wsServer } = await import('$backend/lib/utils/ws');
623
+ wsServer.register(conn);
624
+ } catch (err) {
625
+ debug.error('websocket', 'Failed to register connection:', err);
626
+ }
627
+ },
628
+ close: async (conn) => {
629
+ debug.log('websocket', 'Client disconnected');
630
+
631
+ // Unregister connection from ws singleton
632
+ // All registered cleanups are called automatically by unregister()
633
+ try {
634
+ const { ws: wsServer } = await import('$backend/lib/utils/ws');
635
+ wsServer.unregister(conn);
636
+ } catch (err) {
637
+ debug.error('websocket', 'Failed to unregister connection:', err);
638
+ }
639
+ }
640
+ });
641
+ };
642
+ }
643
+
644
+ /**
645
+ * Phantom getter for type inference
646
+ */
647
+ get $api(): { client: TClient; server: TServer } {
648
+ throw new Error('$api is a phantom getter for type inference only');
649
+ }
650
+ }
651
+
652
+ /**
653
+ * Create a new WebSocket router
654
+ */
655
+ export function createRouter(): WSRouter {
656
+ return new WSRouter();
657
+ }
658
+
659
+ // Export binary utilities for advanced use cases
660
+ export { encodeBinaryMessage, decodeBinaryMessage, containsBinary, isBinaryAction };
Binary file
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
2
+ <!-- Background -->
3
+ <rect width="100" height="100" rx="22" fill="#7C3AED"/>
4
+ <!-- Shared outline: sharp square (visible on right half) -->
5
+ <rect x="24" y="24" width="52" height="52" fill="none" stroke="#fff" stroke-width="8"/>
6
+ <!-- Closed half: filled left side -->
7
+ <rect x="20" y="20" width="30" height="60" fill="#fff"/>
8
+ </svg>