@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,387 @@
1
+ /**
2
+ * Persistent PTY Session Manager
3
+ * Manages long-running interactive PTY sessions (one per terminal tab)
4
+ * Supports true TTY interaction with stdin/stdout streaming
5
+ */
6
+
7
+ import type { IPty } from 'bun-pty';
8
+ import { spawn } from 'bun-pty';
9
+ import { debug } from '$shared/utils/logger';
10
+
11
+ export interface PtySession {
12
+ sessionId: string;
13
+ pty: IPty;
14
+ cwd: string;
15
+ projectId?: string;
16
+ createdAt: Date;
17
+ lastActivityAt: Date;
18
+ // Stream listeners
19
+ dataListeners: Set<(data: string) => void>;
20
+ exitListeners: Set<(event: { exitCode: number; signal?: number | string }) => void>;
21
+ // Output batching (performance optimization)
22
+ pendingOutput: string;
23
+ flushScheduled: boolean;
24
+ // Monotonically increasing sequence number for deduplication
25
+ outputSeq: number;
26
+ }
27
+
28
+ class PtySessionManager {
29
+ private sessions = new Map<string, PtySession>();
30
+ private cleanupInterval: Timer | null = null;
31
+
32
+ constructor() {
33
+ // Start cleanup interval (remove sessions inactive for >1 hour)
34
+ this.startCleanupInterval();
35
+ }
36
+
37
+ /**
38
+ * Create or get existing PTY session
39
+ */
40
+ async createSession(
41
+ sessionId: string,
42
+ cwd: string,
43
+ projectId?: string,
44
+ terminalSize?: { cols: number; rows: number }
45
+ ): Promise<PtySession> {
46
+ // Return existing session if already created
47
+ const existing = this.sessions.get(sessionId);
48
+ if (existing && existing.pty) {
49
+ debug.log('terminal', `♻️ Reusing existing PTY session: ${sessionId}`);
50
+ existing.lastActivityAt = new Date();
51
+ return existing;
52
+ }
53
+
54
+ debug.log('terminal', `🚀 Creating new interactive PTY session: ${sessionId}`);
55
+
56
+ // Determine shell based on platform
57
+ const isWindows = process.platform === 'win32';
58
+ let shell: string;
59
+ let shellArgs: string[] = [];
60
+
61
+ if (isWindows) {
62
+ // Windows: Use PowerShell in interactive mode
63
+ shell = 'powershell';
64
+ shellArgs = ['-NoLogo']; // Interactive mode, no -Command
65
+ } else {
66
+ // Unix: Use user's default shell or bash
67
+ shell = process.env.SHELL || '/bin/bash';
68
+ shellArgs = []; // Interactive mode, no -c
69
+ }
70
+
71
+ // Prepare environment
72
+ const ptyEnv: Record<string, string> = {};
73
+ for (const [key, value] of Object.entries(process.env)) {
74
+ if (value !== undefined) {
75
+ ptyEnv[key] = value;
76
+ }
77
+ }
78
+
79
+ // Terminal size
80
+ const cols = terminalSize?.cols || 80;
81
+ const rows = terminalSize?.rows || 24;
82
+
83
+ // Add terminal-specific environment variables
84
+ Object.assign(ptyEnv, {
85
+ FORCE_COLOR: '1',
86
+ COLORTERM: 'truecolor',
87
+ TERM: 'xterm-256color',
88
+ COLUMNS: cols.toString(),
89
+ LINES: rows.toString(),
90
+ TERM_PROGRAM: 'xterm.js',
91
+ CLICOLOR: '1',
92
+ LC_ALL: 'en_US.UTF-8',
93
+ LANG: 'en_US.UTF-8'
94
+ });
95
+
96
+ // Spawn interactive PTY
97
+ const pty = spawn(shell, shellArgs, {
98
+ name: 'xterm-256color',
99
+ cols,
100
+ rows,
101
+ cwd,
102
+ env: ptyEnv
103
+ });
104
+
105
+ debug.log('terminal', `✅ PTY spawned with PID: ${pty.pid}`);
106
+
107
+ // CRITICAL: Send initial newline to trigger shell prompt display
108
+ // Without this, shell won't show prompt on first connection
109
+ setTimeout(() => {
110
+ try {
111
+ pty.write('\r');
112
+ debug.log('terminal', '📝 Sent initial newline to trigger prompt');
113
+ } catch (error) {
114
+ debug.error('terminal', 'Failed to send initial newline:', error);
115
+ }
116
+ }, 100);
117
+
118
+ // Create session
119
+ const session: PtySession = {
120
+ sessionId,
121
+ pty,
122
+ cwd,
123
+ projectId,
124
+ createdAt: new Date(),
125
+ lastActivityAt: new Date(),
126
+ dataListeners: new Set(),
127
+ exitListeners: new Set(),
128
+ // Initialize batching state
129
+ pendingOutput: '',
130
+ flushScheduled: false,
131
+ outputSeq: 0
132
+ };
133
+
134
+ // Setup PTY event handlers with micro-task batching
135
+ pty.onData((data: string) => {
136
+ session.lastActivityAt = new Date();
137
+
138
+ // IMPORTANT: Always persist output to stream manager FIRST
139
+ // This ensures output is saved even if there are no active listeners
140
+ try {
141
+ const { terminalStreamManager } = require('./stream-manager');
142
+ const stream = terminalStreamManager.getStreamBySession(sessionId);
143
+ if (stream) {
144
+ terminalStreamManager.addOutput(stream.streamId, data);
145
+ }
146
+ } catch (error) {
147
+ // Stream manager not available yet, skip buffering
148
+ }
149
+
150
+ // Batch output for high-frequency data (micro-task batching)
151
+ session.pendingOutput += data;
152
+
153
+ if (!session.flushScheduled) {
154
+ session.flushScheduled = true;
155
+
156
+ // Use queueMicrotask for minimal latency batching
157
+ queueMicrotask(() => {
158
+ const output = session.pendingOutput;
159
+ session.pendingOutput = '';
160
+ session.flushScheduled = false;
161
+
162
+ // Increment sequence number for deduplication
163
+ session.outputSeq++;
164
+
165
+ // Broadcast batched output to all listeners
166
+ session.dataListeners.forEach(listener => {
167
+ try {
168
+ listener(output);
169
+ } catch (error) {
170
+ debug.error('terminal', 'Error in data listener:', error);
171
+ }
172
+ });
173
+ });
174
+ }
175
+ });
176
+
177
+ pty.onExit((event: { exitCode: number; signal?: number | string }) => {
178
+ debug.log('terminal', `🏁 PTY session ${sessionId} exited with code: ${event.exitCode}`);
179
+ // Broadcast to all listeners
180
+ session.exitListeners.forEach(listener => {
181
+ try {
182
+ listener(event);
183
+ } catch (error) {
184
+ debug.error('terminal', 'Error in exit listener:', error);
185
+ }
186
+ });
187
+ // Remove session
188
+ this.sessions.delete(sessionId);
189
+ });
190
+
191
+ // Store session
192
+ this.sessions.set(sessionId, session);
193
+
194
+ return session;
195
+ }
196
+
197
+ /**
198
+ * Get existing session
199
+ */
200
+ getSession(sessionId: string): PtySession | undefined {
201
+ return this.sessions.get(sessionId);
202
+ }
203
+
204
+ /**
205
+ * Write data to PTY (stdin)
206
+ */
207
+ write(sessionId: string, data: string): boolean {
208
+ const session = this.sessions.get(sessionId);
209
+ if (!session || !session.pty) {
210
+ debug.error('terminal', `❌ Cannot write to session ${sessionId}: session not found`);
211
+ return false;
212
+ }
213
+
214
+ try {
215
+ session.pty.write(data);
216
+ session.lastActivityAt = new Date();
217
+ return true;
218
+ } catch (error) {
219
+ debug.error('terminal', `❌ Error writing to PTY ${sessionId}:`, error);
220
+ return false;
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Resize PTY
226
+ */
227
+ resize(sessionId: string, cols: number, rows: number): boolean {
228
+ const session = this.sessions.get(sessionId);
229
+ if (!session || !session.pty) {
230
+ return false;
231
+ }
232
+
233
+ try {
234
+ session.pty.resize(cols, rows);
235
+ debug.log('terminal', `🔧 PTY ${sessionId} resized to ${cols}x${rows}`);
236
+ return true;
237
+ } catch (error) {
238
+ debug.error('terminal', `❌ Error resizing PTY ${sessionId}:`, error);
239
+ return false;
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Kill PTY session
245
+ */
246
+ killSession(sessionId: string, signal?: string): boolean {
247
+ const session = this.sessions.get(sessionId);
248
+ if (!session || !session.pty) {
249
+ return false;
250
+ }
251
+
252
+ try {
253
+ debug.log('terminal', `💀 Killing PTY session ${sessionId}`);
254
+
255
+ if (signal === 'SIGKILL' || signal === '9') {
256
+ session.pty.kill('SIGKILL');
257
+ } else if (signal === 'SIGTERM' || signal === '15') {
258
+ session.pty.kill('SIGTERM');
259
+ } else {
260
+ // Send Ctrl+C first for graceful termination
261
+ session.pty.write('\x03');
262
+
263
+ // Follow up with SIGKILL after delay
264
+ setTimeout(() => {
265
+ if (this.sessions.has(sessionId)) {
266
+ try {
267
+ session.pty.kill('SIGKILL');
268
+ } catch {
269
+ // Already dead, ignore
270
+ }
271
+ }
272
+ }, 1000);
273
+ }
274
+
275
+ this.sessions.delete(sessionId);
276
+ return true;
277
+ } catch (error) {
278
+ debug.error('terminal', `❌ Error killing PTY ${sessionId}:`, error);
279
+ this.sessions.delete(sessionId); // Remove anyway
280
+ return false;
281
+ }
282
+ }
283
+
284
+ /**
285
+ * Add data listener
286
+ */
287
+ addDataListener(sessionId: string, listener: (data: string) => void): boolean {
288
+ const session = this.sessions.get(sessionId);
289
+ if (!session) {
290
+ return false;
291
+ }
292
+ session.dataListeners.add(listener);
293
+ return true;
294
+ }
295
+
296
+ /**
297
+ * Remove data listener
298
+ */
299
+ removeDataListener(sessionId: string, listener: (data: string) => void): boolean {
300
+ const session = this.sessions.get(sessionId);
301
+ if (!session) {
302
+ return false;
303
+ }
304
+ return session.dataListeners.delete(listener);
305
+ }
306
+
307
+ /**
308
+ * Add exit listener
309
+ */
310
+ addExitListener(
311
+ sessionId: string,
312
+ listener: (event: { exitCode: number; signal?: number | string }) => void
313
+ ): boolean {
314
+ const session = this.sessions.get(sessionId);
315
+ if (!session) {
316
+ return false;
317
+ }
318
+ session.exitListeners.add(listener);
319
+ return true;
320
+ }
321
+
322
+ /**
323
+ * Remove exit listener
324
+ */
325
+ removeExitListener(
326
+ sessionId: string,
327
+ listener: (event: { exitCode: number; signal?: number | string }) => void
328
+ ): boolean {
329
+ const session = this.sessions.get(sessionId);
330
+ if (!session) {
331
+ return false;
332
+ }
333
+ return session.exitListeners.delete(listener);
334
+ }
335
+
336
+ /**
337
+ * Get all active sessions
338
+ */
339
+ getAllSessions(): PtySession[] {
340
+ return Array.from(this.sessions.values());
341
+ }
342
+
343
+ /**
344
+ * Start cleanup interval
345
+ */
346
+ private startCleanupInterval() {
347
+ // Run every 15 minutes
348
+ this.cleanupInterval = setInterval(() => {
349
+ this.cleanupInactiveSessions();
350
+ }, 15 * 60 * 1000);
351
+ }
352
+
353
+ /**
354
+ * Cleanup inactive sessions (>1 hour no activity)
355
+ */
356
+ private cleanupInactiveSessions() {
357
+ const now = new Date();
358
+ const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000);
359
+
360
+ for (const [sessionId, session] of this.sessions.entries()) {
361
+ if (session.lastActivityAt < oneHourAgo) {
362
+ debug.log('terminal', `🧹 Cleaning up inactive session: ${sessionId}`);
363
+ this.killSession(sessionId);
364
+ }
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Cleanup all sessions (on shutdown)
370
+ */
371
+ dispose() {
372
+ debug.log('terminal', '🧹 Disposing all PTY sessions');
373
+
374
+ if (this.cleanupInterval) {
375
+ clearInterval(this.cleanupInterval);
376
+ }
377
+
378
+ for (const sessionId of this.sessions.keys()) {
379
+ this.killSession(sessionId, 'SIGKILL');
380
+ }
381
+
382
+ this.sessions.clear();
383
+ }
384
+ }
385
+
386
+ // Export singleton instance
387
+ export const ptySessionManager = new PtySessionManager();
@@ -0,0 +1,313 @@
1
+ /**
2
+ * Shell utilities optimized for bun-pty
3
+ * Provides cross-platform shell detection and PTY creation
4
+ */
5
+
6
+ import { join } from 'path';
7
+ import { spawn, type IPty } from 'bun-pty';
8
+
9
+ import { debug } from '$shared/utils/logger';
10
+ // Platform detection
11
+ export const isWindows = process.platform === 'win32';
12
+ export const isMacOS = process.platform === 'darwin';
13
+ export const isLinux = process.platform === 'linux';
14
+
15
+ /**
16
+ * Find Git Bash executable on Windows
17
+ */
18
+ export async function findGitBash(): Promise<string | null> {
19
+ if (!isWindows) return null;
20
+
21
+ const possiblePaths = [
22
+ 'C:\\Program Files\\Git\\bin\\bash.exe',
23
+ 'C:\\Program Files (x86)\\Git\\bin\\bash.exe',
24
+ process.env.PROGRAMFILES ? join(process.env.PROGRAMFILES, 'Git', 'bin', 'bash.exe') : null,
25
+ process.env.LOCALAPPDATA ? join(process.env.LOCALAPPDATA, 'Programs', 'Git', 'bin', 'bash.exe') : null,
26
+ ].filter(Boolean) as string[];
27
+
28
+ for (const bashPath of possiblePaths) {
29
+ try {
30
+ if (await Bun.file(bashPath).exists()) {
31
+ debug.log('terminal', '✅ Found Git Bash at:', bashPath);
32
+ return bashPath;
33
+ }
34
+ } catch {
35
+ // Continue to next path
36
+ }
37
+ }
38
+
39
+ return null;
40
+ }
41
+
42
+ /**
43
+ * Find PowerShell executable on Windows
44
+ */
45
+ export async function findPowerShell(): Promise<string | null> {
46
+ if (!isWindows) return null;
47
+
48
+ const possiblePaths = [
49
+ 'C:\\Program Files\\PowerShell\\7\\pwsh.exe',
50
+ 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
51
+ process.env.SYSTEMROOT ? join(process.env.SYSTEMROOT, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe') : null,
52
+ ].filter(Boolean) as string[];
53
+
54
+ for (const pwshPath of possiblePaths) {
55
+ try {
56
+ if (await Bun.file(pwshPath).exists()) {
57
+ debug.log('terminal', '✅ Found PowerShell at:', pwshPath);
58
+ return pwshPath;
59
+ }
60
+ } catch {
61
+ // Continue to next path
62
+ }
63
+ }
64
+
65
+ return null;
66
+ }
67
+
68
+ /**
69
+ * Check if Git Bash is available
70
+ */
71
+ export async function isGitBashAvailable(): Promise<boolean> {
72
+ if (!isWindows) return false;
73
+ return (await findGitBash()) !== null;
74
+ }
75
+
76
+ /**
77
+ * Get Git Bash required message
78
+ */
79
+ export function getGitBashRequiredMessage(command: string): string {
80
+ return `⚠️ Git Bash is required for Unix commands!
81
+
82
+ The command '${command}' requires Git Bash.
83
+
84
+ Please install Git for Windows:
85
+ • Download from: https://git-scm.com/download/win
86
+ • Or install via: winget install Git.Git
87
+
88
+ After installation, restart the application.`;
89
+ }
90
+
91
+ /**
92
+ * Get the appropriate shell configuration for the current platform
93
+ */
94
+ export async function getShellConfig(preferGitBash = false): Promise<{
95
+ shell: string;
96
+ args: (command: string) => string[];
97
+ name: string;
98
+ isUnixLike: boolean;
99
+ }> {
100
+ if (isWindows) {
101
+ // For Windows, always use PowerShell as the primary shell
102
+ // PowerShell is available on all modern Windows systems
103
+ return {
104
+ shell: 'powershell',
105
+ args: (command: string) => ['-NoProfile', '-Command', command],
106
+ name: 'PowerShell',
107
+ isUnixLike: false
108
+ };
109
+ }
110
+
111
+ // macOS
112
+ if (isMacOS) {
113
+ if (await Bun.file('/bin/zsh').exists()) {
114
+ return {
115
+ shell: '/bin/zsh',
116
+ args: (command: string) => ['-c', command],
117
+ name: 'Zsh',
118
+ isUnixLike: true
119
+ };
120
+ }
121
+ return {
122
+ shell: '/bin/bash',
123
+ args: (command: string) => ['-c', command],
124
+ name: 'Bash',
125
+ isUnixLike: true
126
+ };
127
+ }
128
+
129
+ // Linux
130
+ if (isLinux) {
131
+ const userShell = process.env.SHELL || '/bin/bash';
132
+ const shellName = userShell.split('/').pop() || 'Shell';
133
+ return {
134
+ shell: userShell,
135
+ args: (command: string) => ['-c', command],
136
+ name: shellName.charAt(0).toUpperCase() + shellName.slice(1),
137
+ isUnixLike: true
138
+ };
139
+ }
140
+
141
+ // Fallback
142
+ return {
143
+ shell: '/bin/sh',
144
+ args: (command: string) => ['-c', command],
145
+ name: 'Shell',
146
+ isUnixLike: true
147
+ };
148
+ }
149
+
150
+ /**
151
+ * Create a PTY instance using bun-pty with xterm.js optimizations
152
+ */
153
+ export function createPty(shell: string, args: string[], cwd: string, terminalSize?: { cols: number; rows: number }): IPty {
154
+ // Prepare environment
155
+ const ptyEnv: Record<string, string> = {};
156
+
157
+ // Copy defined environment variables
158
+ for (const [key, value] of Object.entries(process.env)) {
159
+ if (value !== undefined) {
160
+ ptyEnv[key] = value;
161
+ }
162
+ }
163
+
164
+ // Get terminal dimensions from frontend or use defaults
165
+ const cols = terminalSize?.cols || 80;
166
+ const rows = terminalSize?.rows || 24;
167
+
168
+ // Add terminal-specific environment variables optimized for xterm.js
169
+ Object.assign(ptyEnv, {
170
+ FORCE_COLOR: '1',
171
+ COLORTERM: 'truecolor',
172
+ TERM: 'xterm-256color',
173
+ COLUMNS: cols.toString(),
174
+ LINES: rows.toString(),
175
+ // Enable proper ANSI support
176
+ TERM_PROGRAM: 'xterm.js',
177
+ // Optimize for interactive terminal
178
+ CLICOLOR: '1',
179
+ // Enable Unicode support
180
+ LC_ALL: 'en_US.UTF-8',
181
+ LANG: 'en_US.UTF-8'
182
+ });
183
+
184
+ // Extract the actual command from args
185
+ const actualCommand: string = '';
186
+
187
+ if (isWindows) {
188
+ // Windows: Always use PowerShell
189
+ if (shell === 'powershell') {
190
+ // Extract the actual command from args
191
+ let actualCommand = args.join(' ');
192
+ if (args.length >= 2 && (args[0] === '-Command' || args[0] === '-NoProfile')) {
193
+ // Find the actual command in the args
194
+ const commandIndex = args.findIndex(arg => arg === '-Command');
195
+ if (commandIndex !== -1 && commandIndex + 1 < args.length) {
196
+ actualCommand = args[commandIndex + 1];
197
+ } else if (args[args.length - 1] !== '-Command' && args[args.length - 1] !== '-NoProfile') {
198
+ actualCommand = args[args.length - 1];
199
+ }
200
+ }
201
+ return spawn('powershell', ['-NoProfile', '-NoLogo', '-Command', actualCommand], {
202
+ name: 'xterm-256color',
203
+ cols,
204
+ rows,
205
+ cwd: cwd,
206
+ env: ptyEnv
207
+ });
208
+ }
209
+
210
+ // Default to PowerShell if shell is not recognized
211
+ return spawn('powershell', ['-NoProfile', '-NoLogo', '-Command', args.join(' ') || 'Write-Host "Terminal ready"'], {
212
+ name: 'xterm-256color',
213
+ cols,
214
+ rows,
215
+ cwd: cwd,
216
+ env: ptyEnv
217
+ });
218
+ }
219
+
220
+ // Unix-like systems
221
+ return spawn(shell, args, {
222
+ name: 'xterm-256color',
223
+ cols,
224
+ rows,
225
+ cwd: cwd,
226
+ env: ptyEnv
227
+ });
228
+ }
229
+
230
+ /**
231
+ * Create a PTY wrapper for process-manager compatibility with xterm.js support
232
+ */
233
+ export function createPtyWrapper(pty: IPty, sessionId?: string): any {
234
+ let isKilled = false;
235
+
236
+ return {
237
+ pid: pty.pid,
238
+ pty: pty, // Expose the original PTY instance
239
+
240
+ // Expose PTY event handlers
241
+ onData: (callback: (data: string) => void) => {
242
+ return pty.onData(callback);
243
+ },
244
+
245
+ onExit: (callback: (event: { exitCode: number; signal?: number | string }) => void) => {
246
+ return pty.onExit((event) => {
247
+ isKilled = true;
248
+ debug.log('terminal', `🏁 PTY ${pty.pid} exited with code:`, event.exitCode);
249
+ callback({ exitCode: event.exitCode, signal: event.signal });
250
+ });
251
+ },
252
+
253
+ resize: (cols: number, rows: number) => {
254
+ try {
255
+ debug.log('terminal', `🔧 Resizing PTY ${pty.pid} to ${cols}x${rows}`);
256
+ pty.resize(cols, rows);
257
+ debug.log('terminal', `✅ PTY ${pty.pid} resized successfully`);
258
+ } catch (error) {
259
+ debug.error('terminal', `❌ Failed to resize PTY ${pty.pid}:`, error);
260
+ throw error;
261
+ }
262
+ },
263
+
264
+ write: (data: string) => {
265
+ return pty.write(data);
266
+ },
267
+
268
+ kill: (signal?: number | string) => {
269
+ if (isKilled) {
270
+ debug.log('terminal', '⚠️ PTY already killed, ignoring kill signal');
271
+ return;
272
+ }
273
+
274
+ try {
275
+ isKilled = true;
276
+ debug.log('terminal', `💀 Killing PTY ${pty.pid} with signal:`, signal);
277
+
278
+ if (signal === 'SIGKILL' || signal === 9) {
279
+ pty.kill('SIGKILL');
280
+ } else if (signal === 'SIGTERM' || signal === 15) {
281
+ pty.kill('SIGTERM');
282
+ } else {
283
+ // Send Ctrl+C for graceful termination, then SIGKILL as fallback
284
+ debug.log('terminal', '⌨️ Sending Ctrl+C to PTY...');
285
+ pty.write('\x03');
286
+
287
+ // Give the process time to handle Ctrl+C gracefully
288
+ setTimeout(() => {
289
+ try {
290
+ if (pty.pid && !isKilled) {
291
+ debug.log('terminal', '⏰ Graceful termination timeout, sending SIGKILL...');
292
+ pty.kill('SIGKILL');
293
+ }
294
+ } catch (killError) {
295
+ // Process might already be dead, this is expected
296
+ debug.log('terminal', '💀 PTY process already terminated');
297
+ }
298
+ }, 1000);
299
+ }
300
+ } catch (e) {
301
+ debug.log('terminal', '⚠️ Error killing PTY (this may be normal during shutdown):', e instanceof Error ? e.message : e);
302
+ }
303
+ },
304
+
305
+ exited: new Promise<number>((resolve) => {
306
+ pty.onExit((event) => {
307
+ isKilled = true;
308
+ debug.log('terminal', `🏁 PTY ${pty.pid} exited with code:`, event.exitCode);
309
+ resolve(event.exitCode);
310
+ });
311
+ })
312
+ };
313
+ }