@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,681 @@
1
+ <script module lang="ts">
2
+ // Persistent state that survives component destruction (mobile/desktop switch)
3
+ const projectSearchStates = new Map<string, any>();
4
+ </script>
5
+
6
+ <script lang="ts">
7
+ import { projectState } from '$frontend/lib/stores/core/projects.svelte';
8
+ import type { FileNode as FileNodeType } from '$shared/types/filesystem';
9
+ import FileNode from './FileNode.svelte';
10
+ import SearchResults from './SearchResults.svelte';
11
+ import Icon from '$frontend/lib/components/common/Icon.svelte';
12
+ import ws from '$frontend/lib/utils/ws';
13
+ import { addNotification } from '$frontend/lib/stores/ui/notification.svelte';
14
+ import { showConfirm } from '$frontend/lib/stores/ui/dialog.svelte';
15
+ import { normalizePath } from '$shared/utils/path';
16
+ import { onDestroy } from 'svelte';
17
+
18
+ interface Props {
19
+ files: FileNodeType[];
20
+ onFileSelect?: (file: FileNodeType) => void;
21
+ onFileAction?: (action: string, file: FileNodeType) => void;
22
+ onPasteToRoot?: () => void;
23
+ onNewFileInRoot?: () => void;
24
+ onNewFolderInRoot?: () => void;
25
+ selectedFile?: FileNodeType | null;
26
+ expandedFolders?: Set<string>;
27
+ onToggle?: (folderPath: string) => void;
28
+ hasClipboard?: boolean;
29
+ onFileOpen?: (path: string, lineNumber?: number) => void;
30
+ onRefresh?: () => void;
31
+ modifiedFiles?: Set<string>;
32
+ activeFilePath?: string | null;
33
+ }
34
+
35
+ let {
36
+ files = [],
37
+ onFileSelect,
38
+ onFileAction,
39
+ onPasteToRoot,
40
+ onNewFileInRoot,
41
+ onNewFolderInRoot,
42
+ selectedFile = null,
43
+ expandedFolders,
44
+ onToggle,
45
+ hasClipboard = false,
46
+ onFileOpen,
47
+ onRefresh,
48
+ modifiedFiles = new Set(),
49
+ activeFilePath = null
50
+ }: Props = $props();
51
+
52
+ // Create local state if expandedFolders is not provided
53
+ let localState = $state(new Set<string>());
54
+
55
+ // Use provided expandedFolders or local state (computed)
56
+ const localExpandedFolders = $derived(expandedFolders || localState);
57
+
58
+ // State to track which menu is currently open (only one menu at a time)
59
+ let openMenuPath = $state<string | null>(null);
60
+
61
+ // Search visibility
62
+ let searchVisible = $state(false);
63
+
64
+ // Search state
65
+ let searchQuery = $state('');
66
+ let submittedQuery = $state('');
67
+ let searchMode = $state<'files' | 'code'>('files');
68
+ let isSearching = $state(false);
69
+ let fileSearchResults = $state<any[]>([]);
70
+ let codeSearchResults = $state<any[]>([]);
71
+ let searchInputRef = $state<HTMLInputElement>();
72
+
73
+ // Search options
74
+ let caseSensitive = $state(false);
75
+ let wholeWord = $state(false);
76
+ let useRegex = $state(false);
77
+
78
+ // File filter state
79
+ let filesToInclude = $state('');
80
+ let filesToExclude = $state('');
81
+ let showFilters = $state(false);
82
+
83
+ // Replace state
84
+ let replaceQuery = $state('');
85
+ let showReplace = $state(false);
86
+ let isReplacing = $state(false);
87
+
88
+ // Abort controller for cancelling search
89
+ let searchAbortController: AbortController | null = null;
90
+
91
+ // projectSearchStates is at module level to survive component destruction (mobile/desktop switch)
92
+ let lastProjectId = $state('');
93
+
94
+ // Save/restore search state per project
95
+ $effect(() => {
96
+ const currentProjectId = projectState.currentProject?.id || '';
97
+ if (currentProjectId !== lastProjectId) {
98
+ // Save current state for old project
99
+ if (lastProjectId) {
100
+ projectSearchStates.set(lastProjectId, {
101
+ searchVisible,
102
+ searchQuery,
103
+ submittedQuery,
104
+ searchMode,
105
+ fileSearchResults,
106
+ codeSearchResults,
107
+ caseSensitive,
108
+ wholeWord,
109
+ useRegex,
110
+ filesToInclude,
111
+ filesToExclude,
112
+ showFilters,
113
+ showReplace,
114
+ replaceQuery
115
+ });
116
+ }
117
+ // Restore or reset for new project
118
+ const saved = projectSearchStates.get(currentProjectId);
119
+ if (saved) {
120
+ searchVisible = saved.searchVisible;
121
+ searchQuery = saved.searchQuery;
122
+ submittedQuery = saved.submittedQuery;
123
+ searchMode = saved.searchMode;
124
+ fileSearchResults = saved.fileSearchResults;
125
+ codeSearchResults = saved.codeSearchResults;
126
+ caseSensitive = saved.caseSensitive;
127
+ wholeWord = saved.wholeWord;
128
+ useRegex = saved.useRegex;
129
+ filesToInclude = saved.filesToInclude;
130
+ filesToExclude = saved.filesToExclude;
131
+ showFilters = saved.showFilters;
132
+ showReplace = saved.showReplace;
133
+ replaceQuery = saved.replaceQuery;
134
+ } else {
135
+ searchVisible = false;
136
+ searchQuery = '';
137
+ submittedQuery = '';
138
+ searchMode = 'files';
139
+ fileSearchResults = [];
140
+ codeSearchResults = [];
141
+ caseSensitive = false;
142
+ wholeWord = false;
143
+ useRegex = false;
144
+ filesToInclude = '';
145
+ filesToExclude = '';
146
+ showFilters = false;
147
+ showReplace = false;
148
+ replaceQuery = '';
149
+ }
150
+ lastProjectId = currentProjectId;
151
+ }
152
+ });
153
+
154
+ // Save search state to persistent storage on component destruction (mobile/desktop switch)
155
+ onDestroy(() => {
156
+ const currentProjectId = projectState.currentProject?.id || '';
157
+ if (currentProjectId) {
158
+ projectSearchStates.set(currentProjectId, {
159
+ searchVisible,
160
+ searchQuery,
161
+ submittedQuery,
162
+ searchMode,
163
+ fileSearchResults,
164
+ codeSearchResults,
165
+ caseSensitive,
166
+ wholeWord,
167
+ useRegex,
168
+ filesToInclude,
169
+ filesToExclude,
170
+ showFilters,
171
+ showReplace,
172
+ replaceQuery
173
+ });
174
+ }
175
+ });
176
+
177
+ function handleMenuToggle(filePath: string) {
178
+ openMenuPath = openMenuPath === filePath ? null : filePath;
179
+ }
180
+
181
+ function handleFileSelect(file: FileNodeType) {
182
+ onFileSelect?.(file);
183
+ }
184
+
185
+ function handleFileAction(action: string, file: FileNodeType) {
186
+ if (action === 'find-in-folder') {
187
+ const projectPath = projectState.currentProject?.path || '';
188
+ let relativePath = file.path;
189
+ if (projectPath && relativePath.startsWith(projectPath)) {
190
+ relativePath = relativePath.slice(projectPath.length);
191
+ if (relativePath.startsWith('/') || relativePath.startsWith('\\')) {
192
+ relativePath = relativePath.slice(1);
193
+ }
194
+ }
195
+ searchVisible = true;
196
+ searchMode = 'code';
197
+ filesToInclude = relativePath;
198
+ showFilters = true;
199
+ setTimeout(() => searchInputRef?.focus(), 100);
200
+ return;
201
+ }
202
+ onFileAction?.(action, file);
203
+ }
204
+
205
+ function toggleFolder(folderPath: string) {
206
+ if (onToggle) {
207
+ onToggle(folderPath);
208
+ } else if (expandedFolders) {
209
+ if (expandedFolders.has(folderPath)) {
210
+ expandedFolders.delete(folderPath);
211
+ } else {
212
+ expandedFolders.add(folderPath);
213
+ }
214
+ expandedFolders = new Set(expandedFolders);
215
+ } else {
216
+ if (localState.has(folderPath)) {
217
+ localState.delete(folderPath);
218
+ } else {
219
+ localState.add(folderPath);
220
+ }
221
+ localState = new Set(localState);
222
+ }
223
+ }
224
+
225
+ // Toggle search visibility (preserve inputs, auto-submit on re-open)
226
+ function toggleSearch() {
227
+ searchVisible = !searchVisible;
228
+ if (searchVisible) {
229
+ setTimeout(() => {
230
+ searchInputRef?.focus();
231
+ // Auto-submit if there's already a query
232
+ if (searchQuery.trim()) {
233
+ performSearch();
234
+ }
235
+ }, 100);
236
+ }
237
+ // Don't clear search state on close - preserve inputs
238
+ }
239
+
240
+ // Search functions
241
+ async function performSearch() {
242
+ if (!searchQuery.trim()) {
243
+ fileSearchResults = [];
244
+ codeSearchResults = [];
245
+ submittedQuery = '';
246
+ return;
247
+ }
248
+
249
+ const projectPath = projectState.currentProject?.path;
250
+ if (!projectPath) return;
251
+
252
+ submittedQuery = searchQuery.trim();
253
+ isSearching = true;
254
+
255
+ searchAbortController = new AbortController();
256
+
257
+ try {
258
+ if (searchMode === 'files') {
259
+ const results = await ws.http('files:search-files', {
260
+ project_path: projectPath,
261
+ query: submittedQuery
262
+ });
263
+
264
+ fileSearchResults = results || [];
265
+ } else {
266
+ // Only apply filters when the filter panel is visible
267
+ const includePattern = showFilters ? (filesToInclude || undefined) : undefined;
268
+ const excludePattern = showFilters ? (filesToExclude || undefined) : undefined;
269
+ const data = await ws.http('files:search-code', {
270
+ project_path: projectPath,
271
+ query: submittedQuery,
272
+ case_sensitive: caseSensitive,
273
+ whole_word: wholeWord,
274
+ use_regex: useRegex,
275
+ include_pattern: includePattern,
276
+ exclude_pattern: excludePattern
277
+ });
278
+
279
+ codeSearchResults = data || [];
280
+ }
281
+ } catch (error) {
282
+ if (searchAbortController?.signal.aborted) {
283
+ return;
284
+ }
285
+ addNotification({
286
+ type: 'error',
287
+ title: 'Search Failed',
288
+ message: error instanceof Error ? error.message : 'Search failed',
289
+ duration: 3000
290
+ });
291
+ } finally {
292
+ isSearching = false;
293
+ searchAbortController = null;
294
+ }
295
+ }
296
+
297
+ function cancelSearch() {
298
+ if (searchAbortController) {
299
+ searchAbortController.abort();
300
+ searchAbortController = null;
301
+ }
302
+ isSearching = false;
303
+ }
304
+
305
+ async function handleReplaceAll() {
306
+ if (!submittedQuery) return;
307
+
308
+ const projectPath = projectState.currentProject?.path;
309
+ if (!projectPath) return;
310
+
311
+ const confirmed = await showConfirm({
312
+ title: 'Replace All',
313
+ message: `Replace all occurrences of "${submittedQuery}" with "${replaceQuery}" across all matching files? This action cannot be undone.`,
314
+ type: 'warning',
315
+ confirmText: 'Replace All',
316
+ cancelText: 'Cancel'
317
+ });
318
+
319
+ if (!confirmed) return;
320
+
321
+ // Respect showFilters state - same as search
322
+ const includePattern = showFilters ? (filesToInclude || undefined) : undefined;
323
+ const excludePattern = showFilters ? (filesToExclude || undefined) : undefined;
324
+
325
+ isReplacing = true;
326
+ try {
327
+ const result = await ws.http('files:replace-in-files', {
328
+ project_path: projectPath,
329
+ search_query: submittedQuery,
330
+ replace_with: replaceQuery,
331
+ case_sensitive: caseSensitive,
332
+ whole_word: wholeWord,
333
+ use_regex: useRegex,
334
+ include_pattern: includePattern,
335
+ exclude_pattern: excludePattern
336
+ });
337
+
338
+ addNotification({
339
+ type: 'success',
340
+ title: 'Replace Complete',
341
+ message: `Replaced ${result.totalReplacements} occurrences in ${result.totalFiles} files`,
342
+ duration: 5000
343
+ });
344
+
345
+ onRefresh?.();
346
+ await performSearch();
347
+ } catch (error) {
348
+ addNotification({
349
+ type: 'error',
350
+ title: 'Replace Failed',
351
+ message: error instanceof Error ? error.message : 'Replace failed',
352
+ duration: 5000
353
+ });
354
+ } finally {
355
+ isReplacing = false;
356
+ }
357
+ }
358
+
359
+ function handleSearchKeydown(e: KeyboardEvent) {
360
+ if (e.key === 'Enter') {
361
+ e.preventDefault();
362
+ performSearch();
363
+ } else if (e.key === 'Escape') {
364
+ clearSearch();
365
+ }
366
+ }
367
+
368
+ function clearSearch() {
369
+ searchQuery = '';
370
+ submittedQuery = '';
371
+ fileSearchResults = [];
372
+ codeSearchResults = [];
373
+ }
374
+
375
+ function switchSearchMode(mode: 'files' | 'code') {
376
+ searchMode = mode;
377
+ // Clear results but keep query, then auto-search
378
+ fileSearchResults = [];
379
+ codeSearchResults = [];
380
+ submittedQuery = '';
381
+ if (searchQuery.trim()) {
382
+ performSearch();
383
+ }
384
+ }
385
+
386
+ // Search result handlers
387
+ function handleFileClick(result: any) {
388
+ if (onFileOpen) {
389
+ onFileOpen(result.path);
390
+ }
391
+ }
392
+
393
+ function handleCodeMatchClick(result: any, match: any) {
394
+ const projectPath = projectState.currentProject?.path || '';
395
+ const separator = projectPath.includes('\\') ? '\\' : '/';
396
+ // Normalize relativePath to use the OS-appropriate separator
397
+ const relPath = separator === '\\'
398
+ ? result.relativePath.replace(/\//g, '\\')
399
+ : result.relativePath.replace(/\\/g, '/');
400
+ const fullPath = `${projectPath}${separator}${relPath}`;
401
+
402
+ if (onFileOpen) {
403
+ onFileOpen(fullPath, match.line);
404
+ }
405
+ }
406
+
407
+ // Public method for search toggle
408
+ export function focusSearch(mode?: 'files' | 'code') {
409
+ searchVisible = true;
410
+ if (mode) {
411
+ searchMode = mode;
412
+ }
413
+ setTimeout(() => searchInputRef?.focus(), 100);
414
+ }
415
+
416
+ // Public method for "Find in Folder"
417
+ export function openFindInFolder(folderRelativePath: string) {
418
+ searchVisible = true;
419
+ searchMode = 'code';
420
+ filesToInclude = folderRelativePath;
421
+ showFilters = true;
422
+ setTimeout(() => searchInputRef?.focus(), 100);
423
+ }
424
+ </script>
425
+
426
+ <div class="relative flex flex-col h-full overflow-hidden">
427
+ <!-- Modern Header -->
428
+ <div class="px-5 py-3 border-b border-slate-200 dark:border-slate-700">
429
+ <div class="flex items-start justify-between gap-2">
430
+ <div class="flex-1 min-w-0">
431
+ <h3 class="text-sm font-bold text-slate-900 dark:text-slate-100">
432
+ {projectState.currentProject?.name}
433
+ </h3>
434
+ <p class="text-xs text-slate-600 dark:text-slate-400 mt-0.5 font-mono truncate">
435
+ {projectState.currentProject?.path}
436
+ </p>
437
+ </div>
438
+ <div class="flex items-center gap-1">
439
+ {#if onNewFileInRoot}
440
+ <button
441
+ class="flex flex-shrink-0 p-1.5 text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30 rounded-md transition-colors"
442
+ onclick={onNewFileInRoot}
443
+ title="New File"
444
+ >
445
+ <Icon name="lucide:file-plus" class="w-4 h-4" />
446
+ </button>
447
+ {/if}
448
+ {#if onNewFolderInRoot}
449
+ <button
450
+ class="flex flex-shrink-0 p-1.5 text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30 rounded-md transition-colors"
451
+ onclick={onNewFolderInRoot}
452
+ title="New Folder"
453
+ >
454
+ <Icon name="lucide:folder-plus" class="w-4 h-4" />
455
+ </button>
456
+ {/if}
457
+ <!-- Search toggle button -->
458
+ <button
459
+ class="flex flex-shrink-0 p-1.5 rounded-md transition-colors {searchVisible ? 'text-violet-600 dark:text-violet-400 bg-violet-50 dark:bg-violet-900/30' : 'text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30'}"
460
+ onclick={toggleSearch}
461
+ title="Search"
462
+ >
463
+ <Icon name="lucide:search" class="w-4 h-4" />
464
+ </button>
465
+ {#if hasClipboard && onPasteToRoot}
466
+ <button
467
+ class="flex flex-shrink-0 p-1.5 text-slate-600 dark:text-slate-400 hover:text-violet-600 dark:hover:text-violet-400 hover:bg-violet-50 dark:hover:bg-violet-900/30 rounded-md transition-colors"
468
+ onclick={onPasteToRoot}
469
+ title="Paste to root"
470
+ >
471
+ <Icon name="lucide:clipboard" class="w-4 h-4" />
472
+ </button>
473
+ {/if}
474
+ </div>
475
+ </div>
476
+ </div>
477
+
478
+ <!-- Search Bar (toggle) -->
479
+ {#if searchVisible}
480
+ <div class="px-3 py-2 border-b border-slate-200 dark:border-slate-700 bg-slate-50 dark:bg-slate-800/50">
481
+ <!-- Search Input -->
482
+ <div class="relative mb-2">
483
+ <input
484
+ bind:this={searchInputRef}
485
+ bind:value={searchQuery}
486
+ onkeydown={handleSearchKeydown}
487
+ type="text"
488
+ placeholder="Search {searchMode === 'files' ? 'files...' : 'code...'}"
489
+ class="w-full pl-3 pr-16 py-1.5 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-md focus:outline-none focus:ring focus:ring-violet-500 dark:focus:ring-violet-400 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500"
490
+ />
491
+ <div class="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1.5">
492
+ {#if submittedQuery}
493
+ <button
494
+ onclick={clearSearch}
495
+ class="p-0.5 text-slate-400 hover:text-slate-600 dark:hover:text-slate-300 transition-colors"
496
+ title="Clear search"
497
+ >
498
+ <Icon name="lucide:x" class="w-3.5 h-3.5" />
499
+ </button>
500
+ {/if}
501
+ {#if isSearching}
502
+ <button
503
+ onclick={cancelSearch}
504
+ class="px-1.5 py-0.5 text-xs font-medium rounded transition-colors bg-red-600 text-white hover:bg-red-700"
505
+ title="Cancel search"
506
+ >
507
+ <Icon name="lucide:circle-stop" class="w-3.5 h-3.5" />
508
+ </button>
509
+ {:else}
510
+ <button
511
+ onclick={performSearch}
512
+ disabled={!searchQuery.trim()}
513
+ class="px-1.5 py-0.5 text-xs font-medium rounded transition-colors {searchQuery.trim() ? 'bg-violet-600 text-white hover:bg-violet-700' : 'bg-slate-300 dark:bg-slate-700 text-slate-500 dark:text-slate-400 cursor-not-allowed'}"
514
+ title="Search (Enter)"
515
+ >
516
+ <Icon name="lucide:arrow-right" class="w-3.5 h-3.5" />
517
+ </button>
518
+ {/if}
519
+ </div>
520
+ </div>
521
+
522
+ <!-- Replace input (code mode only) -->
523
+ {#if searchMode === 'code' && showReplace}
524
+ <div class="relative mb-2">
525
+ <input
526
+ bind:value={replaceQuery}
527
+ type="text"
528
+ placeholder="Replace with..."
529
+ class="w-full pl-3 pr-16 py-1.5 text-sm bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-md focus:outline-none focus:ring focus:ring-violet-500 dark:focus:ring-violet-400 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500"
530
+ />
531
+ <div class="absolute right-2 top-1/2 -translate-y-1/2">
532
+ <button
533
+ onclick={handleReplaceAll}
534
+ disabled={!submittedQuery || isReplacing}
535
+ class="p-0.5 rounded transition-colors {submittedQuery && !isReplacing ? 'text-orange-600 dark:text-orange-400 hover:bg-orange-50 dark:hover:bg-orange-900/30' : 'text-slate-400 dark:text-slate-600 cursor-not-allowed'}"
536
+ title="Replace All"
537
+ >
538
+ {#if isReplacing}
539
+ <div class="w-3.5 h-3.5 border-2 border-orange-600 border-t-transparent rounded-full animate-spin"></div>
540
+ {:else}
541
+ <Icon name="lucide:replace-all" class="w-3.5 h-3.5" />
542
+ {/if}
543
+ </button>
544
+ </div>
545
+ </div>
546
+ {/if}
547
+
548
+ <!-- Search Mode Toggle & Options -->
549
+ <div class="flex items-center gap-2">
550
+ <div class="flex bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-md overflow-hidden">
551
+ <button
552
+ onclick={() => switchSearchMode('files')}
553
+ class="px-3 py-1 text-xs font-medium transition-colors {searchMode === 'files' ? 'bg-violet-600 text-white' : 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
554
+ >
555
+ Files
556
+ </button>
557
+ <button
558
+ onclick={() => switchSearchMode('code')}
559
+ class="px-3 py-1 text-xs font-medium transition-colors {searchMode === 'code' ? 'bg-violet-600 text-white' : 'text-slate-600 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
560
+ >
561
+ Code
562
+ </button>
563
+ </div>
564
+
565
+ {#if searchMode === 'code'}
566
+ <div class="flex items-center gap-1 ml-auto">
567
+ <button
568
+ onclick={() => { caseSensitive = !caseSensitive; }}
569
+ class="font-medium text-xs w-6 flex p-1 rounded transition-colors {caseSensitive ? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
570
+ title="Match Case"
571
+ >
572
+ Aa
573
+ </button>
574
+ <button
575
+ onclick={() => { wholeWord = !wholeWord; }}
576
+ class="underline underline-offset-2 font-medium text-xs w-6 flex p-1 rounded transition-colors {wholeWord ? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
577
+ title="Match Whole Word"
578
+ >
579
+ Ab
580
+ </button>
581
+ <button
582
+ onclick={() => { useRegex = !useRegex; }}
583
+ class="flex p-1 rounded transition-colors {useRegex ? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
584
+ title="Use Regular Expression"
585
+ >
586
+ <Icon name="lucide:regex" class="w-3.5 h-3.5" />
587
+ </button>
588
+ <button
589
+ onclick={() => { showReplace = !showReplace; }}
590
+ class="flex p-1 rounded transition-colors {showReplace ? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
591
+ title="Toggle Replace"
592
+ >
593
+ <Icon name="lucide:replace" class="w-3.5 h-3.5" />
594
+ </button>
595
+ <button
596
+ onclick={() => { showFilters = !showFilters; }}
597
+ class="flex p-1 rounded transition-colors {showFilters ? 'bg-violet-100 dark:bg-violet-900/30 text-violet-700 dark:text-violet-400' : 'text-slate-500 dark:text-slate-400 hover:bg-slate-100 dark:hover:bg-slate-800'}"
598
+ title="Toggle File Filters"
599
+ >
600
+ <Icon name="lucide:filter" class="w-3.5 h-3.5" />
601
+ </button>
602
+ </div>
603
+ {/if}
604
+ </div>
605
+
606
+ <!-- File filters (code mode only) - use hidden to preserve input values -->
607
+ <div class="mt-2 space-y-1.5" class:hidden={!(searchMode === 'code' && showFilters)}>
608
+ <input
609
+ bind:value={filesToInclude}
610
+ type="text"
611
+ placeholder="files to include (e.g. *.ts, src/)"
612
+ class="w-full px-2.5 py-1 text-xs bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-md focus:outline-none focus:ring-1 focus:ring-violet-500 dark:focus:ring-violet-400 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500"
613
+ />
614
+ <input
615
+ bind:value={filesToExclude}
616
+ type="text"
617
+ placeholder="files to exclude (e.g. *.min.js, *.map)"
618
+ class="w-full px-2.5 py-1 text-xs bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-700 rounded-md focus:outline-none focus:ring-1 focus:ring-violet-500 dark:focus:ring-violet-400 text-slate-900 dark:text-slate-100 placeholder-slate-400 dark:placeholder-slate-500"
619
+ />
620
+ </div>
621
+ </div>
622
+ {/if}
623
+
624
+ <!-- Search Results OR File Tree -->
625
+ {#if searchVisible}
626
+ <!-- When search is visible, always show search area (no file tree) -->
627
+ {#if submittedQuery || isSearching}
628
+ <div class="flex-1 overflow-hidden">
629
+ <SearchResults
630
+ mode={searchMode}
631
+ query={submittedQuery}
632
+ fileResults={fileSearchResults}
633
+ codeResults={codeSearchResults}
634
+ isLoading={isSearching}
635
+ {useRegex}
636
+ onFileClick={handleFileClick}
637
+ onCodeMatchClick={handleCodeMatchClick}
638
+ />
639
+ </div>
640
+ {:else}
641
+ <div class="flex-1 flex flex-col items-center justify-center gap-3 text-slate-500 dark:text-slate-400 px-6">
642
+ <Icon name="lucide:search" class="w-8 h-8 opacity-40" />
643
+ <p class="text-sm text-center">Enter a search query to find {searchMode === 'files' ? 'files' : 'code'}</p>
644
+ </div>
645
+ {/if}
646
+ {:else}
647
+ <div class="overflow-auto flex-1 p-2 select-none">
648
+ {#if files.length === 0}
649
+ <div class="text-center py-12">
650
+ <div class="bg-slate-100 dark:bg-slate-800 rounded-full w-16 h-16 flex items-center justify-center mx-auto mb-4">
651
+ <Icon name="lucide:file-text" class="w-8 h-8 text-slate-400" />
652
+ </div>
653
+ <p class="text-sm font-semibold text-slate-600 dark:text-slate-300">No files in project</p>
654
+ <p class="text-xs text-slate-500 dark:text-slate-400 mt-1">
655
+ Create a file or folder to get started
656
+ </p>
657
+ </div>
658
+ {:else}
659
+ <div class="space-y-1 h-0">
660
+ {#each files as file (file.path)}
661
+ <FileNode
662
+ {file}
663
+ isSelected={activeFilePath ? file.path === activeFilePath : selectedFile?.path === file.path}
664
+ isExpanded={localExpandedFolders.has(file.path)}
665
+ isModified={modifiedFiles.has(file.path)}
666
+ {openMenuPath}
667
+ expandedFolders={localExpandedFolders}
668
+ onSelect={handleFileSelect}
669
+ onAction={handleFileAction}
670
+ onToggle={toggleFolder}
671
+ onMenuToggle={handleMenuToggle}
672
+ {hasClipboard}
673
+ {modifiedFiles}
674
+ {activeFilePath}
675
+ />
676
+ {/each}
677
+ </div>
678
+ {/if}
679
+ </div>
680
+ {/if}
681
+ </div>