@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,235 @@
1
+ /**
2
+ * Browser Native UI WebSocket Handler
3
+ * Handles native browser UI interactions: dialogs, print, select dropdown, and context menu
4
+ * **PROJECT ISOLATION**: Uses project-specific BrowserPreviewService instances
5
+ *
6
+ * Note: Event forwarding is now handled by BrowserPreviewServiceManager
7
+ * per-project service instances with proper project scoping.
8
+ */
9
+
10
+ import { t } from 'elysia';
11
+ import { createRouter } from '$shared/utils/ws-server';
12
+ import { debug } from '$shared/utils/logger';
13
+ import { browserPreviewServiceManager } from '../../../lib/preview/index';
14
+ import { ws } from '$backend/lib/utils/ws';
15
+
16
+ // Event forwarding is now handled automatically by BrowserPreviewServiceManager
17
+ // when service instances are created, ensuring proper project isolation.
18
+
19
+ export const nativeUIPreviewHandler = createRouter()
20
+ // Action: Client responds to a dialog (alert, confirm, prompt)
21
+ .on('preview:browser-dialog-input', {
22
+ data: t.Object({
23
+ dialogId: t.String({ minLength: 1 }),
24
+ accept: t.Boolean(),
25
+ promptText: t.Optional(t.String())
26
+ })
27
+ }, async ({ data, conn }) => {
28
+ try {
29
+ const { dialogId, accept, promptText } = data;
30
+ const projectId = ws.getProjectId(conn);
31
+
32
+ // Get project-specific preview service
33
+ const previewService = browserPreviewServiceManager.getService(projectId);
34
+
35
+ debug.log('preview', `📬 Dialog response received from frontend - dialogId: ${dialogId}, accept: ${accept}${promptText ? `, promptText: "${promptText}"` : ''} (project: ${projectId})`);
36
+
37
+ // Get active tab
38
+ const tab = previewService.getActiveTab();
39
+ if (!tab) {
40
+ debug.error('preview', `❌ No active tab for dialog input (project: ${projectId})`);
41
+ return;
42
+ }
43
+
44
+ debug.log('preview', `✅ Active tab found: ${tab.id}`);
45
+
46
+ // Send response to dialog handler
47
+ const result = await previewService.respondToDialog({
48
+ tabId: tab.id,
49
+ dialogId,
50
+ accept,
51
+ promptText
52
+ });
53
+
54
+ if (result) {
55
+ debug.log('preview', `✅ Dialog response processed successfully - dialogId: ${dialogId}`);
56
+ } else {
57
+ debug.warn('preview', `⚠️ Dialog response failed - dialogId: ${dialogId} (dialog may not be found)`);
58
+ }
59
+ } catch (error) {
60
+ debug.error('preview', '💥 Error handling dialog response:', error);
61
+ }
62
+ })
63
+
64
+ // Action: Client triggers print (in response to print event or manually)
65
+ .on('preview:browser-print-input', {
66
+ data: t.Object({})
67
+ }, async ({ conn }) => {
68
+ try {
69
+ const projectId = ws.getProjectId(conn);
70
+
71
+ // Get project-specific preview service
72
+ const previewService = browserPreviewServiceManager.getService(projectId);
73
+
74
+ const tab = previewService.getActiveTab();
75
+ if (!tab) {
76
+ debug.error('preview', `No active tab for print input (project: ${projectId})`);
77
+ return;
78
+ }
79
+
80
+ debug.log('preview', `🖨️ Print trigger received for tab: ${tab.id} (project: ${projectId})`);
81
+
82
+ // For native print, we just acknowledge the trigger
83
+ // Frontend will handle window.print() directly
84
+ } catch (error) {
85
+ debug.error('preview', 'Error handling print trigger:', error);
86
+ }
87
+ })
88
+
89
+ // Action: Client responds to a select dropdown
90
+ .on('preview:browser-select-input', {
91
+ data: t.Object({
92
+ sessionId: t.String({ minLength: 1 }),
93
+ selectId: t.String({ minLength: 1 }),
94
+ selectedIndex: t.Number()
95
+ })
96
+ }, async ({ data, conn }) => {
97
+ try {
98
+ const { sessionId, selectId, selectedIndex } = data;
99
+ const projectId = ws.getProjectId(conn);
100
+
101
+ // Get project-specific preview service
102
+ const previewService = browserPreviewServiceManager.getService(projectId);
103
+
104
+ debug.log('preview', `📋 Select response received - selectId: ${selectId}, selectedIndex: ${selectedIndex} (project: ${projectId})`);
105
+
106
+ // Send response to native UI handler
107
+ await previewService.handleSelectResponse(sessionId, {
108
+ tabId: sessionId,
109
+ selectId,
110
+ selectedIndex
111
+ });
112
+ } catch (error) {
113
+ debug.error('preview', 'Error handling select response:', error);
114
+ }
115
+ })
116
+
117
+ // Action: Client responds to a context menu
118
+ .on('preview:browser-context-menu-input', {
119
+ data: t.Object({
120
+ sessionId: t.String({ minLength: 1 }),
121
+ menuId: t.String({ minLength: 1 }),
122
+ itemId: t.String({ minLength: 1 }),
123
+ clipboardText: t.Optional(t.String())
124
+ })
125
+ }, async ({ data, conn }) => {
126
+ try {
127
+ const { sessionId, menuId, itemId, clipboardText } = data;
128
+ const projectId = ws.getProjectId(conn);
129
+
130
+ // Get project-specific preview service
131
+ const previewService = browserPreviewServiceManager.getService(projectId);
132
+
133
+ debug.log('preview', `📜 Context menu response received - menuId: ${menuId}, itemId: ${itemId} (project: ${projectId})`);
134
+
135
+ // Send response to native UI handler
136
+ await previewService.handleContextMenuResponse(sessionId, {
137
+ tabId: sessionId,
138
+ menuId,
139
+ itemId
140
+ }, clipboardText);
141
+ } catch (error) {
142
+ debug.error('preview', 'Error handling context menu response:', error);
143
+ }
144
+ })
145
+
146
+ // Event declarations (Server → Client)
147
+ // These events are emitted by preview service when native UI interactions occur
148
+ .emit('preview:browser-dialog', t.Object({
149
+ sessionId: t.String(),
150
+ dialogId: t.String(),
151
+ type: t.Union([
152
+ t.Literal('alert'),
153
+ t.Literal('confirm'),
154
+ t.Literal('prompt'),
155
+ t.Literal('beforeunload')
156
+ ]),
157
+ message: t.String(),
158
+ defaultValue: t.Optional(t.String()),
159
+ timestamp: t.Number()
160
+ }))
161
+
162
+ .emit('preview:browser-print', t.Object({
163
+ sessionId: t.String(),
164
+ timestamp: t.Number()
165
+ }))
166
+
167
+ .emit('preview:browser-select', t.Object({
168
+ sessionId: t.String(),
169
+ selectId: t.String(),
170
+ x: t.Number(),
171
+ y: t.Number(),
172
+ boundingBox: t.Object({
173
+ x: t.Number(),
174
+ y: t.Number(),
175
+ width: t.Number(),
176
+ height: t.Number()
177
+ }),
178
+ options: t.Array(t.Object({
179
+ index: t.Number(),
180
+ value: t.String(),
181
+ text: t.String(),
182
+ selected: t.Boolean(),
183
+ disabled: t.Optional(t.Boolean())
184
+ })),
185
+ selectedIndex: t.Number(),
186
+ timestamp: t.Number()
187
+ }))
188
+
189
+ .emit('preview:browser-context-menu', t.Object({
190
+ sessionId: t.String(),
191
+ menuId: t.String(),
192
+ x: t.Number(),
193
+ y: t.Number(),
194
+ items: t.Array(t.Object({
195
+ id: t.String(),
196
+ label: t.String(),
197
+ enabled: t.Boolean(),
198
+ type: t.Optional(t.Union([
199
+ t.Literal('normal'),
200
+ t.Literal('separator'),
201
+ t.Literal('submenu')
202
+ ])),
203
+ icon: t.Optional(t.String())
204
+ })),
205
+ elementInfo: t.Object({
206
+ tagName: t.String(),
207
+ isLink: t.Boolean(),
208
+ isImage: t.Boolean(),
209
+ isInput: t.Boolean(),
210
+ isTextSelected: t.Boolean(),
211
+ linkUrl: t.Optional(t.String()),
212
+ imageUrl: t.Optional(t.String()),
213
+ inputType: t.Optional(t.String())
214
+ }),
215
+ timestamp: t.Number()
216
+ }))
217
+
218
+ .emit('preview:browser-copy-to-clipboard', t.Object({
219
+ text: t.String()
220
+ }))
221
+
222
+ .emit('preview:browser-open-url-new-tab', t.Object({
223
+ url: t.String()
224
+ }))
225
+
226
+ .emit('preview:browser-download-image', t.Object({
227
+ base64: t.String(),
228
+ type: t.String(),
229
+ filename: t.String()
230
+ }))
231
+
232
+ .emit('preview:browser-copy-image-to-clipboard', t.Object({
233
+ base64: t.String(),
234
+ type: t.String()
235
+ }));
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Browser Streaming Statistics WebSocket Handler
3
+ * Handles getting browser streaming statistics
4
+ * **PROJECT ISOLATION**: Uses project-specific BrowserPreviewService instances
5
+ */
6
+
7
+ import { t } from 'elysia';
8
+ import { createRouter } from '$shared/utils/ws-server';
9
+ import { browserPreviewServiceManager } from '../../../lib/preview/index';
10
+ import { ws } from '$backend/lib/utils/ws';
11
+
12
+ export const statsPreviewHandler = createRouter()
13
+ .http('preview:browser-tab-stats', {
14
+ data: t.Object({
15
+ tabId: t.Optional(t.String()) // If not provided, get active tab stats
16
+ }),
17
+ response: t.Object({
18
+ tabId: t.String(),
19
+ isWebCodecsActive: t.Boolean(),
20
+ streamingMode: t.String(),
21
+ stats: t.Union([
22
+ t.Object({
23
+ videoBytesSent: t.Number(),
24
+ audioBytesSent: t.Number(),
25
+ videoFramesEncoded: t.Number(),
26
+ audioFramesEncoded: t.Number(),
27
+ connectionState: t.String()
28
+ }),
29
+ t.Null()
30
+ ]),
31
+ timestamp: t.String()
32
+ })
33
+ }, async ({ data, conn }) => {
34
+ const { tabId } = data;
35
+ const projectId = ws.getProjectId(conn);
36
+
37
+ // Get project-specific preview service
38
+ const previewService = browserPreviewServiceManager.getService(projectId);
39
+
40
+ // Get tab (active tab if not specified)
41
+ const tab = tabId ? previewService.getTab(tabId) : previewService.getActiveTab();
42
+ if (!tab) {
43
+ throw new Error(tabId ? `Tab not found: ${tabId}` : 'No active tab');
44
+ }
45
+
46
+ const stats = await previewService.getWebCodecsStats(tab.id);
47
+
48
+ return {
49
+ tabId: tab.id,
50
+ isWebCodecsActive: previewService.isWebCodecsActive(tab.id),
51
+ streamingMode: 'webcodecs',
52
+ stats: stats ?? null,
53
+ timestamp: new Date().toISOString()
54
+ };
55
+ });
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Browser Tab Info WebSocket Handler
3
+ * Handles getting browser tab information and listing all tabs
4
+ * **PROJECT ISOLATION**: Uses project-specific BrowserPreviewService instances
5
+ */
6
+
7
+ import { t } from 'elysia';
8
+ import { createRouter } from '$shared/utils/ws-server';
9
+ import { browserPreviewServiceManager } from '../../../lib/preview/index';
10
+ import { ws } from '$backend/lib/utils/ws';
11
+ import { debug } from '$shared/utils/logger';
12
+
13
+ export const tabInfoPreviewHandler = createRouter()
14
+ // Get single tab info
15
+ .http('preview:browser-tab-info', {
16
+ data: t.Object({
17
+ tabId: t.Optional(t.String()) // If not provided, get active tab info
18
+ }),
19
+ response: t.Object({
20
+ tabId: t.String(),
21
+ url: t.String(),
22
+ title: t.String(),
23
+ quality: t.String(),
24
+ isStreaming: t.Boolean(),
25
+ deviceSize: t.String(),
26
+ rotation: t.String(),
27
+ isActive: t.Boolean()
28
+ })
29
+ }, async ({ data, conn }) => {
30
+ const { tabId } = data;
31
+ const projectId = ws.getProjectId(conn);
32
+
33
+ // Get project-specific preview service
34
+ const previewService = browserPreviewServiceManager.getService(projectId);
35
+
36
+ // Get tab (active tab if not specified)
37
+ const tab = tabId ? previewService.getTab(tabId) : previewService.getActiveTab();
38
+ if (!tab) {
39
+ throw new Error(tabId ? `Tab not found: ${tabId}` : 'No active tab');
40
+ }
41
+
42
+ const tabInfo = previewService.getTabInfo(tab.id);
43
+ if (!tabInfo) {
44
+ throw new Error('Tab info not found');
45
+ }
46
+
47
+ return {
48
+ ...tabInfo,
49
+ tabId: tabInfo.id
50
+ };
51
+ })
52
+
53
+ // Get all active tabs (for session recovery after browser refresh)
54
+ .http('preview:browser-tabs-list', {
55
+ data: t.Object({}),
56
+ response: t.Object({
57
+ tabs: t.Array(t.Object({
58
+ tabId: t.String(),
59
+ url: t.String(),
60
+ title: t.String(),
61
+ quality: t.String(),
62
+ isStreaming: t.Boolean(),
63
+ deviceSize: t.String(),
64
+ rotation: t.String(),
65
+ isActive: t.Boolean()
66
+ })),
67
+ activeTabId: t.Union([t.String(), t.Null()]),
68
+ count: t.Number()
69
+ })
70
+ }, async ({ conn }) => {
71
+ const projectId = ws.getProjectId(conn);
72
+
73
+ // Get project-specific preview service
74
+ const previewService = browserPreviewServiceManager.getService(projectId);
75
+
76
+ const allTabsInfo = previewService.getAllTabsInfo();
77
+ const activeTab = previewService.getActiveTab();
78
+
79
+ debug.log('preview', `📋 Listing ${allTabsInfo.length} active browser tabs for session recovery (project: ${projectId})`);
80
+
81
+ return {
82
+ tabs: allTabsInfo.map(tab => ({
83
+ tabId: tab.id,
84
+ url: tab.url,
85
+ title: tab.title,
86
+ quality: tab.quality,
87
+ isStreaming: tab.isStreaming,
88
+ deviceSize: tab.deviceSize,
89
+ rotation: tab.rotation,
90
+ isActive: tab.isActive
91
+ })),
92
+ activeTabId: activeTab?.id || null,
93
+ count: allTabsInfo.length
94
+ };
95
+ })
96
+
97
+ // Switch to a specific tab (for session recovery)
98
+ .http('preview:browser-tab-switch', {
99
+ data: t.Object({
100
+ tabId: t.String()
101
+ }),
102
+ response: t.Object({
103
+ success: t.Boolean(),
104
+ tabId: t.String(),
105
+ message: t.String()
106
+ })
107
+ }, async ({ data, conn }) => {
108
+ const { tabId } = data;
109
+ const projectId = ws.getProjectId(conn);
110
+
111
+ // Get project-specific preview service
112
+ const previewService = browserPreviewServiceManager.getService(projectId);
113
+
114
+ const success = previewService.switchTab(tabId);
115
+ if (!success) {
116
+ throw new Error(`Failed to switch to tab: ${tabId}`);
117
+ }
118
+
119
+ debug.log('preview', `🔄 Switched to tab: ${tabId} (project: ${projectId})`);
120
+
121
+ return {
122
+ success: true,
123
+ tabId,
124
+ message: `Switched to tab ${tabId}`
125
+ };
126
+ });
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Browser Tab Operations WebSocket Handlers
3
+ * Handles tab lifecycle operations: open, navigate, close
4
+ * **PROJECT ISOLATION**: Each project has its own BrowserPreviewService instance
5
+ */
6
+
7
+ import { t } from 'elysia';
8
+ import { createRouter } from '$shared/utils/ws-server';
9
+ import { browserPreviewServiceManager } from '../../../lib/preview/index';
10
+ import { ws } from '$backend/lib/utils/ws';
11
+ import { debug } from '$shared/utils/logger';
12
+
13
+ // Timeout for browser tab open (60 seconds)
14
+ const OPEN_TIMEOUT = 60000;
15
+
16
+ export const tabPreviewHandler = createRouter()
17
+ // Open new browser tab
18
+ .http('preview:browser-tab-open', {
19
+ data: t.Object({
20
+ url: t.Optional(t.String()), // URL is now optional - can create blank tab
21
+ deviceSize: t.Optional(t.Union([
22
+ t.Literal('desktop'),
23
+ t.Literal('laptop'),
24
+ t.Literal('tablet'),
25
+ t.Literal('mobile')
26
+ ])),
27
+ rotation: t.Optional(t.Union([
28
+ t.Literal('portrait'),
29
+ t.Literal('landscape')
30
+ ])),
31
+ }),
32
+ response: t.Object({
33
+ tabId: t.String(),
34
+ quality: t.String(),
35
+ url: t.String(),
36
+ title: t.String(),
37
+ isActive: t.Boolean(),
38
+ message: t.String()
39
+ })
40
+ }, async ({ data, conn }) => {
41
+ const projectId = ws.getProjectId(conn);
42
+
43
+ debug.log('preview', `🔴🔴🔴 browser-tab-open - Request received for project: ${projectId} 🔴🔴🔴`);
44
+
45
+ const {
46
+ url,
47
+ deviceSize = 'laptop',
48
+ rotation = 'portrait'
49
+ } = data;
50
+
51
+ debug.log('preview', `📥 Tab open params - URL: ${url || 'about:blank'}, deviceSize: ${deviceSize}, rotation: ${rotation}`);
52
+
53
+ // Get project-specific preview service
54
+ const previewService = browserPreviewServiceManager.getService(projectId);
55
+
56
+ // Create browser tab
57
+ const tabPromise = previewService.createTab(
58
+ url, // Can be undefined for blank tab
59
+ deviceSize as 'desktop' | 'laptop' | 'tablet' | 'mobile',
60
+ rotation as 'portrait' | 'landscape'
61
+ );
62
+ const timeoutPromise = new Promise((_, reject) => {
63
+ setTimeout(() => reject(new Error('Browser tab open timeout - took longer than 60 seconds')), OPEN_TIMEOUT);
64
+ });
65
+
66
+ debug.log('preview', `⏳ Opening browser tab (timeout: ${OPEN_TIMEOUT}ms)...`);
67
+ const tab = await Promise.race([tabPromise, timeoutPromise]) as Awaited<typeof tabPromise>;
68
+
69
+ debug.log('preview', `✅ Browser tab opened successfully - tabId: ${tab.id}, URL: ${tab.url}, project: ${projectId}`);
70
+
71
+ // Tab activity is marked automatically in tab-manager
72
+
73
+ return {
74
+ tabId: tab.id,
75
+ quality: tab.quality,
76
+ url: tab.url,
77
+ title: tab.title,
78
+ isActive: tab.isActive,
79
+ message: `Browser tab opened with ${tab.quality} quality streaming`
80
+ };
81
+ })
82
+
83
+ // Navigate browser tab
84
+ .http('preview:browser-tab-navigate', {
85
+ data: t.Object({
86
+ url: t.String({ minLength: 1 }),
87
+ tabId: t.Optional(t.String()) // If not provided, navigate active tab
88
+ }),
89
+ response: t.Object({
90
+ tabId: t.String(),
91
+ finalUrl: t.String(),
92
+ title: t.String(),
93
+ message: t.String()
94
+ })
95
+ }, async ({ data, conn }) => {
96
+ const { url, tabId } = data;
97
+ const projectId = ws.getProjectId(conn);
98
+
99
+ // Get project-specific preview service
100
+ const previewService = browserPreviewServiceManager.getService(projectId);
101
+
102
+ // Get tab to navigate (active tab if not specified)
103
+ const tab = tabId ? previewService.getTab(tabId) : previewService.getActiveTab();
104
+ if (!tab) {
105
+ throw new Error(tabId ? `Tab not found: ${tabId}` : 'No active tab');
106
+ }
107
+
108
+ debug.log('preview', `🌐 Navigating tab ${tab.id} to: ${url} (project: ${projectId})`);
109
+
110
+ const finalUrl = await previewService.navigateTab(tab.id, url);
111
+
112
+ debug.log('preview', `✅ Navigation completed - final URL: ${finalUrl}`);
113
+
114
+ return {
115
+ tabId: tab.id,
116
+ finalUrl: finalUrl,
117
+ title: tab.title,
118
+ message: 'Navigation completed'
119
+ };
120
+ })
121
+
122
+ // Close browser tab
123
+ .http('preview:browser-tab-close', {
124
+ data: t.Object({
125
+ tabId: t.Optional(t.String()) // If not provided, close active tab
126
+ }),
127
+ response: t.Object({
128
+ success: t.Boolean(),
129
+ tabId: t.String(),
130
+ newActiveTabId: t.Union([t.String(), t.Null()]),
131
+ message: t.String()
132
+ })
133
+ }, async ({ data, conn }) => {
134
+ const { tabId } = data;
135
+ const projectId = ws.getProjectId(conn);
136
+
137
+ // Get project-specific preview service
138
+ const previewService = browserPreviewServiceManager.getService(projectId);
139
+
140
+ // Get tab to close (active tab if not specified)
141
+ const tab = tabId ? previewService.getTab(tabId) : previewService.getActiveTab();
142
+
143
+ if (!tab) {
144
+ throw new Error(tabId ? `Tab not found: ${tabId}` : 'No active tab');
145
+ }
146
+
147
+ const closingTabId = tab.id;
148
+
149
+ debug.log('preview', `🗑️ Closing tab: ${closingTabId} (project: ${projectId})`);
150
+
151
+ // Close tab
152
+ const result = await previewService.closeTab(closingTabId);
153
+
154
+ if (!result.success) {
155
+ throw new Error(`Failed to close tab: ${closingTabId}`);
156
+ }
157
+
158
+ debug.log('preview', `✅ Tab closed: ${closingTabId} (new active: ${result.newActiveTabId || 'none'})`);
159
+
160
+ return {
161
+ success: true,
162
+ tabId: closingTabId,
163
+ newActiveTabId: result.newActiveTabId,
164
+ message: `Tab ${closingTabId} closed successfully`
165
+ };
166
+ });