@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,796 @@
1
+ <!--
2
+ Modular XTerm Component - xterm.js Integration
3
+ Cleaner, more maintainable implementation using XTermService
4
+ -->
5
+ <script lang="ts">
6
+ import { onMount, onDestroy } from 'svelte';
7
+ import { browser } from '$frontend/lib/app-environment';
8
+ import type { TerminalSession } from '$shared/types/terminal';
9
+ import { XTermService } from './xterm-service';
10
+ import type { XTermProps, XTermMethods } from './types';
11
+ import { terminalStore } from '$frontend/lib/stores/features/terminal.svelte';
12
+ import { backgroundTerminalService } from '$frontend/lib/services/terminal/background';
13
+ import { terminalService } from '$frontend/lib/services/terminal';
14
+
15
+ // Import CSS directly - Vite will handle it properly
16
+ import 'xterm/css/xterm.css';
17
+
18
+ // Props
19
+ const {
20
+ session,
21
+ class: className = '',
22
+ hasActiveProject = false,
23
+ projectPath = '',
24
+ isExecuting = false,
25
+ onExecuteCommand,
26
+ onClearSession
27
+ }: XTermProps = $props();
28
+
29
+ // Track local execution state for this session
30
+ let sessionExecuting = $state(false);
31
+ let lastExecutingState = $state(false);
32
+
33
+ // Local state
34
+ let terminalContainer = $state<HTMLDivElement>();
35
+ const xtermService = new XTermService();
36
+ let lastProcessedIndex = $state(-1);
37
+ let isInitialized = $state(false); // Reactive state for initialization
38
+ const initialPromptShown = $state(false); // Track if initial prompt has been shown
39
+ let isTerminalReady = $state(false); // Track if terminal is ready for input
40
+ let isAfterClearOperation = $state(false); // Track if session was manually cleared
41
+ let lastSessionId = $state(''); // Track last session to detect changes
42
+ let promptShownForSession = $state(false); // Track if prompt shown for current session
43
+ let justRestoredFromBuffer = $state(false); // Track if we just restored from buffer
44
+ let isInitialMount = $state(true); // Track if this is the initial mount after restore
45
+ const keyboardEventHandler: ((e: KeyboardEvent) => void) | null = null; // Store event handler reference
46
+
47
+ // Track which sessions have connected to PTY in this browser session (after refresh)
48
+ // This prevents reconnection issues after browser refresh
49
+ const connectedSessions = new Set<string>();
50
+
51
+ // Initialize terminal
52
+ async function initializeTerminal() {
53
+ if (!terminalContainer) {
54
+ return;
55
+ }
56
+
57
+ await xtermService.initialize(terminalContainer);
58
+
59
+ if (xtermService.isInitialized && session) {
60
+ // CRITICAL: Mark session as connected BEFORE setting isInitialized
61
+ // This prevents the session change $effect from triggering a duplicate connection
62
+ // because $effect checks connectedSessions.has(session.id)
63
+ if (hasActiveProject && onExecuteCommand) {
64
+ connectedSessions.add(session.id);
65
+ }
66
+
67
+ // Update reactive state AFTER marking connected
68
+ isInitialized = xtermService.isInitialized;
69
+
70
+ // Setup input handling - forwards all keystrokes to PTY
71
+ xtermService.setupInput(
72
+ session.id,
73
+ onExecuteCommand,
74
+ onClearSession,
75
+ hasActiveProject,
76
+ projectPath,
77
+ session?.directory
78
+ );
79
+
80
+ // Focus terminal for immediate input
81
+ xtermService.terminal?.focus();
82
+
83
+ // Process existing session data first - but ONLY if not initial mount
84
+ // On initial mount after restore, lines will be rendered by restoreSessionBuffer
85
+ if (session && !isInitialMount) {
86
+ processSessionLines();
87
+ }
88
+
89
+ // Mark terminal as ready immediately - shell handles prompts
90
+ isTerminalReady = true;
91
+
92
+ // CRITICAL: Auto-connect to PTY session right after initialization
93
+ // This opens the SSE stream to persistent PTY
94
+ if (hasActiveProject && onExecuteCommand && session) {
95
+ // IMPORTANT: Get actual terminal dimensions and pass to PTY
96
+ // This ensures PTY is created with correct size to prevent cursor positioning issues
97
+ let terminalSize: { cols: number; rows: number } | undefined;
98
+ if (xtermService.fitAddon && xtermService.terminal) {
99
+ // First fit to get proper dimensions
100
+ xtermService.fitAddon.fit();
101
+ const dims = xtermService.fitAddon.proposeDimensions();
102
+ if (dims) {
103
+ terminalSize = { cols: dims.cols, rows: dims.rows };
104
+ }
105
+ }
106
+
107
+ onExecuteCommand('', terminalSize).catch(() => {
108
+ // Silently handle connection errors
109
+ });
110
+ }
111
+ } else {
112
+ // Update reactive state even if not fully initialized
113
+ isInitialized = xtermService.isInitialized;
114
+ }
115
+ }
116
+
117
+ // Process session lines and render to terminal
118
+ function processSessionLines() {
119
+ if (!isInitialized || !session) return;
120
+
121
+ const lines = session.lines;
122
+ const startIndex = Math.max(0, lastProcessedIndex + 1);
123
+ const totalLines = sessionLinesLength;
124
+
125
+
126
+ // Process new lines since last update
127
+ for (let i = startIndex; i < totalLines; i++) {
128
+ const line = lines[i];
129
+
130
+ // Track when command starts (input line detected)
131
+ if (line.type === 'input') {
132
+ sessionExecuting = true;
133
+ }
134
+
135
+ // Track when command finishes (prompt-trigger detected)
136
+ if (line.type === 'prompt-trigger') {
137
+ // Only set to false if we were executing
138
+ if (sessionExecuting) {
139
+ sessionExecuting = false;
140
+ // IMPORTANT: Also update xterm service execution state
141
+ // This ensures prompt can be displayed properly
142
+ xtermService.setExecuting(false);
143
+ }
144
+ }
145
+
146
+ xtermService.renderLine(line);
147
+ }
148
+
149
+ lastProcessedIndex = totalLines - 1;
150
+
151
+ // Auto scroll to bottom only if user is near the bottom
152
+ // This prevents disrupting user's reading when they scroll up
153
+ requestAnimationFrame(() => {
154
+ xtermService.scrollToBottomIfNearEnd();
155
+ });
156
+ }
157
+
158
+ // Handle resize events
159
+ function setupResizeHandling() {
160
+ const handleResize = () => {
161
+ setTimeout(() => {
162
+ xtermService.fit(session?.id);
163
+ }, 100);
164
+ };
165
+
166
+ window.addEventListener('resize', handleResize);
167
+
168
+ // Also fit when container size might change
169
+ const resizeObserver = new ResizeObserver(() => {
170
+ handleResize();
171
+ });
172
+
173
+ if (terminalContainer) {
174
+ resizeObserver.observe(terminalContainer);
175
+ }
176
+
177
+ return () => {
178
+ window.removeEventListener('resize', handleResize);
179
+ resizeObserver.disconnect();
180
+ };
181
+ }
182
+
183
+ // Handle right-click copy/paste functionality
184
+ function setupRightClickCopy() {
185
+ if (!terminalContainer || !xtermService.terminal) return;
186
+
187
+ const handleRightClick = async (event: MouseEvent) => {
188
+ event.preventDefault(); // Prevent default context menu
189
+
190
+ // Get selected text from xterm.js
191
+ const selectedText = xtermService.getSelectedText();
192
+
193
+ if (selectedText && selectedText.trim()) {
194
+ // Copy selected text to clipboard
195
+ try {
196
+ await navigator.clipboard.writeText(selectedText);
197
+
198
+ // Clear selection after copy (like most terminals do)
199
+ xtermService.clearSelection();
200
+
201
+ // Show brief visual feedback
202
+ showCopyFeedback();
203
+ } catch (err) {
204
+ }
205
+ } else {
206
+ // No text selected - paste from clipboard instead
207
+ try {
208
+ const clipboardText = await navigator.clipboard.readText();
209
+ if (clipboardText && clipboardText.trim() && xtermService.isReady) {
210
+
211
+ // Use terminal's built-in paste functionality
212
+ // This simulates typing each character through the input handler
213
+ if ((xtermService as any).inputHandler) {
214
+ // Process each character through the input handler
215
+ for (const char of clipboardText) {
216
+ // Skip newlines - let user decide when to execute
217
+ if (char !== '\n' && char !== '\r') {
218
+ (xtermService as any).inputHandler(char);
219
+ }
220
+ }
221
+ }
222
+
223
+ // Show brief visual feedback
224
+ showPasteFeedback();
225
+ }
226
+ } catch {
227
+ // paste not supported
228
+ }
229
+ }
230
+ };
231
+
232
+ // Add right-click event listener to terminal container
233
+ terminalContainer.addEventListener('contextmenu', handleRightClick);
234
+
235
+ return () => {
236
+ terminalContainer?.removeEventListener('contextmenu', handleRightClick);
237
+ };
238
+ }
239
+
240
+ // Show brief visual feedback for copy action
241
+ function showCopyFeedback() {
242
+ if (!terminalContainer) return;
243
+
244
+ // Create temporary feedback element
245
+ const feedback = document.createElement('div');
246
+ feedback.textContent = 'Copied!';
247
+ feedback.style.cssText = `
248
+ position: absolute;
249
+ top: 10px;
250
+ right: 10px;
251
+ background: rgb(34 197 94 / 0.9);
252
+ color: white;
253
+ padding: 4px 8px;
254
+ border-radius: 4px;
255
+ font-size: 12px;
256
+ font-family: system-ui, sans-serif;
257
+ z-index: 1000;
258
+ pointer-events: none;
259
+ `;
260
+
261
+ terminalContainer.style.position = 'relative';
262
+ terminalContainer.appendChild(feedback);
263
+
264
+ // Remove feedback after 1 second
265
+ setTimeout(() => {
266
+ if (feedback.parentNode) {
267
+ feedback.parentNode.removeChild(feedback);
268
+ }
269
+ }, 1000);
270
+ }
271
+
272
+ // Show brief visual feedback for paste action
273
+ function showPasteFeedback() {
274
+ if (!terminalContainer) return;
275
+
276
+ // Create temporary feedback element
277
+ const feedback = document.createElement('div');
278
+ feedback.textContent = 'Pasted!';
279
+ feedback.style.cssText = `
280
+ position: absolute;
281
+ top: 10px;
282
+ right: 10px;
283
+ background: rgba(59, 130, 246, 0.9);
284
+ color: white;
285
+ padding: 4px 8px;
286
+ border-radius: 4px;
287
+ font-size: 12px;
288
+ font-family: system-ui, sans-serif;
289
+ z-index: 1000;
290
+ pointer-events: none;
291
+ `;
292
+
293
+ terminalContainer.style.position = 'relative';
294
+ terminalContainer.appendChild(feedback);
295
+
296
+ // Remove feedback after 1 second
297
+ setTimeout(() => {
298
+ if (feedback.parentNode) {
299
+ feedback.parentNode.removeChild(feedback);
300
+ }
301
+ }, 1000);
302
+ }
303
+
304
+ // Handle theme changes
305
+ function setupThemeHandling() {
306
+ xtermService.updateTheme();
307
+
308
+ // Watch for theme changes
309
+ const observer = new MutationObserver(() => {
310
+ xtermService.updateTheme();
311
+ });
312
+
313
+ observer.observe(document.documentElement, {
314
+ attributes: true,
315
+ attributeFilter: ['class', 'data-theme']
316
+ });
317
+
318
+ return () => {
319
+ observer.disconnect();
320
+ };
321
+ }
322
+
323
+ // Reactive effects
324
+ $effect(() => {
325
+ // Initialize terminal when container is available
326
+ if (terminalContainer && !xtermService.isInitialized) {
327
+ // Small delay to ensure DOM is ready
328
+ setTimeout(() => {
329
+ initializeTerminal();
330
+ }, 10);
331
+ }
332
+ });
333
+
334
+ // Use $derived.by to properly track session.lines changes in Svelte 5
335
+ const sessionLinesLength = $derived.by(() => {
336
+ const length = session ? session.lines.length : 0;
337
+ return length;
338
+ });
339
+
340
+ $effect(() => {
341
+ // Process new session data when lines length changes
342
+ // Skip if we just restored from buffer to avoid duplicates
343
+ // Also skip if this is a session switch (lastSessionId !== session.id)
344
+ if (isInitialized && session && !justRestoredFromBuffer && lastSessionId === session.id) {
345
+ // Check if this is a clear operation (lines reduced and contains clear-screen marker)
346
+ const hasClearMarker = session.lines.length > 0 &&
347
+ session.lines[session.lines.length - 1].type === 'clear-screen';
348
+
349
+ if (hasClearMarker && sessionLinesLength < lastProcessedIndex) {
350
+ // This is a clear operation - handle it immediately
351
+ xtermService.clear();
352
+ lastProcessedIndex = -1;
353
+
354
+ // Process the clear marker
355
+ processSessionLines();
356
+ } else if (sessionLinesLength > lastProcessedIndex) {
357
+ // Normal case: process NEW lines
358
+ processSessionLines();
359
+ }
360
+ }
361
+ });
362
+
363
+ // Track session execution state changes
364
+ $effect(() => {
365
+ // Check if execution state changed from true to false (command finished)
366
+ // Also verify that terminal store agrees that execution is done
367
+ if (session && lastExecutingState && !sessionExecuting && !isExecuting) {
368
+ // Command just finished for this session
369
+
370
+ // Trigger prompt display for this session regardless of whether it's active
371
+ if (isInitialized && hasActiveProject) {
372
+ setTimeout(() => {
373
+ // Check if there's still an active stream before showing prompt
374
+ // This prevents race condition after refresh where execution state changes
375
+ // but stream is still being reconnected
376
+ if (!backgroundTerminalService.hasActiveStream(session.id)) {
377
+ xtermService.showPrompt();
378
+ isTerminalReady = true;
379
+ }
380
+ }, 200);
381
+ }
382
+ }
383
+
384
+ // Update last state for next comparison
385
+ lastExecutingState = sessionExecuting;
386
+ });
387
+
388
+ // Save terminal buffer before switching away
389
+ function saveCurrentBuffer(sessionId: string) {
390
+ // No longer saving buffer since we re-render from session lines
391
+ // This preserves ANSI colors when switching tabs
392
+ }
393
+
394
+ // Restore terminal buffer when switching to a session
395
+ function restoreSessionBuffer(sessionId: string) {
396
+ if (!isInitialized || !sessionId) return;
397
+
398
+
399
+ // ALWAYS re-render from session lines to preserve ANSI colors
400
+ // This ensures colors are preserved when switching tabs
401
+
402
+ // Clear terminal first
403
+ xtermService.clear();
404
+
405
+ // Don't set the flag here - it's already set in the session change handler
406
+ // justRestoredFromBuffer = true;
407
+
408
+ // Reset processed index to re-render all lines
409
+ lastProcessedIndex = -1;
410
+
411
+ // Re-render all lines from the session (with ANSI colors intact)
412
+ if (session && session.lines.length > 0) {
413
+ // Check if the session was cleared (has clear-screen marker)
414
+ const hasClearMarker = session.lines.some(line => line.type === 'clear-screen');
415
+
416
+ if (hasClearMarker) {
417
+ // Find the last clear-screen marker
418
+ let lastClearIndex = -1;
419
+ for (let i = session.lines.length - 1; i >= 0; i--) {
420
+ if (session.lines[i].type === 'clear-screen') {
421
+ lastClearIndex = i;
422
+ break;
423
+ }
424
+ }
425
+
426
+ // Only render lines after the last clear
427
+ // This ensures the terminal stays clear when switching tabs
428
+ for (let i = lastClearIndex; i < session.lines.length; i++) {
429
+ const line = session.lines[i];
430
+
431
+ // Track execution state while re-rendering
432
+ if (line.type === 'input') {
433
+ sessionExecuting = true;
434
+ }
435
+ if (line.type === 'prompt-trigger') {
436
+ sessionExecuting = false;
437
+ // Update xterm service execution state for proper prompt display
438
+ xtermService.setExecuting(false);
439
+ }
440
+
441
+ // Render line with original ANSI codes preserved
442
+ // Pass isRestoring=true to properly handle input lines
443
+ xtermService.renderLine(line, true);
444
+ }
445
+
446
+ // After restoring, reset execution state for proper prompt display
447
+ sessionExecuting = false;
448
+ xtermService.setExecuting(false);
449
+ } else {
450
+ // No clear marker - render all lines as before
451
+ for (let i = 0; i < session.lines.length; i++) {
452
+ const line = session.lines[i];
453
+
454
+ // Track execution state while re-rendering
455
+ if (line.type === 'input') {
456
+ sessionExecuting = true;
457
+ }
458
+ if (line.type === 'prompt-trigger') {
459
+ sessionExecuting = false;
460
+ // Update xterm service execution state for proper prompt display
461
+ xtermService.setExecuting(false);
462
+ }
463
+
464
+ // Render line with original ANSI codes preserved
465
+ // Pass isRestoring=true to properly handle input lines
466
+ xtermService.renderLine(line, true);
467
+ }
468
+ }
469
+
470
+ // After restoring all lines, ensure execution state is correct
471
+ // If the last line wasn't a prompt-trigger, reset execution state
472
+ // This ensures directory path is always shown for input lines during restore
473
+ sessionExecuting = false;
474
+ xtermService.setExecuting(false);
475
+
476
+ // Update last processed index to current length
477
+ lastProcessedIndex = session.lines.length - 1;
478
+
479
+ // Only scroll to bottom if there's content after clear
480
+ // If it was just cleared, keep at top
481
+ if (!hasClearMarker || session.lines.length > 1) {
482
+ requestAnimationFrame(() => {
483
+ xtermService.scrollToBottom();
484
+ });
485
+ }
486
+ }
487
+
488
+ // Reset flag after rendering is complete
489
+ // Use longer timeout to ensure effect doesn't trigger during restoration
490
+ setTimeout(() => {
491
+ justRestoredFromBuffer = false;
492
+ }, 200);
493
+ }
494
+
495
+ $effect(() => {
496
+ // Handle session changes
497
+ if (isInitialized && session) {
498
+ // Check if session ID changed
499
+ if (lastSessionId !== session.id) {
500
+ // Save buffer for previous session before switching
501
+ if (lastSessionId) {
502
+ saveCurrentBuffer(lastSessionId);
503
+ }
504
+
505
+ // Set flag BEFORE updating lastSessionId to prevent double processing
506
+ justRestoredFromBuffer = true;
507
+
508
+ lastSessionId = session.id;
509
+ promptShownForSession = false; // Reset prompt flag for new session
510
+ isAfterClearOperation = false;
511
+ sessionExecuting = false; // Reset execution state for new session
512
+ lastExecutingState = false; // Reset last state tracking
513
+
514
+ // Update command history for the new session
515
+ xtermService.updateCommandHistory(session.commandHistory);
516
+
517
+ // IMPORTANT: Update context with new session directory BEFORE restore
518
+ // This ensures sessionDirectory is correct when restoring input lines
519
+ xtermService.updateContext(hasActiveProject, projectPath, session.directory);
520
+ // Set session ID for active stream checking
521
+ xtermService.setSessionId(session.id);
522
+
523
+ // Restore terminal buffer for the new session
524
+ // This will also update lastProcessedIndex
525
+ restoreSessionBuffer(session.id);
526
+
527
+ // Mark initial mount as complete after first restore
528
+ if (isInitialMount) {
529
+ setTimeout(() => {
530
+ isInitialMount = false;
531
+ }, 500); // Give enough time for restoration to complete
532
+ }
533
+
534
+ // CRITICAL: Auto-connect to PTY for session that hasn't connected yet
535
+ // This ensures PTY session is created on server for:
536
+ // 1. New tabs (session.lines.length === 0)
537
+ // 2. Restored sessions after browser refresh that haven't connected yet
538
+ const hasNotConnected = !connectedSessions.has(session.id);
539
+ if (hasNotConnected && hasActiveProject && onExecuteCommand) {
540
+ // Session hasn't connected to PTY yet - create connection
541
+ connectedSessions.add(session.id);
542
+ setTimeout(() => {
543
+ // Get terminal dimensions for PTY size sync
544
+ let terminalSize: { cols: number; rows: number } | undefined;
545
+ if (xtermService.fitAddon && xtermService.terminal) {
546
+ xtermService.fitAddon.fit();
547
+ const dims = xtermService.fitAddon.proposeDimensions();
548
+ if (dims) {
549
+ terminalSize = { cols: dims.cols, rows: dims.rows };
550
+ }
551
+ }
552
+ onExecuteCommand('', terminalSize).catch(() => {
553
+ // Silently handle connection errors
554
+ });
555
+ }, 200); // Small delay to ensure session is ready
556
+ }
557
+
558
+ // Check if we need to show prompt after restore
559
+ // Also check isExecuting prop from terminal store to avoid showing prompt during active execution
560
+ if (!sessionExecuting && !isExecuting) {
561
+ // IMPORTANT: Add delay to allow stream reconnection to complete
562
+ // This prevents prompt from appearing before reconnected output
563
+ setTimeout(() => {
564
+ // Check again if there's an active stream after the delay
565
+ const hasActiveStream = session ? backgroundTerminalService.hasActiveStream(session.id) : false;
566
+ if (!hasActiveStream) {
567
+ if (session.lines.length > 0) {
568
+ // Session has history - check if we need prompt
569
+ const lastLine = session.lines[session.lines.length - 1];
570
+ if (lastLine.type === 'prompt-trigger' || lastLine.type === 'output') {
571
+ xtermService.showPrompt();
572
+ isTerminalReady = true;
573
+ }
574
+ } else {
575
+ // Empty session - show prompt immediately
576
+ // (prompt will be displayed before PTY connection completes)
577
+ xtermService.showPrompt();
578
+ isTerminalReady = true;
579
+ }
580
+ } else {
581
+ // Active stream detected, mark as ready without prompt
582
+ isTerminalReady = true;
583
+ }
584
+ }, 500); // Delay to ensure stream reconnection completes
585
+ }
586
+ } else if (lastProcessedIndex >= sessionLinesLength && sessionLinesLength === 0) {
587
+ // Only clear if lines were actually removed (shouldn't happen anymore)
588
+ // With new approach, clear command adds a clear-screen marker instead
589
+ isAfterClearOperation = true;
590
+ xtermService.clear();
591
+ lastProcessedIndex = -1;
592
+ }
593
+ }
594
+ });
595
+
596
+ $effect(() => {
597
+ // Handle execution state changes and context updates
598
+ if (isInitialized) {
599
+ // Update context whenever props change
600
+ xtermService.updateContext(hasActiveProject, projectPath, session?.directory);
601
+ // Update session ID for active stream checking
602
+ if (session) {
603
+ xtermService.setSessionId(session.id);
604
+ }
605
+ // Use session-specific execution state instead of global
606
+ xtermService.setExecuting(sessionExecuting);
607
+
608
+ // Update ready state only if becoming ready (don't go back to false during execution)
609
+ // This prevents DOM blocking from re-activating during normal operation
610
+ const serviceReady = xtermService.isReady;
611
+ const serviceInit = xtermService.isInitialized;
612
+
613
+ if (serviceReady) {
614
+ isTerminalReady = true;
615
+ }
616
+ // Only set to false if terminal is genuinely not initialized
617
+ else if (!serviceInit) {
618
+ isTerminalReady = false;
619
+ }
620
+ }
621
+ });
622
+
623
+ // Update command history whenever it changes
624
+ $effect(() => {
625
+ if (isInitialized && session?.commandHistory) {
626
+ xtermService.updateCommandHistory(session.commandHistory);
627
+ }
628
+ });
629
+
630
+ // Setup event handlers
631
+ $effect(() => {
632
+ if (!isInitialized) return;
633
+
634
+ const cleanupResize = setupResizeHandling();
635
+ const cleanupTheme = setupThemeHandling();
636
+ const cleanupRightClick = setupRightClickCopy();
637
+
638
+ return () => {
639
+ cleanupResize();
640
+ cleanupTheme();
641
+ cleanupRightClick?.();
642
+ };
643
+ });
644
+
645
+ // Cleanup on destroy
646
+ onDestroy(() => {
647
+ // Remove keyboard event listener (must match capture phase)
648
+ if (keyboardEventHandler && xtermService.terminal?.element) {
649
+ xtermService.terminal.element.removeEventListener('keydown', keyboardEventHandler, true);
650
+ }
651
+
652
+ // CRITICAL: Clean up terminal service WebSocket listeners for the current session
653
+ // This prevents stale listeners from accumulating when XTerm is destroyed
654
+ // during project switches, which would cause duplicate output processing
655
+ if (lastSessionId) {
656
+ terminalService.cleanupListeners(lastSessionId);
657
+ }
658
+
659
+ xtermService.dispose();
660
+ });
661
+
662
+ // Expose methods for parent component (implementing XTermMethods)
663
+ export function clear() {
664
+ xtermService.clear();
665
+ lastProcessedIndex = -1;
666
+ }
667
+
668
+ export function fit() {
669
+ xtermService.fit(session?.id);
670
+ }
671
+
672
+ export function scrollToBottom() {
673
+ xtermService.scrollToBottom();
674
+ }
675
+
676
+ export function scrollToBottomIfNearEnd() {
677
+ xtermService.scrollToBottomIfNearEnd();
678
+ }
679
+
680
+ export function writeData(data: string) {
681
+ xtermService.writeData(data);
682
+ }
683
+
684
+ export function getSelectedText(): string {
685
+ return xtermService.getSelectedText();
686
+ }
687
+
688
+ export function clearSelection() {
689
+ xtermService.clearSelection();
690
+ }
691
+ </script>
692
+
693
+ <!-- Pure xterm.js terminal container -->
694
+ <div
695
+ bind:this={terminalContainer}
696
+ class="w-full h-full overflow-hidden bg-slate-50 dark:bg-slate-900/70 {className} select-none"
697
+ style="transition: opacity 0.2s ease-in-out; user-select: text;"
698
+ role="textbox"
699
+ tabindex="0"
700
+ aria-label="Interactive terminal"
701
+ title="Right-click selected text to copy"
702
+ >
703
+ {#if !browser}
704
+ <!-- SSR fallback -->
705
+ <div class="flex items-center justify-center h-full">
706
+ <div class="text-slate-600 dark:text-slate-400 text-sm">
707
+ Loading terminal...
708
+ </div>
709
+ </div>
710
+ {:else if !isInitialized}
711
+ <!-- Browser loading state -->
712
+ <div class="flex items-center justify-center h-full">
713
+ <div class="text-slate-600 dark:text-slate-400 text-sm">
714
+ Initializing terminal...
715
+ </div>
716
+ </div>
717
+ {/if}
718
+ </div>
719
+
720
+ <style>
721
+ /* Custom xterm.js styling */
722
+ :global(.xterm) {
723
+ padding: 0.5rem !important;
724
+ background: transparent !important;
725
+ height: 100% !important;
726
+ }
727
+
728
+ :global(.xterm .xterm-viewport) {
729
+ background: transparent !important;
730
+ height: 100% !important;
731
+ }
732
+
733
+ :global(.xterm .xterm-screen) {
734
+ background: transparent !important;
735
+ height: 100% !important;
736
+ }
737
+
738
+ :global(.xterm .xterm-helper-textarea) {
739
+ height: 100% !important;
740
+ }
741
+
742
+ /* Text selection styling */
743
+ :global(.xterm .xterm-selection div) {
744
+ background-color: #22c55e !important; /* green-500 */
745
+ opacity: 0.3 !important;
746
+ }
747
+
748
+ /* Better text selection on dark theme */
749
+ :global(.dark .xterm .xterm-selection div) {
750
+ background-color: #4ade80 !important; /* green-400 */
751
+ opacity: 0.4 !important;
752
+ }
753
+
754
+ /* Scrollbar styling to match application theme */
755
+ :global(.xterm .xterm-viewport::-webkit-scrollbar) {
756
+ width: 6px;
757
+ }
758
+
759
+ :global(.xterm .xterm-viewport::-webkit-scrollbar-track) {
760
+ background: transparent;
761
+ }
762
+
763
+ :global(.xterm .xterm-viewport::-webkit-scrollbar-thumb) {
764
+ background-color: rgb(148 163 184 / 0.3); /* slate-400 with opacity */
765
+ border-radius: 3px;
766
+ transition: background-color 0.2s ease;
767
+ }
768
+
769
+ :global(.dark .xterm .xterm-viewport::-webkit-scrollbar-thumb) {
770
+ background-color: rgb(71 85 105 / 0.3); /* slate-600 with opacity */
771
+ }
772
+
773
+ :global(.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover) {
774
+ background-color: rgb(148 163 184 / 0.6); /* slate-400 with higher opacity */
775
+ }
776
+
777
+ :global(.dark .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover) {
778
+ background-color: rgb(71 85 105 / 0.6); /* slate-600 with higher opacity */
779
+ }
780
+
781
+ /* Ensure proper focus styling */
782
+ :global(.xterm.focus .xterm-cursor) {
783
+ background-color: #22c55e !important; /* green-500 */
784
+ }
785
+
786
+ :global(.xterm:not(.focus) .xterm-cursor) {
787
+ background-color: #52525b !important; /* zinc-600 */
788
+ }
789
+
790
+ /* Responsive font size adjustments */
791
+ @media (max-width: 640px) {
792
+ :global(.xterm) {
793
+ font-size: 12px !important;
794
+ }
795
+ }
796
+ </style>