@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,293 @@
1
+ /**
2
+ * Terminal Stream Manager
3
+ * Manages background terminal streams and their state
4
+ */
5
+
6
+ import type { IPty } from 'bun-pty';
7
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, unlinkSync } from 'fs';
8
+ import { join } from 'path';
9
+
10
+ interface TerminalStream {
11
+ streamId: string;
12
+ sessionId: string;
13
+ command: string;
14
+ pty: IPty;
15
+ status: 'active' | 'completed' | 'error' | 'cancelled';
16
+ startedAt: Date;
17
+ workingDirectory?: string;
18
+ projectPath?: string;
19
+ projectId?: string;
20
+ output: string[];
21
+ processId?: number;
22
+ outputStartIndex?: number; // Track where new output starts (for background output)
23
+ }
24
+
25
+ class TerminalStreamManager {
26
+ private streams: Map<string, TerminalStream> = new Map();
27
+ private sessionToStream: Map<string, string> = new Map();
28
+ private tempDir: string = '.terminal-output-cache';
29
+
30
+ constructor() {
31
+ // Create temp directory for output caching
32
+ if (!existsSync(this.tempDir)) {
33
+ mkdirSync(this.tempDir, { recursive: true });
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Create a new terminal stream
39
+ */
40
+ createStream(
41
+ sessionId: string,
42
+ command: string,
43
+ pty: IPty,
44
+ workingDirectory?: string,
45
+ projectPath?: string,
46
+ projectId?: string,
47
+ predefinedStreamId?: string,
48
+ outputStartIndex?: number
49
+ ): string {
50
+ // Check if there's already a stream for this session
51
+ const existingStreamId = this.sessionToStream.get(sessionId);
52
+ if (existingStreamId) {
53
+ const existingStream = this.streams.get(existingStreamId);
54
+ if (existingStream) {
55
+ // Clean up existing stream first
56
+ if (existingStream.pty && existingStream.pty !== pty) {
57
+ // If it's a different PTY, kill the old one
58
+ try {
59
+ existingStream.pty.kill();
60
+ } catch (error) {
61
+ // Ignore error if PTY already killed
62
+ }
63
+ }
64
+ // Remove the old stream
65
+ this.streams.delete(existingStreamId);
66
+ }
67
+ }
68
+
69
+ // Use provided streamId or generate a new one
70
+ const streamId = predefinedStreamId || `terminal-stream-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
71
+
72
+ const stream: TerminalStream = {
73
+ streamId,
74
+ sessionId,
75
+ command,
76
+ pty,
77
+ status: 'active',
78
+ startedAt: new Date(),
79
+ workingDirectory,
80
+ projectPath,
81
+ projectId,
82
+ output: [],
83
+ processId: pty.pid,
84
+ outputStartIndex: outputStartIndex || 0
85
+ };
86
+
87
+ this.streams.set(streamId, stream);
88
+ this.sessionToStream.set(sessionId, streamId);
89
+
90
+ return streamId;
91
+ }
92
+
93
+ /**
94
+ * Get stream by ID
95
+ */
96
+ getStream(streamId: string): TerminalStream | undefined {
97
+ return this.streams.get(streamId);
98
+ }
99
+
100
+ /**
101
+ * Get stream by session ID
102
+ */
103
+ getStreamBySession(sessionId: string): TerminalStream | undefined {
104
+ const streamId = this.sessionToStream.get(sessionId);
105
+ if (streamId) {
106
+ return this.streams.get(streamId);
107
+ }
108
+ return undefined;
109
+ }
110
+
111
+ /**
112
+ * Add output to stream
113
+ */
114
+ addOutput(streamId: string, output: string): void {
115
+ const stream = this.streams.get(streamId);
116
+ if (stream) {
117
+ stream.output.push(output);
118
+
119
+ // Keep only last 2000 entries to prevent memory overflow
120
+ if (stream.output.length > 2000) {
121
+ stream.output = stream.output.slice(-2000);
122
+ }
123
+
124
+ // Also persist output to disk for background persistence
125
+ this.persistOutputToDisk(stream);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Persist output to disk for cross-project persistence
131
+ */
132
+ private persistOutputToDisk(stream: TerminalStream): void {
133
+ try {
134
+ const cacheFile = join(this.tempDir, `${stream.sessionId}.json`);
135
+
136
+ // Only save new output (from outputStartIndex onwards)
137
+ // This prevents duplicating old output that was already displayed
138
+ const newOutput = stream.outputStartIndex !== undefined
139
+ ? stream.output.slice(stream.outputStartIndex)
140
+ : stream.output;
141
+
142
+ const cacheData = {
143
+ streamId: stream.streamId,
144
+ sessionId: stream.sessionId,
145
+ command: stream.command,
146
+ projectId: stream.projectId,
147
+ projectPath: stream.projectPath,
148
+ workingDirectory: stream.workingDirectory,
149
+ startedAt: stream.startedAt,
150
+ status: stream.status,
151
+ output: newOutput, // Only save new output
152
+ outputStartIndex: stream.outputStartIndex || 0,
153
+ lastUpdated: new Date().toISOString()
154
+ };
155
+ writeFileSync(cacheFile, JSON.stringify(cacheData));
156
+ } catch (error) {
157
+ // Silently handle write errors
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Load cached output from disk (public method for API access)
163
+ */
164
+ loadCachedOutput(sessionId: string): string[] | null {
165
+ try {
166
+ const cacheFile = join(this.tempDir, `${sessionId}.json`);
167
+ if (existsSync(cacheFile)) {
168
+ const data = JSON.parse(readFileSync(cacheFile, 'utf-8'));
169
+ return data.output || [];
170
+ }
171
+ } catch (error) {
172
+ // Silently handle read errors
173
+ }
174
+ return null;
175
+ }
176
+
177
+ /**
178
+ * Get output from index
179
+ */
180
+ getOutput(streamId: string, fromIndex: number = 0): string[] {
181
+ const stream = this.streams.get(streamId);
182
+ if (stream) {
183
+ return stream.output.slice(fromIndex);
184
+ }
185
+
186
+ // If stream not in memory, try to load from cache
187
+ // This handles cases where server restarts or stream is cleaned from memory
188
+ const sessionId = this.getSessionIdByStreamId(streamId);
189
+ if (sessionId) {
190
+ const cachedOutput = this.loadCachedOutput(sessionId);
191
+ if (cachedOutput) {
192
+ return cachedOutput.slice(fromIndex);
193
+ }
194
+ }
195
+
196
+ return [];
197
+ }
198
+
199
+ /**
200
+ * Get session ID from stream ID (helper method)
201
+ */
202
+ private getSessionIdByStreamId(streamId: string): string | null {
203
+ for (const [sessionId, sid] of this.sessionToStream.entries()) {
204
+ if (sid === streamId) {
205
+ return sessionId;
206
+ }
207
+ }
208
+ return null;
209
+ }
210
+
211
+ /**
212
+ * Update stream status
213
+ */
214
+ updateStatus(streamId: string, status: TerminalStream['status']): void {
215
+ const stream = this.streams.get(streamId);
216
+ if (stream) {
217
+ stream.status = status;
218
+
219
+ // Clean up completed/cancelled streams after a delay
220
+ if (status === 'completed' || status === 'cancelled' || status === 'error') {
221
+ // Keep stream for 5 minutes for reconnection attempts
222
+ setTimeout(() => {
223
+ this.removeStream(streamId);
224
+ }, 5 * 60 * 1000);
225
+ }
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Remove stream
231
+ */
232
+ removeStream(streamId: string): void {
233
+ const stream = this.streams.get(streamId);
234
+ if (stream) {
235
+ // Kill PTY if still active
236
+ if (stream.status === 'active' && stream.pty) {
237
+ try {
238
+ stream.pty.kill();
239
+ } catch (error) {
240
+ // Silently handle error
241
+ }
242
+ }
243
+
244
+ // Clean up cache file
245
+ try {
246
+ const cacheFile = join(this.tempDir, `${stream.sessionId}.json`);
247
+ if (existsSync(cacheFile)) {
248
+ unlinkSync(cacheFile);
249
+ }
250
+ } catch (error) {
251
+ // Silently handle error
252
+ }
253
+
254
+ // Remove from maps
255
+ this.streams.delete(streamId);
256
+ this.sessionToStream.delete(stream.sessionId);
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Get stream status info
262
+ */
263
+ getStreamStatus(streamId: string): {
264
+ status: string;
265
+ messagesCount: number;
266
+ startedAt: Date;
267
+ processId?: number;
268
+ } | null {
269
+ const stream = this.streams.get(streamId);
270
+ if (!stream) {
271
+ return null;
272
+ }
273
+
274
+ return {
275
+ status: stream.status,
276
+ messagesCount: stream.output.length,
277
+ startedAt: stream.startedAt,
278
+ processId: stream.processId
279
+ };
280
+ }
281
+
282
+ /**
283
+ * Clean up all streams
284
+ */
285
+ cleanup(): void {
286
+ for (const streamId of this.streams.keys()) {
287
+ this.removeStream(streamId);
288
+ }
289
+ }
290
+ }
291
+
292
+ // Export singleton instance
293
+ export const terminalStreamManager = new TerminalStreamManager();
@@ -0,0 +1,243 @@
1
+ import { Tunnel, bin, install } from 'cloudflared';
2
+ import { debug } from '$shared/utils/logger';
3
+ import { existsSync } from 'fs';
4
+
5
+ interface TunnelInstance {
6
+ tunnel: any;
7
+ publicUrl: string;
8
+ localPort: number;
9
+ startedAt: Date;
10
+ autoStopTimer?: NodeJS.Timeout;
11
+ }
12
+
13
+ class GlobalTunnelManager {
14
+ // Key format: "port" for global tunnels
15
+ private activeTunnels = new Map<number, TunnelInstance>();
16
+ private binaryInstalled = false;
17
+
18
+ /**
19
+ * Ensure cloudflared binary is installed
20
+ * Downloads binary on first use (~66MB, takes 30-60 seconds)
21
+ */
22
+ private async ensureBinaryInstalled(
23
+ onProgress?: (stage: string, data?: any) => void
24
+ ): Promise<{
25
+ needsDownload: boolean;
26
+ downloadTime?: number;
27
+ }> {
28
+ // Check if already verified in this session
29
+ if (this.binaryInstalled) {
30
+ onProgress?.('binary-ready', { cached: true });
31
+ return { needsDownload: false };
32
+ }
33
+
34
+ // Check if binary file exists
35
+ if (!existsSync(bin)) {
36
+ debug.log('tunnel', '[PROGRESS:DOWNLOADING_BINARY] Cloudflared binary not found, downloading...');
37
+ debug.log('tunnel', 'This may take 30-60 seconds on first use (downloading ~66MB)');
38
+ onProgress?.('downloading-binary', { size: '~66MB' });
39
+
40
+ const startTime = Date.now();
41
+
42
+ try {
43
+ // Call install() from cloudflared package
44
+ // This downloads the binary from Cloudflare's CDN
45
+ await install(bin);
46
+
47
+ const downloadTime = Date.now() - startTime;
48
+ debug.log('tunnel', `[PROGRESS:BINARY_READY] Cloudflared binary installed successfully in ${downloadTime}ms at: ${bin}`);
49
+
50
+ // Verify the binary was actually created
51
+ if (!existsSync(bin)) {
52
+ throw new Error('Binary file was not created after installation');
53
+ }
54
+
55
+ this.binaryInstalled = true;
56
+ onProgress?.('binary-ready', { downloaded: true, time: downloadTime });
57
+ return { needsDownload: true, downloadTime };
58
+ } catch (error) {
59
+ debug.error('tunnel', 'Failed to install cloudflared binary:', error);
60
+ throw new Error(
61
+ 'Failed to download cloudflared binary. Please check your internet connection and try again. ' +
62
+ 'The tunnel feature requires downloading ~66MB on first use.'
63
+ );
64
+ }
65
+ } else {
66
+ debug.log('tunnel', '[PROGRESS:BINARY_READY] Cloudflared binary already installed at:', bin);
67
+ this.binaryInstalled = true;
68
+ onProgress?.('binary-ready', { cached: true });
69
+ return { needsDownload: false };
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Start cloudflared tunnel for a port
75
+ */
76
+ async startTunnel(
77
+ port: number,
78
+ autoStopMinutes: number = 60,
79
+ onProgress?: (stage: string, data?: any) => void
80
+ ): Promise<{ publicUrl: string; status: 'active'; binaryDownloaded: boolean; timings: any }> {
81
+ debug.log('tunnel', '[PROGRESS:CHECKING_BINARY] Checking if binary is installed...');
82
+ onProgress?.('checking-binary');
83
+
84
+ // Ensure binary is installed
85
+ const binaryInfo = await this.ensureBinaryInstalled(onProgress);
86
+ const timings: any = {};
87
+
88
+ if (binaryInfo.needsDownload) {
89
+ timings.binaryDownload = binaryInfo.downloadTime;
90
+ }
91
+
92
+ // Check if tunnel already exists for this port
93
+ if (this.activeTunnels.has(port)) {
94
+ const existing = this.activeTunnels.get(port)!;
95
+ debug.log('tunnel', `Tunnel already exists for port ${port}, returning existing URL`);
96
+ return {
97
+ publicUrl: existing.publicUrl,
98
+ status: 'active',
99
+ binaryDownloaded: binaryInfo.needsDownload,
100
+ timings
101
+ };
102
+ }
103
+
104
+ debug.log('tunnel', `[PROGRESS:STARTING_TUNNEL] Starting cloudflared tunnel for port ${port}...`);
105
+ onProgress?.('starting-tunnel', { port });
106
+
107
+ const tunnelStartTime = Date.now();
108
+
109
+ try {
110
+ // Create tunnel instance
111
+ const tunnel = new Tunnel({
112
+ // Bind to specific port
113
+ url: `http://localhost:${port}`
114
+ });
115
+
116
+ // Wait for tunnel to be ready
117
+ debug.log('tunnel', '[PROGRESS:GENERATING_URL] Waiting for public URL...');
118
+ onProgress?.('generating-url');
119
+
120
+ const publicUrl: string = await new Promise((resolve, reject) => {
121
+ const timeout = setTimeout(() => {
122
+ reject(new Error('Tunnel connection timeout (30s)'));
123
+ }, 30000); // 30 second timeout
124
+
125
+ tunnel.on('url', (url: string) => {
126
+ debug.log('tunnel', `[PROGRESS:CONNECTED] ✅ Tunnel connected! Public URL: ${url}`);
127
+ clearTimeout(timeout);
128
+ resolve(url);
129
+ });
130
+
131
+ tunnel.on('error', (error: Error) => {
132
+ debug.error('tunnel', '[PROGRESS:FAILED] Tunnel error:', error);
133
+ clearTimeout(timeout);
134
+ reject(error);
135
+ });
136
+ });
137
+
138
+ timings.tunnelStart = Date.now() - tunnelStartTime;
139
+ timings.total = Date.now() - tunnelStartTime + (binaryInfo.downloadTime || 0);
140
+
141
+ // Setup auto-stop timer
142
+ const autoStopMs = autoStopMinutes * 60 * 1000;
143
+ const autoStopTimer = setTimeout(
144
+ () => {
145
+ debug.log('tunnel', `Auto-stopping tunnel on port ${port} after ${autoStopMinutes} minutes`);
146
+ this.stopTunnel(port);
147
+ },
148
+ autoStopMs
149
+ );
150
+
151
+ // Store tunnel instance
152
+ const instance: TunnelInstance = {
153
+ tunnel,
154
+ publicUrl,
155
+ localPort: port,
156
+ startedAt: new Date(),
157
+ autoStopTimer
158
+ };
159
+
160
+ this.activeTunnels.set(port, instance);
161
+
162
+ debug.log('tunnel', `✅ Tunnel started successfully on port ${port}`);
163
+ debug.log('tunnel', ` Public URL: ${publicUrl}`);
164
+ debug.log('tunnel', ` Auto-stop: ${autoStopMinutes} minutes`);
165
+ debug.log('tunnel', ` Timings:`, timings);
166
+ onProgress?.('connected', { publicUrl, timings });
167
+
168
+ return {
169
+ publicUrl,
170
+ status: 'active',
171
+ binaryDownloaded: binaryInfo.needsDownload,
172
+ timings
173
+ };
174
+ } catch (error) {
175
+ debug.error('tunnel', `Failed to start tunnel on port ${port}:`, error);
176
+ throw error;
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Stop tunnel for a port
182
+ */
183
+ async stopTunnel(port: number): Promise<void> {
184
+ const instance = this.activeTunnels.get(port);
185
+
186
+ if (!instance) {
187
+ debug.log('tunnel', `No active tunnel found for port ${port}`);
188
+ return;
189
+ }
190
+
191
+ debug.log('tunnel', `Stopping tunnel on port ${port}...`);
192
+
193
+ try {
194
+ // Clear auto-stop timer
195
+ if (instance.autoStopTimer) {
196
+ clearTimeout(instance.autoStopTimer);
197
+ }
198
+
199
+ // Stop tunnel
200
+ if (instance.tunnel && typeof instance.tunnel.stop === 'function') {
201
+ await instance.tunnel.stop();
202
+ }
203
+
204
+ // Remove from active tunnels
205
+ this.activeTunnels.delete(port);
206
+
207
+ debug.log('tunnel', `✅ Tunnel stopped on port ${port}`);
208
+ } catch (error) {
209
+ debug.error('tunnel', `Error stopping tunnel on port ${port}:`, error);
210
+ // Still remove from active tunnels even if stop failed
211
+ this.activeTunnels.delete(port);
212
+ throw error;
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Get all active tunnels
218
+ */
219
+ getActiveTunnels(): Array<{
220
+ port: number;
221
+ publicUrl: string;
222
+ startedAt: string;
223
+ }> {
224
+ return Array.from(this.activeTunnels.entries()).map(([port, instance]) => ({
225
+ port,
226
+ publicUrl: instance.publicUrl,
227
+ startedAt: instance.startedAt.toISOString()
228
+ }));
229
+ }
230
+
231
+ /**
232
+ * Stop all tunnels
233
+ */
234
+ async stopAllTunnels(): Promise<void> {
235
+ debug.log('tunnel', 'Stopping all tunnels...');
236
+ const ports = Array.from(this.activeTunnels.keys());
237
+ await Promise.all(ports.map((port) => this.stopTunnel(port)));
238
+ debug.log('tunnel', '✅ All tunnels stopped');
239
+ }
240
+ }
241
+
242
+ // Singleton instance
243
+ export const globalTunnelManager = new GlobalTunnelManager();