@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,197 @@
1
+ <script lang="ts">
2
+ import { userStore } from '$frontend/lib/stores/features/user.svelte';
3
+ import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
4
+ import Icon from '../../common/Icon.svelte';
5
+ import { debug } from '$shared/utils/logger';
6
+
7
+ // State
8
+ let userNameInput = $state('');
9
+ let isEditing = $state(false);
10
+ let isSaving = $state(false);
11
+
12
+ // Update input when user changes
13
+ $effect(() => {
14
+ if (userStore.currentUser?.name) {
15
+ userNameInput = userStore.currentUser.name;
16
+ }
17
+ });
18
+
19
+ // Handle save user name
20
+ async function saveUserName() {
21
+ if (!userNameInput.trim()) {
22
+ addNotification({
23
+ type: 'error',
24
+ title: 'Validation Error',
25
+ message: 'Name cannot be empty'
26
+ });
27
+ return;
28
+ }
29
+
30
+ isSaving = true;
31
+
32
+ try {
33
+ const success = await userStore.updateName(userNameInput.trim());
34
+
35
+ if (success) {
36
+ isEditing = false;
37
+ addNotification({
38
+ type: 'success',
39
+ title: 'Updated',
40
+ message: 'Display name updated successfully'
41
+ });
42
+ } else {
43
+ addNotification({
44
+ type: 'error',
45
+ title: 'Error',
46
+ message: 'Failed to update user name'
47
+ });
48
+ }
49
+ } catch (error) {
50
+ debug.error('settings', 'Error updating user name:', error);
51
+ addNotification({
52
+ type: 'error',
53
+ title: 'Error',
54
+ message: 'An error occurred while updating user name'
55
+ });
56
+ } finally {
57
+ isSaving = false;
58
+ }
59
+ }
60
+
61
+ // Handle cancel edit
62
+ function cancelEdit() {
63
+ userNameInput = userStore.currentUser?.name || '';
64
+ isEditing = false;
65
+ }
66
+
67
+ // Handle start edit
68
+ function startEdit() {
69
+ isEditing = true;
70
+ }
71
+ </script>
72
+
73
+ <div class="py-1">
74
+ <h3 class="text-base font-bold text-slate-900 dark:text-slate-100 mb-1.5">User Profile</h3>
75
+ <p class="text-sm text-slate-600 dark:text-slate-500 mb-5">
76
+ Manage your identity and display preferences
77
+ </p>
78
+
79
+ {#if !userStore.currentUser}
80
+ <div class="flex items-center justify-center gap-3 py-10 text-slate-600 dark:text-slate-500 text-sm">
81
+ <div
82
+ class="w-5 h-5 border-2 border-violet-500/20 border-t-violet-600 rounded-full animate-spin"
83
+ ></div>
84
+ <span>Loading user settings...</span>
85
+ </div>
86
+ {:else}
87
+ <div class="flex flex-col gap-4">
88
+ <!-- Current User Card -->
89
+ <div
90
+ class="flex items-center gap-3.5 p-4.5 bg-gradient-to-br from-violet-500/10 to-purple-500/5 dark:from-violet-500/10 dark:to-purple-500/8 border border-violet-500/20 rounded-xl"
91
+ >
92
+ <div
93
+ class="flex items-center justify-center w-12 h-12 rounded-xl text-lg font-bold text-white shrink-0"
94
+ style="background-color: {userStore.currentUser?.color || '#7c3aed'}"
95
+ >
96
+ {userStore.currentUser?.avatar || 'U'}
97
+ </div>
98
+ <div class="flex-1 min-w-0">
99
+ <div class="text-base font-semibold text-slate-900 dark:text-slate-100 mb-0.5">
100
+ {userStore.currentUser?.name || 'Anonymous User'}
101
+ </div>
102
+ <div class="text-xs text-slate-600 dark:text-slate-500">Anonymous user identity</div>
103
+ </div>
104
+ <div
105
+ class="flex items-center gap-1.5 py-1.5 px-3 bg-emerald-500/15 rounded-full text-xs font-medium text-emerald-500"
106
+ >
107
+ <span class="w-1.5 h-1.5 bg-emerald-500 rounded-full animate-pulse"></span>
108
+ <span>Active</span>
109
+ </div>
110
+ </div>
111
+
112
+ <!-- Edit Display Name -->
113
+ <div
114
+ class="p-4 bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-800 rounded-xl"
115
+ >
116
+ <div
117
+ class="flex items-center gap-2 text-sm font-semibold text-slate-500 mb-3"
118
+ >
119
+ <Icon name="lucide:pencil" class="w-4 h-4 opacity-70" />
120
+ <span>Display Name</span>
121
+ </div>
122
+
123
+ {#if isEditing}
124
+ <div class="flex flex-col gap-3">
125
+ <input
126
+ type="text"
127
+ bind:value={userNameInput}
128
+ placeholder="Enter your display name"
129
+ class="w-full py-3 px-3.5 bg-slate-50 dark:bg-slate-900/80 border border-violet-500/20 rounded-lg text-slate-900 dark:text-slate-100 text-sm outline-none transition-all duration-150 placeholder:text-slate-600 dark:placeholder:text-slate-500 focus:border-violet-600 focus:shadow-[0_0_0_3px_rgba(124,58,237,0.1)]"
130
+ />
131
+ <div class="flex gap-2.5">
132
+ <button
133
+ type="button"
134
+ class="flex items-center justify-center gap-1.5 py-2.5 px-4 border-none rounded-lg text-sm font-semibold cursor-pointer transition-all duration-150 bg-gradient-to-br from-violet-600 to-purple-600 text-white hover:shadow-violet-500/30 disabled:opacity-50 disabled:cursor-not-allowed"
135
+ onclick={saveUserName}
136
+ disabled={!userNameInput.trim() || isSaving}
137
+ >
138
+ {#if isSaving}
139
+ <div
140
+ class="w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin"
141
+ ></div>
142
+ Saving...
143
+ {:else}
144
+ <Icon name="lucide:check" class="w-4 h-4" />
145
+ Save
146
+ {/if}
147
+ </button>
148
+ <button
149
+ type="button"
150
+ class="flex items-center justify-center gap-1.5 py-2.5 px-4 border-none rounded-lg text-sm font-semibold cursor-pointer transition-all duration-150 bg-slate-100 dark:bg-slate-600/20 text-slate-500 hover:bg-slate-200 dark:hover:bg-slate-600/30 disabled:opacity-50 disabled:cursor-not-allowed"
151
+ onclick={cancelEdit}
152
+ disabled={isSaving}
153
+ >
154
+ Cancel
155
+ </button>
156
+ </div>
157
+ </div>
158
+ {:else}
159
+ <div class="flex items-center justify-between gap-3">
160
+ <div class="text-sm text-slate-900 dark:text-slate-100">
161
+ {userStore.currentUser?.name || 'Not set'}
162
+ </div>
163
+ <button
164
+ type="button"
165
+ class="flex items-center gap-1.5 py-2 px-3.5 bg-transparent border border-violet-500/20 dark:border-violet-500/30 rounded-lg text-sm font-semibold text-violet-600 dark:text-violet-400 cursor-pointer transition-all duration-150 hover:bg-violet-500/10"
166
+ onclick={startEdit}
167
+ >
168
+ <Icon name="lucide:pencil" class="w-4 h-4" />
169
+ Edit
170
+ </button>
171
+ </div>
172
+ {/if}
173
+ </div>
174
+
175
+ <!-- User ID -->
176
+ <div
177
+ class="p-4 bg-slate-100/80 dark:bg-slate-800/80 border border-slate-200 dark:border-slate-800 rounded-xl"
178
+ >
179
+ <div
180
+ class="flex items-center gap-2 text-sm font-semibold text-slate-500 mb-3"
181
+ >
182
+ <Icon name="lucide:fingerprint" class="w-4 h-4 opacity-70" />
183
+ <span>User ID</span>
184
+ </div>
185
+ <div class="flex flex-col gap-1.5">
186
+ <code
187
+ class="py-2.5 px-3.5 bg-slate-50 dark:bg-slate-900/80 border border-slate-200 dark:border-slate-800 rounded-lg font-mono text-xs text-slate-500 break-all"
188
+ >{userStore.currentUser?.id || 'Not available'}</code
189
+ >
190
+ <span class="text-xs text-slate-600 dark:text-slate-500"
191
+ >Unique identifier for this session</span
192
+ >
193
+ </div>
194
+ </div>
195
+ </div>
196
+ {/if}
197
+ </div>
@@ -0,0 +1,368 @@
1
+ <!--
2
+ Pure XTerminal Integration - Clean Terminal Component
3
+ All terminal functionality handled by xterm.js
4
+ -->
5
+ <script lang="ts">
6
+ import { terminalStore } from '$frontend/lib/stores/features/terminal.svelte';
7
+ import { projectState } from '$frontend/lib/stores/core/projects.svelte';
8
+ import { getShortcutLabels } from '$frontend/lib/utils/platform';
9
+ import TerminalTabs from './TerminalTabs.svelte';
10
+ import LoadingSpinner from '../common/LoadingSpinner.svelte';
11
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
12
+ import XTerm from '$frontend/lib/components/common/xterm/XTerm.svelte';
13
+
14
+ // Project-aware state
15
+ const hasActiveProject = $derived(projectState.currentProject !== null);
16
+ const projectPath = $derived(projectState.currentProject?.path || '');
17
+ const projectId = $derived(projectState.currentProject?.id || '');
18
+
19
+ // Terminal state from store
20
+ const activeSession = $derived(terminalStore.activeSession);
21
+ // Use session-specific execution state instead of global
22
+ const isExecuting = $derived(activeSession ? terminalStore.isSessionExecuting(activeSession.id) : false);
23
+
24
+ // XTerminal reference and cancellation state
25
+ let xterminalRef = $state<{ scrollToBottom: () => void; clear: () => void; writeData: (data: string) => void }>();
26
+ let isCancelling = $state(false);
27
+ let terminalContainer: HTMLDivElement | undefined = $state();
28
+
29
+ // Get platform-specific shortcut labels
30
+ const shortcuts = $derived(getShortcutLabels());
31
+
32
+ // Initialize terminal only once when component mounts
33
+ let isInitialized = false;
34
+ $effect(() => {
35
+ if (hasActiveProject && !isInitialized) {
36
+ // Wait for background service to complete restoration
37
+ const checkAndInitialize = async () => {
38
+ if (typeof window !== 'undefined') {
39
+ try {
40
+ const { backgroundTerminalService } = await import('$frontend/lib/services/terminal/background');
41
+ const { terminalProjectManager } = await import('$frontend/lib/services/terminal');
42
+
43
+ // Initialize terminalProjectManager first
44
+ terminalProjectManager.initialize();
45
+
46
+ // First ensure background service is initialized
47
+ await backgroundTerminalService.initialize();
48
+
49
+ // Wait for restoration to complete (max 2 seconds)
50
+ let attempts = 0;
51
+ while (!backgroundTerminalService.isRestorationDone() && attempts < 20) {
52
+ await new Promise(resolve => setTimeout(resolve, 100));
53
+ attempts++;
54
+ }
55
+
56
+ // Now safe to initialize terminal store
57
+ terminalStore.initialize(hasActiveProject, projectPath);
58
+
59
+ // Ensure project context is set up in terminalProjectManager
60
+ if (projectId && projectPath) {
61
+ await terminalProjectManager.switchToProject(projectId, projectPath);
62
+ }
63
+
64
+ isInitialized = true;
65
+ } catch (error) {
66
+ // Failed to initialize terminal services
67
+ // If background service fails, just initialize normally
68
+ terminalStore.initialize(hasActiveProject, projectPath);
69
+ isInitialized = true;
70
+ }
71
+ }
72
+ };
73
+
74
+ checkAndInitialize();
75
+ }
76
+ });
77
+
78
+ // Setup event listeners for Ctrl+C handling
79
+ $effect(() => {
80
+ if (typeof window !== 'undefined') {
81
+ // Use capture phase to intercept Ctrl+C before XTerm
82
+ const handleGlobalKeyDown = (e: KeyboardEvent) => {
83
+ // Check if terminal is focused
84
+ const terminalElement = document.querySelector('.xterm');
85
+ const isTerminalFocused = terminalElement && terminalElement.contains(document.activeElement);
86
+
87
+ if (isTerminalFocused && e.ctrlKey && e.key === 'c' && isExecuting && !isCancelling) {
88
+ e.preventDefault();
89
+ e.stopPropagation();
90
+ e.stopImmediatePropagation();
91
+ // Global Ctrl+C intercepted during execution
92
+ handleCancel();
93
+ }
94
+ };
95
+
96
+ // Add listener with capture to intercept before bubbling
97
+ document.addEventListener('keydown', handleGlobalKeyDown, true);
98
+
99
+ // Also listen for custom event from XTerm as backup
100
+ const handleTerminalCancel = (event: Event) => {
101
+ const customEvent = event as CustomEvent;
102
+ // Received terminal-cancel event
103
+ if (isExecuting && !isCancelling) {
104
+ handleCancel();
105
+ }
106
+ };
107
+
108
+ if (terminalContainer) {
109
+ terminalContainer.addEventListener('terminal-cancel', handleTerminalCancel);
110
+ }
111
+
112
+ return () => {
113
+ document.removeEventListener('keydown', handleGlobalKeyDown, true);
114
+ terminalContainer?.removeEventListener('terminal-cancel', handleTerminalCancel);
115
+ };
116
+ }
117
+ });
118
+
119
+ // Connect to PTY session (initial auto-connect)
120
+ async function connectToPtySession(command: string, terminalSize?: { cols: number; rows: number }) {
121
+ if (!hasActiveProject || !projectId) {
122
+ return;
123
+ }
124
+
125
+ // Only connect, ignore command parameter (kept for XTerm compatibility)
126
+ await terminalStore.connectToSession(hasActiveProject ? projectPath : undefined, projectId, terminalSize);
127
+ }
128
+
129
+ // Handle keyboard shortcuts
130
+ function handleKeyDown(e: KeyboardEvent) {
131
+ // Handle Ctrl+C to cancel running command
132
+ if (e.ctrlKey && e.key === 'c' && isExecuting) {
133
+ // Ctrl+C pressed - cancelling command
134
+ handleCancel();
135
+ }
136
+ }
137
+
138
+ // Send Ctrl+C signal to terminal
139
+ // This function always sends the interrupt signal regardless of execution state
140
+ // It's useful as a utility shortcut, especially for mobile accessibility
141
+ async function handleCancel() {
142
+ // UI: Cancel button clicked - sending Ctrl+C signal
143
+ isCancelling = true;
144
+
145
+ if (!activeSession) {
146
+ // No active session to send signal
147
+ isCancelling = false;
148
+ return;
149
+ }
150
+
151
+ try {
152
+ // Always attempt to send cancel signal to the active session
153
+ // This sends Ctrl+C (\x03) directly to the PTY process
154
+ await terminalStore.cancelCommand();
155
+ // Ctrl+C signal sent to terminal
156
+ } catch (error) {
157
+ // Error sending Ctrl+C signal
158
+ } finally {
159
+ isCancelling = false;
160
+ }
161
+ }
162
+
163
+ // Clear terminal session
164
+ function handleClear() {
165
+ if (activeSession) {
166
+ // Clear the terminal store session
167
+ terminalStore.clearSession(activeSession.id);
168
+
169
+ // Also immediately clear the XTerm display
170
+ if (xterminalRef) {
171
+ xterminalRef.clear();
172
+ }
173
+ }
174
+ }
175
+
176
+ // Create new terminal session
177
+ async function handleCloseSession(sessionId: string) {
178
+ // Check if this is the last session before closing
179
+ const isLastSession = terminalStore.sessions.length <= 1;
180
+
181
+ // Close the session
182
+ const closed = await terminalStore.closeSession(sessionId);
183
+
184
+ // If it was the last session and it was closed successfully, create a new one
185
+ if (closed && isLastSession) {
186
+ await handleNewSession();
187
+ }
188
+ }
189
+
190
+ async function handleNewSession() {
191
+ if (!hasActiveProject || !projectId || !projectPath) {
192
+ // Cannot create new terminal: missing project information
193
+ return;
194
+ }
195
+
196
+ // Use terminalProjectManager to create new session for current project
197
+ try {
198
+ const { terminalProjectManager } = await import('$frontend/lib/services/terminal');
199
+
200
+ // Pass project ID and path directly to ensure it works even if currentProjectId isn't set
201
+ const newSessionId = terminalProjectManager.addTerminalToCurrentProject(projectId, projectPath);
202
+
203
+ if (!newSessionId) {
204
+ // If it still fails, try switching to project first then adding
205
+ // Failed to add terminal, switching to project first
206
+ await terminalProjectManager.switchToProject(projectId, projectPath);
207
+ const retrySessionId = terminalProjectManager.addTerminalToCurrentProject(projectId, projectPath);
208
+
209
+ if (!retrySessionId) {
210
+ // Final fallback
211
+ // Using fallback terminal creation
212
+ terminalStore.createNewSession(projectPath, projectPath, projectId);
213
+ }
214
+ }
215
+ } catch (error) {
216
+ // Failed to create new terminal session
217
+ // Fallback to direct creation with projectId for proper isolation
218
+ terminalStore.createNewSession(projectPath, projectPath, projectId);
219
+ }
220
+ }
221
+
222
+ // Format directory path for display in header
223
+ function formatDirectory(dir: string): string {
224
+ if (!dir || typeof dir !== 'string') return '~';
225
+
226
+ // Convert backslashes to forward slashes for consistent display
227
+ const normalizedDir = dir.replace(/\\/g, '/');
228
+
229
+ // For long paths, shorten them
230
+ const maxLength = 50;
231
+ if (normalizedDir.length > maxLength) {
232
+ const parts = normalizedDir.split('/');
233
+ if (parts.length > 3) {
234
+ return parts[0] + '/.../' + parts.slice(-1).join('/');
235
+ }
236
+ }
237
+
238
+ return normalizedDir;
239
+ }
240
+
241
+ // Export actions and state for parent components
242
+ export const terminalActions = {
243
+ handleClear,
244
+ handleNewSession,
245
+ handleCancel,
246
+ getSessions: () => terminalStore.sessions,
247
+ getActiveSession: () => activeSession,
248
+ isExecuting: () => isExecuting,
249
+ isCancelling: () => isCancelling
250
+ };
251
+ </script>
252
+
253
+ <!-- Terminal container -->
254
+ <div class="h-full bg-slate-50 dark:bg-slate-950 flex flex-col overflow-hidden min-h-0"
255
+ onkeydown={handleKeyDown}
256
+ tabindex="-1"
257
+ role="application"
258
+ aria-label="Terminal application">
259
+
260
+ <!-- Terminal Header with Tabs -->
261
+ <div class="flex-shrink-0 px-3 py-2.5 bg-slate-100 dark:bg-slate-900 border-b border-slate-200 dark:border-slate-700">
262
+ <!-- Terminal Tabs -->
263
+ <TerminalTabs
264
+ sessions={terminalStore.sessions}
265
+ activeSessionId={terminalStore.activeSessionId}
266
+ onSwitchSession={(sessionId) => terminalStore.switchToSession(sessionId)}
267
+ onCloseSession={handleCloseSession}
268
+ onNewSession={handleNewSession}
269
+ />
270
+ </div>
271
+
272
+ <!-- Pure XTerminal integration -->
273
+ {#if activeSession}
274
+ <div class="flex-1 relative min-h-0 overflow-hidden font-mono" bind:this={terminalContainer}>
275
+ <XTerm
276
+ bind:this={xterminalRef}
277
+ session={activeSession}
278
+ hasActiveProject={hasActiveProject}
279
+ projectPath={projectPath}
280
+ isExecuting={isExecuting}
281
+ onExecuteCommand={connectToPtySession}
282
+ onClearSession={() => handleClear()}
283
+ class="absolute inset-0"
284
+ />
285
+ </div>
286
+ {:else}
287
+ <!-- No active session -->
288
+ <div class="flex-1 flex items-center justify-center font-mono">
289
+ <div class="text-center text-slate-600 dark:text-slate-400">
290
+ <p>No terminal sessions available</p>
291
+ <button
292
+ class="mt-2 px-4 py-2 bg-violet-600 hover:bg-violet-700 text-white rounded-md transition-colors"
293
+ onclick={handleNewSession}
294
+ >
295
+ Create New Terminal
296
+ </button>
297
+ </div>
298
+ </div>
299
+ {/if}
300
+
301
+ <!-- Terminal status bar -->
302
+ <div class="flex-shrink-0 px-2 py-0.5 bg-slate-100 dark:bg-slate-900 border-t border-slate-200 dark:border-slate-800 text-3xs text-slate-500 dark:text-slate-500 font-mono">
303
+ <div class="flex items-center justify-between">
304
+ <div class="flex items-center space-x-3">
305
+ <span class="hidden sm:inline"><kbd class="px-1 py-0.5 bg-slate-200 dark:bg-slate-800 border border-slate-300 dark:border-slate-700 rounded text-3xs text-slate-700 dark:text-slate-300">↑↓</kbd> History</span>
306
+ <span class="hidden md:inline"><kbd class="px-1 py-0.5 bg-slate-200 dark:bg-slate-800 border border-slate-300 dark:border-slate-700 rounded text-3xs text-slate-700 dark:text-slate-300">Ctrl+L</kbd> Clear</span>
307
+ <span class="hidden sm:inline"><kbd class="px-1 py-0.5 bg-slate-200 dark:bg-slate-800 border border-slate-300 dark:border-slate-700 rounded text-3xs text-slate-700 dark:text-slate-300">{shortcuts.cancel}</kbd> Interrupt
308
+ {#if isCancelling}
309
+ <span class="animate-pulse ml-1">(cancelling...)</span>
310
+ {/if}
311
+ </span>
312
+ </div>
313
+ <div class="flex items-center space-x-1.5">
314
+ {#if hasActiveProject}
315
+ <span class="text-emerald-500 text-xs">●</span>
316
+ <span class="hidden sm:inline">Ready</span>
317
+ {:else}
318
+ <span class="text-amber-500 text-xs">●</span>
319
+ <span class="hidden sm:inline">No Project</span>
320
+ {/if}
321
+ </div>
322
+ </div>
323
+ </div>
324
+ </div>
325
+
326
+ <style>
327
+ /* Terminal cursor animation */
328
+ @keyframes terminal-cursor {
329
+ 0%, 50% { background-color: #22c55e; }
330
+ 51%, 100% { background-color: transparent; }
331
+ }
332
+
333
+ :global(.animate-terminal-cursor) {
334
+ animation: terminal-cursor 1s infinite;
335
+ }
336
+
337
+ /* Terminal scrollbar styling */
338
+ :global(.terminal-scrollbar) {
339
+ scrollbar-width: thin;
340
+ scrollbar-color: rgb(148 163 184 / 0.3) transparent;
341
+ }
342
+
343
+ :global(.terminal-scrollbar::-webkit-scrollbar) {
344
+ width: 6px;
345
+ }
346
+
347
+ :global(.terminal-scrollbar::-webkit-scrollbar-track) {
348
+ background: transparent;
349
+ }
350
+
351
+ :global(.terminal-scrollbar::-webkit-scrollbar-thumb) {
352
+ background-color: rgb(148 163 184 / 0.3);
353
+ border-radius: 3px;
354
+ transition: background-color 0.2s ease;
355
+ }
356
+
357
+ :global(.dark .terminal-scrollbar::-webkit-scrollbar-thumb) {
358
+ background-color: rgb(71 85 105 / 0.3);
359
+ }
360
+
361
+ :global(.terminal-scrollbar::-webkit-scrollbar-thumb:hover) {
362
+ background-color: rgb(148 163 184 / 0.6);
363
+ }
364
+
365
+ :global(.dark .terminal-scrollbar::-webkit-scrollbar-thumb:hover) {
366
+ background-color: rgb(71 85 105 / 0.6);
367
+ }
368
+ </style>
@@ -0,0 +1,87 @@
1
+ <!--
2
+ Terminal Tabs Component
3
+ Manages terminal session tabs with close and new tab functionality
4
+ -->
5
+ <script lang="ts">
6
+ import type { TerminalSession } from '$shared/types/terminal';
7
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
8
+
9
+ const {
10
+ sessions = [],
11
+ activeSessionId,
12
+ onSwitchSession,
13
+ onCloseSession,
14
+ onNewSession
15
+ }: {
16
+ sessions: TerminalSession[];
17
+ activeSessionId: string | null;
18
+ onSwitchSession?: (sessionId: string) => void;
19
+ onCloseSession?: (sessionId: string) => void;
20
+ onNewSession?: () => void;
21
+ } = $props();
22
+
23
+ // Check for duplicate sessions (for debugging)
24
+ $effect(() => {
25
+ const ids = sessions.map(s => s.id);
26
+ const uniqueIds = new Set(ids);
27
+ if (ids.length !== uniqueIds.size) {
28
+ // Duplicate session IDs detected in TerminalTabs
29
+ // Sessions:
30
+ }
31
+ });
32
+ </script>
33
+
34
+ <!-- Compact Terminal Tabs -->
35
+ <div class="flex items-center gap-1.5 overflow-x-auto flex-1">
36
+ {#each sessions as session (session.id)}
37
+ <div
38
+ class="group relative flex items-center gap-2 pl-3 pr-2 py-1.5 border border-slate-200 dark:border-slate-700 rounded-lg transition-all duration-200 min-w-0 max-w-xs cursor-pointer
39
+ {session.isActive
40
+ ? 'bg-slate-100 dark:bg-slate-700 text-slate-900 dark:text-slate-100'
41
+ : 'bg-white dark:bg-slate-800 text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-700'}"
42
+ onclick={() => onSwitchSession?.(session.id)}
43
+ role="tab"
44
+ tabindex="0"
45
+ onkeydown={(e) => {
46
+ if (e.key === 'Enter' || e.key === ' ') {
47
+ e.preventDefault();
48
+ onSwitchSession?.(session.id);
49
+ }
50
+ }}
51
+ >
52
+ <!-- Terminal icon -->
53
+ <Icon name="lucide:terminal" class="w-3 h-3 flex-shrink-0" />
54
+
55
+ <!-- Session name -->
56
+ <span class="text-xs font-medium truncate max-w-37.5">
57
+ {session.name}
58
+ </span>
59
+
60
+ <!-- Close button -->
61
+ <button
62
+ onclick={(e) => {
63
+ e.stopPropagation();
64
+ onCloseSession?.(session.id);
65
+ }}
66
+ class="flex hover:bg-slate-300 dark:hover:bg-slate-600 rounded p-0.5 transition-all duration-200 flex-shrink-0"
67
+ title="Close terminal"
68
+ aria-label="Close terminal session"
69
+ >
70
+ <Icon name="lucide:x" class="w-3 h-3" />
71
+ </button>
72
+ </div>
73
+ {/each}
74
+
75
+ <!-- New terminal button -->
76
+ {#if onNewSession}
77
+ <button
78
+ onclick={onNewSession}
79
+ class="flex items-center justify-center w-5 h-5 rounded-md hover:bg-slate-200 dark:hover:bg-slate-700 transition-all duration-200 flex-shrink-0"
80
+ title="New terminal"
81
+ aria-label="New terminal session"
82
+ >
83
+ <Icon name="lucide:plus" class="w-3 h-3" />
84
+ </button>
85
+ {/if}
86
+ </div>
87
+