@myrialabs/clopen 0.0.4 → 0.0.6

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 (453) hide show
  1. package/.env.example +5 -5
  2. package/.github/workflows/{release.yml → ci.yml} +86 -60
  3. package/CONTRIBUTING.md +499 -499
  4. package/LICENSE +21 -21
  5. package/README.md +209 -209
  6. package/backend/index.ts +168 -156
  7. package/backend/lib/chat/helpers.ts +42 -42
  8. package/backend/lib/chat/index.ts +1 -1
  9. package/backend/lib/chat/stream-manager.ts +1126 -1126
  10. package/backend/lib/database/README.md +76 -76
  11. package/backend/lib/database/index.ts +118 -118
  12. package/backend/lib/database/migrations/001_create_projects_table.ts +30 -30
  13. package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +32 -32
  14. package/backend/lib/database/migrations/003_create_messages_table.ts +31 -31
  15. package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -34
  16. package/backend/lib/database/migrations/005_create_settings_table.ts +23 -23
  17. package/backend/lib/database/migrations/006_add_user_to_messages.ts +57 -57
  18. package/backend/lib/database/migrations/007_create_stream_states_table.ts +40 -40
  19. package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +61 -61
  20. package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -41
  21. package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -70
  22. package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -156
  23. package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -41
  24. package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -118
  25. package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -18
  26. package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -18
  27. package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -34
  28. package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -32
  29. package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -24
  30. package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -18
  31. package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -32
  32. package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -33
  33. package/backend/lib/database/migrations/index.ts +153 -153
  34. package/backend/lib/database/queries/checkpoint-queries.ts +87 -87
  35. package/backend/lib/database/queries/engine-queries.ts +75 -75
  36. package/backend/lib/database/queries/index.ts +8 -8
  37. package/backend/lib/database/queries/message-queries.ts +471 -471
  38. package/backend/lib/database/queries/project-queries.ts +117 -117
  39. package/backend/lib/database/queries/session-queries.ts +270 -270
  40. package/backend/lib/database/queries/settings-queries.ts +33 -33
  41. package/backend/lib/database/queries/snapshot-queries.ts +325 -325
  42. package/backend/lib/database/queries/utils-queries.ts +58 -58
  43. package/backend/lib/database/seeders/index.ts +12 -12
  44. package/backend/lib/database/seeders/settings_seeder.ts +83 -83
  45. package/backend/lib/database/utils/connection.ts +173 -173
  46. package/backend/lib/database/utils/index.ts +3 -3
  47. package/backend/lib/database/utils/migration-runner.ts +117 -117
  48. package/backend/lib/database/utils/seeder-runner.ts +120 -120
  49. package/backend/lib/engine/adapters/claude/environment.ts +160 -164
  50. package/backend/lib/engine/adapters/claude/error-handler.ts +60 -60
  51. package/backend/lib/engine/adapters/claude/index.ts +1 -1
  52. package/backend/lib/engine/adapters/claude/path-utils.ts +38 -38
  53. package/backend/lib/engine/adapters/claude/stream.ts +177 -177
  54. package/backend/lib/engine/adapters/opencode/index.ts +2 -2
  55. package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -862
  56. package/backend/lib/engine/adapters/opencode/server.ts +104 -104
  57. package/backend/lib/engine/adapters/opencode/stream.ts +755 -755
  58. package/backend/lib/engine/index.ts +196 -196
  59. package/backend/lib/engine/types.ts +58 -58
  60. package/backend/lib/files/file-operations.ts +478 -478
  61. package/backend/lib/files/file-reading.ts +308 -308
  62. package/backend/lib/files/file-watcher.ts +383 -383
  63. package/backend/lib/files/path-browsing.ts +382 -382
  64. package/backend/lib/git/git-executor.ts +89 -88
  65. package/backend/lib/git/git-parser.ts +411 -411
  66. package/backend/lib/git/git-service.ts +505 -505
  67. package/backend/lib/mcp/README.md +1144 -1144
  68. package/backend/lib/mcp/config.ts +317 -316
  69. package/backend/lib/mcp/index.ts +35 -35
  70. package/backend/lib/mcp/project-context.ts +236 -236
  71. package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -156
  72. package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -419
  73. package/backend/lib/mcp/servers/browser-automation/index.ts +791 -791
  74. package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -501
  75. package/backend/lib/mcp/servers/helper.ts +143 -143
  76. package/backend/lib/mcp/servers/index.ts +44 -44
  77. package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -56
  78. package/backend/lib/mcp/servers/weather/index.ts +31 -31
  79. package/backend/lib/mcp/stdio-server.ts +103 -103
  80. package/backend/lib/mcp/types.ts +65 -65
  81. package/backend/lib/preview/browser/browser-audio-capture.ts +86 -86
  82. package/backend/lib/preview/browser/browser-console-manager.ts +262 -262
  83. package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -222
  84. package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -421
  85. package/backend/lib/preview/browser/browser-mcp-control.ts +415 -415
  86. package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -512
  87. package/backend/lib/preview/browser/browser-navigation-tracker.ts +103 -103
  88. package/backend/lib/preview/browser/browser-pool.ts +357 -357
  89. package/backend/lib/preview/browser/browser-preview-service.ts +882 -882
  90. package/backend/lib/preview/browser/browser-tab-manager.ts +935 -935
  91. package/backend/lib/preview/browser/browser-video-capture.ts +695 -695
  92. package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -292
  93. package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -85
  94. package/backend/lib/preview/browser/scripts/video-stream.ts +438 -438
  95. package/backend/lib/preview/browser/types.ts +359 -359
  96. package/backend/lib/preview/index.ts +23 -23
  97. package/backend/lib/project/index.ts +1 -1
  98. package/backend/lib/project/status-manager.ts +181 -181
  99. package/backend/lib/shared/env.ts +117 -0
  100. package/backend/lib/shared/index.ts +5 -2
  101. package/backend/lib/shared/port-utils.ts +25 -25
  102. package/backend/lib/shared/process-manager.ts +280 -280
  103. package/backend/lib/snapshot/blob-store.ts +227 -227
  104. package/backend/lib/snapshot/gitignore.ts +307 -307
  105. package/backend/lib/snapshot/helpers.ts +397 -397
  106. package/backend/lib/snapshot/snapshot-service.ts +483 -483
  107. package/backend/lib/terminal/helpers.ts +14 -14
  108. package/backend/lib/terminal/index.ts +7 -7
  109. package/backend/lib/terminal/pty-manager.ts +3 -3
  110. package/backend/lib/terminal/pty-session-manager.ts +370 -387
  111. package/backend/lib/terminal/shell-utils.ts +315 -312
  112. package/backend/lib/terminal/stream-manager.ts +292 -292
  113. package/backend/lib/tunnel/global-tunnel-manager.ts +266 -243
  114. package/backend/lib/tunnel/project-tunnel-manager.ts +311 -311
  115. package/backend/lib/user/helpers.ts +87 -87
  116. package/backend/lib/utils/ws.ts +944 -944
  117. package/backend/lib/vite-dev.ts +295 -295
  118. package/backend/middleware/cors.ts +16 -15
  119. package/backend/middleware/error-handler.ts +50 -49
  120. package/backend/middleware/logger.ts +9 -9
  121. package/backend/types/api.ts +24 -24
  122. package/backend/ws/README.md +1505 -1505
  123. package/backend/ws/chat/background.ts +198 -198
  124. package/backend/ws/chat/index.ts +21 -21
  125. package/backend/ws/chat/stream.ts +707 -707
  126. package/backend/ws/engine/claude/accounts.ts +399 -401
  127. package/backend/ws/engine/claude/index.ts +13 -13
  128. package/backend/ws/engine/claude/status.ts +43 -43
  129. package/backend/ws/engine/index.ts +14 -14
  130. package/backend/ws/engine/opencode/index.ts +11 -11
  131. package/backend/ws/engine/opencode/status.ts +30 -30
  132. package/backend/ws/engine/utils.ts +36 -36
  133. package/backend/ws/files/index.ts +30 -30
  134. package/backend/ws/files/read.ts +189 -189
  135. package/backend/ws/files/search.ts +453 -453
  136. package/backend/ws/files/watch.ts +124 -124
  137. package/backend/ws/files/write.ts +143 -143
  138. package/backend/ws/git/branch.ts +106 -106
  139. package/backend/ws/git/commit.ts +39 -39
  140. package/backend/ws/git/conflict.ts +68 -68
  141. package/backend/ws/git/diff.ts +69 -69
  142. package/backend/ws/git/index.ts +24 -24
  143. package/backend/ws/git/log.ts +41 -41
  144. package/backend/ws/git/remote.ts +214 -214
  145. package/backend/ws/git/staging.ts +84 -84
  146. package/backend/ws/git/status.ts +90 -90
  147. package/backend/ws/index.ts +69 -69
  148. package/backend/ws/mcp/index.ts +61 -61
  149. package/backend/ws/messages/crud.ts +74 -74
  150. package/backend/ws/messages/index.ts +14 -14
  151. package/backend/ws/preview/browser/cleanup.ts +129 -129
  152. package/backend/ws/preview/browser/console.ts +114 -114
  153. package/backend/ws/preview/browser/interact.ts +513 -513
  154. package/backend/ws/preview/browser/mcp.ts +129 -129
  155. package/backend/ws/preview/browser/native-ui.ts +235 -235
  156. package/backend/ws/preview/browser/stats.ts +55 -55
  157. package/backend/ws/preview/browser/tab-info.ts +126 -126
  158. package/backend/ws/preview/browser/tab.ts +166 -166
  159. package/backend/ws/preview/browser/webcodecs.ts +293 -293
  160. package/backend/ws/preview/index.ts +146 -146
  161. package/backend/ws/projects/crud.ts +113 -113
  162. package/backend/ws/projects/index.ts +25 -25
  163. package/backend/ws/projects/presence.ts +46 -46
  164. package/backend/ws/projects/status.ts +116 -116
  165. package/backend/ws/sessions/crud.ts +327 -327
  166. package/backend/ws/sessions/index.ts +33 -33
  167. package/backend/ws/settings/crud.ts +112 -112
  168. package/backend/ws/settings/index.ts +14 -14
  169. package/backend/ws/snapshot/index.ts +17 -17
  170. package/backend/ws/snapshot/restore.ts +173 -173
  171. package/backend/ws/snapshot/timeline.ts +141 -141
  172. package/backend/ws/system/index.ts +14 -14
  173. package/backend/ws/system/operations.ts +49 -49
  174. package/backend/ws/terminal/index.ts +40 -40
  175. package/backend/ws/terminal/persistence.ts +153 -153
  176. package/backend/ws/terminal/session.ts +382 -382
  177. package/backend/ws/terminal/stream.ts +79 -79
  178. package/backend/ws/tunnel/index.ts +14 -14
  179. package/backend/ws/tunnel/operations.ts +91 -91
  180. package/backend/ws/types.ts +20 -20
  181. package/backend/ws/user/crud.ts +156 -156
  182. package/backend/ws/user/index.ts +14 -14
  183. package/bin/clopen.ts +307 -307
  184. package/bun.lock +1353 -1352
  185. package/frontend/App.svelte +38 -34
  186. package/frontend/app.css +313 -313
  187. package/frontend/lib/app-environment.ts +10 -10
  188. package/frontend/lib/components/chat/ChatInterface.svelte +406 -406
  189. package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +56 -56
  190. package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +223 -223
  191. package/frontend/lib/components/chat/formatters/TextMessage.svelte +394 -394
  192. package/frontend/lib/components/chat/formatters/Tools.svelte +69 -69
  193. package/frontend/lib/components/chat/formatters/index.ts +2 -2
  194. package/frontend/lib/components/chat/input/ChatInput.svelte +421 -421
  195. package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -78
  196. package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -30
  197. package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -33
  198. package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -619
  199. package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -48
  200. package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -31
  201. package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -201
  202. package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -148
  203. package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -216
  204. package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -357
  205. package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -57
  206. package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -478
  207. package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -541
  208. package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -86
  209. package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -86
  210. package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -157
  211. package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -59
  212. package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -124
  213. package/frontend/lib/components/chat/shared/index.ts +1 -1
  214. package/frontend/lib/components/chat/shared/utils.ts +115 -115
  215. package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -35
  216. package/frontend/lib/components/chat/tools/BashTool.svelte +45 -45
  217. package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -139
  218. package/frontend/lib/components/chat/tools/EditTool.svelte +47 -47
  219. package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +31 -31
  220. package/frontend/lib/components/chat/tools/GlobTool.svelte +50 -50
  221. package/frontend/lib/components/chat/tools/GrepTool.svelte +89 -89
  222. package/frontend/lib/components/chat/tools/KillShellTool.svelte +25 -25
  223. package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +30 -30
  224. package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +37 -37
  225. package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +33 -33
  226. package/frontend/lib/components/chat/tools/ReadTool.svelte +40 -40
  227. package/frontend/lib/components/chat/tools/TaskTool.svelte +63 -63
  228. package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +74 -74
  229. package/frontend/lib/components/chat/tools/WebFetchTool.svelte +34 -34
  230. package/frontend/lib/components/chat/tools/WebSearchTool.svelte +83 -83
  231. package/frontend/lib/components/chat/tools/WriteTool.svelte +32 -32
  232. package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +78 -78
  233. package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +407 -407
  234. package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -45
  235. package/frontend/lib/components/chat/tools/components/InfoLine.svelte +18 -18
  236. package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +26 -26
  237. package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +53 -53
  238. package/frontend/lib/components/chat/tools/components/index.ts +7 -7
  239. package/frontend/lib/components/chat/tools/index.ts +25 -25
  240. package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +248 -248
  241. package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -78
  242. package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -391
  243. package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -26
  244. package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +86 -86
  245. package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -108
  246. package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -59
  247. package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -168
  248. package/frontend/lib/components/checkpoint/timeline/config.ts +44 -44
  249. package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -304
  250. package/frontend/lib/components/checkpoint/timeline/types.ts +65 -65
  251. package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -53
  252. package/frontend/lib/components/common/Alert.svelte +138 -138
  253. package/frontend/lib/components/common/AvatarBubble.svelte +55 -55
  254. package/frontend/lib/components/common/Button.svelte +71 -71
  255. package/frontend/lib/components/common/Card.svelte +102 -102
  256. package/frontend/lib/components/common/Checkbox.svelte +48 -48
  257. package/frontend/lib/components/common/Dialog.svelte +248 -248
  258. package/frontend/lib/components/common/FolderBrowser.svelte +842 -842
  259. package/frontend/lib/components/common/Icon.svelte +57 -57
  260. package/frontend/lib/components/common/Input.svelte +72 -72
  261. package/frontend/lib/components/common/Lightbox.svelte +232 -232
  262. package/frontend/lib/components/common/LoadingScreen.svelte +52 -52
  263. package/frontend/lib/components/common/LoadingSpinner.svelte +48 -48
  264. package/frontend/lib/components/common/Modal.svelte +177 -177
  265. package/frontend/lib/components/common/ModalProvider.svelte +27 -27
  266. package/frontend/lib/components/common/ModelSelector.svelte +110 -110
  267. package/frontend/lib/components/common/MonacoEditor.svelte +568 -568
  268. package/frontend/lib/components/common/NotificationToast.svelte +113 -113
  269. package/frontend/lib/components/common/PageTemplate.svelte +75 -75
  270. package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -79
  271. package/frontend/lib/components/common/Select.svelte +97 -97
  272. package/frontend/lib/components/common/Textarea.svelte +79 -79
  273. package/frontend/lib/components/common/ThemeToggle.svelte +44 -44
  274. package/frontend/lib/components/common/lucide-icons.ts +1642 -1642
  275. package/frontend/lib/components/common/material-icons.ts +1082 -1082
  276. package/frontend/lib/components/common/xterm/XTerm.svelte +809 -795
  277. package/frontend/lib/components/common/xterm/index.ts +15 -15
  278. package/frontend/lib/components/common/xterm/terminal-config.ts +67 -67
  279. package/frontend/lib/components/common/xterm/types.ts +30 -30
  280. package/frontend/lib/components/common/xterm/xterm-service.ts +379 -353
  281. package/frontend/lib/components/files/FileNode.svelte +383 -383
  282. package/frontend/lib/components/files/FileTree.svelte +681 -681
  283. package/frontend/lib/components/files/FileViewer.svelte +728 -728
  284. package/frontend/lib/components/files/SearchResults.svelte +303 -303
  285. package/frontend/lib/components/git/BranchManager.svelte +458 -458
  286. package/frontend/lib/components/git/ChangesSection.svelte +107 -107
  287. package/frontend/lib/components/git/CommitForm.svelte +76 -76
  288. package/frontend/lib/components/git/ConflictResolver.svelte +158 -158
  289. package/frontend/lib/components/git/DiffViewer.svelte +364 -364
  290. package/frontend/lib/components/git/FileChangeItem.svelte +97 -97
  291. package/frontend/lib/components/git/GitButton.svelte +33 -33
  292. package/frontend/lib/components/git/GitLog.svelte +361 -361
  293. package/frontend/lib/components/git/GitModal.svelte +80 -80
  294. package/frontend/lib/components/history/HistoryModal.svelte +563 -563
  295. package/frontend/lib/components/history/HistoryView.svelte +614 -614
  296. package/frontend/lib/components/index.ts +34 -34
  297. package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -549
  298. package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -1058
  299. package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +756 -756
  300. package/frontend/lib/components/preview/browser/components/Container.svelte +450 -450
  301. package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -236
  302. package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -224
  303. package/frontend/lib/components/preview/browser/components/Toolbar.svelte +338 -338
  304. package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +35 -35
  305. package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -155
  306. package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -837
  307. package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -113
  308. package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -296
  309. package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -391
  310. package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -231
  311. package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -210
  312. package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -239
  313. package/frontend/lib/components/preview/index.ts +1 -1
  314. package/frontend/lib/components/settings/SettingsModal.svelte +235 -235
  315. package/frontend/lib/components/settings/SettingsView.svelte +36 -36
  316. package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -51
  317. package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -160
  318. package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -76
  319. package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -917
  320. package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -187
  321. package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -203
  322. package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -10
  323. package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -357
  324. package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -205
  325. package/frontend/lib/components/settings/user/UserSettings.svelte +197 -197
  326. package/frontend/lib/components/terminal/Terminal.svelte +367 -367
  327. package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -87
  328. package/frontend/lib/components/terminal/TerminalView.svelte +54 -54
  329. package/frontend/lib/components/tunnel/TunnelActive.svelte +157 -142
  330. package/frontend/lib/components/tunnel/TunnelButton.svelte +60 -54
  331. package/frontend/lib/components/tunnel/TunnelInactive.svelte +285 -284
  332. package/frontend/lib/components/tunnel/TunnelModal.svelte +48 -47
  333. package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -49
  334. package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -382
  335. package/frontend/lib/components/workspace/MobileNavigator.svelte +394 -403
  336. package/frontend/lib/components/workspace/PanelContainer.svelte +100 -100
  337. package/frontend/lib/components/workspace/PanelHeader.svelte +505 -505
  338. package/frontend/lib/components/workspace/ViewMenu.svelte +162 -162
  339. package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -169
  340. package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -15
  341. package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -17
  342. package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -42
  343. package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +84 -84
  344. package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -37
  345. package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -274
  346. package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -1261
  347. package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -1560
  348. package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -150
  349. package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -73
  350. package/frontend/lib/constants/preview.ts +44 -44
  351. package/frontend/lib/services/chat/chat.service.ts +704 -704
  352. package/frontend/lib/services/chat/index.ts +6 -6
  353. package/frontend/lib/services/notification/global-stream-monitor.ts +86 -86
  354. package/frontend/lib/services/notification/index.ts +7 -7
  355. package/frontend/lib/services/notification/push.service.ts +143 -143
  356. package/frontend/lib/services/notification/sound.service.ts +126 -126
  357. package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -61
  358. package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -1499
  359. package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -67
  360. package/frontend/lib/services/preview/index.ts +22 -22
  361. package/frontend/lib/services/project/index.ts +7 -7
  362. package/frontend/lib/services/project/status.service.ts +159 -159
  363. package/frontend/lib/services/snapshot/snapshot.service.ts +47 -47
  364. package/frontend/lib/services/terminal/background/index.ts +129 -129
  365. package/frontend/lib/services/terminal/background/session-restore.ts +273 -273
  366. package/frontend/lib/services/terminal/background/stream-manager.ts +285 -285
  367. package/frontend/lib/services/terminal/index.ts +13 -13
  368. package/frontend/lib/services/terminal/persistence.service.ts +260 -260
  369. package/frontend/lib/services/terminal/project.service.ts +952 -952
  370. package/frontend/lib/services/terminal/session.service.ts +363 -363
  371. package/frontend/lib/services/terminal/terminal.service.ts +369 -369
  372. package/frontend/lib/stores/core/app.svelte.ts +117 -117
  373. package/frontend/lib/stores/core/files.svelte.ts +72 -72
  374. package/frontend/lib/stores/core/presence.svelte.ts +48 -48
  375. package/frontend/lib/stores/core/projects.svelte.ts +317 -317
  376. package/frontend/lib/stores/core/sessions.svelte.ts +383 -383
  377. package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -58
  378. package/frontend/lib/stores/features/models.svelte.ts +89 -89
  379. package/frontend/lib/stores/features/settings.svelte.ts +87 -87
  380. package/frontend/lib/stores/features/terminal.svelte.ts +700 -700
  381. package/frontend/lib/stores/features/tunnel.svelte.ts +163 -161
  382. package/frontend/lib/stores/features/user.svelte.ts +95 -95
  383. package/frontend/lib/stores/ui/chat-input.svelte.ts +56 -56
  384. package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -61
  385. package/frontend/lib/stores/ui/dialog.svelte.ts +58 -58
  386. package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -214
  387. package/frontend/lib/stores/ui/notification.svelte.ts +166 -166
  388. package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -88
  389. package/frontend/lib/stores/ui/theme.svelte.ts +179 -179
  390. package/frontend/lib/stores/ui/workspace.svelte.ts +754 -754
  391. package/frontend/lib/types/native-ui.ts +73 -73
  392. package/frontend/lib/utils/chat/date-separator.ts +38 -38
  393. package/frontend/lib/utils/chat/message-grouper.ts +218 -218
  394. package/frontend/lib/utils/chat/message-processor.ts +134 -134
  395. package/frontend/lib/utils/chat/tool-handler.ts +160 -160
  396. package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -142
  397. package/frontend/lib/utils/click-outside.ts +20 -20
  398. package/frontend/lib/utils/context-manager.ts +256 -256
  399. package/frontend/lib/utils/file-icon-mappings.ts +768 -768
  400. package/frontend/lib/utils/folder-icon-mappings.ts +1029 -1029
  401. package/frontend/lib/utils/git-status.ts +68 -68
  402. package/frontend/lib/utils/platform.ts +112 -112
  403. package/frontend/lib/utils/port-check.ts +64 -64
  404. package/frontend/lib/utils/terminalFormatter.ts +206 -206
  405. package/frontend/lib/utils/theme.ts +6 -6
  406. package/frontend/lib/utils/tree-visualizer.ts +320 -320
  407. package/frontend/lib/utils/ws.ts +44 -44
  408. package/frontend/main.ts +13 -13
  409. package/index.html +70 -70
  410. package/package.json +111 -111
  411. package/scripts/generate-icons.ts +86 -86
  412. package/scripts/pre-publish-check.sh +142 -142
  413. package/scripts/setup-hooks.sh +134 -134
  414. package/scripts/validate-branch-name.sh +47 -47
  415. package/scripts/validate-commit-msg.sh +42 -42
  416. package/shared/constants/engines.ts +134 -134
  417. package/shared/types/database/connection.ts +15 -15
  418. package/shared/types/database/index.ts +5 -5
  419. package/shared/types/database/schema.ts +140 -140
  420. package/shared/types/engine/index.ts +45 -45
  421. package/shared/types/filesystem/index.ts +21 -21
  422. package/shared/types/git.ts +171 -171
  423. package/shared/types/messaging/index.ts +238 -238
  424. package/shared/types/messaging/tool.ts +525 -525
  425. package/shared/types/network/api.ts +17 -17
  426. package/shared/types/network/index.ts +4 -4
  427. package/shared/types/stores/app.ts +22 -22
  428. package/shared/types/stores/dialog.ts +20 -20
  429. package/shared/types/stores/index.ts +2 -2
  430. package/shared/types/stores/settings.ts +15 -15
  431. package/shared/types/terminal/index.ts +43 -43
  432. package/shared/types/ui/components.ts +60 -60
  433. package/shared/types/ui/icons.ts +22 -22
  434. package/shared/types/ui/index.ts +21 -21
  435. package/shared/types/ui/notifications.ts +13 -13
  436. package/shared/types/ui/theme.ts +11 -11
  437. package/shared/types/websocket/index.ts +43 -43
  438. package/shared/types/window.d.ts +12 -12
  439. package/shared/utils/anonymous-user.ts +167 -167
  440. package/shared/utils/async.ts +10 -10
  441. package/shared/utils/diff-calculator.ts +184 -184
  442. package/shared/utils/file-type-detection.ts +165 -165
  443. package/shared/utils/logger.ts +144 -158
  444. package/shared/utils/message-formatter.ts +79 -79
  445. package/shared/utils/path.ts +47 -47
  446. package/shared/utils/ws-client.ts +768 -768
  447. package/shared/utils/ws-server.ts +660 -660
  448. package/static/favicon.svg +7 -7
  449. package/static/fonts/dm-sans.css +96 -96
  450. package/svelte.config.js +20 -20
  451. package/tsconfig.json +41 -41
  452. package/vite.config.ts +33 -33
  453. package/.github/workflows/test.yml +0 -40
@@ -1,478 +1,478 @@
1
- import { dirname, extname, join } from 'path';
2
- import { stat as fsStat } from 'node:fs/promises';
3
-
4
- import { debug } from '$shared/utils/logger';
5
-
6
- // Import Bun native functions and create wrappers for better API compatibility
7
- const { spawn } = Bun;
8
-
9
- // Bun-compatible readdir implementation (cross-platform)
10
- async function readdir(path: string): Promise<string[]> {
11
- let proc;
12
- if (process.platform === 'win32') {
13
- proc = Bun.spawn(['cmd', '/c', 'dir', '/b', path], { stdout: 'pipe', stderr: 'ignore' });
14
- } else {
15
- proc = Bun.spawn(['ls', '-1', path], { stdout: 'pipe', stderr: 'ignore' });
16
- }
17
- const result = await new Response(proc.stdout).text();
18
- // Split and clean up, removing \r characters for Windows compatibility
19
- return result.trim().split(/\r?\n/).map(line => line.trim()).filter(Boolean);
20
- }
21
-
22
- async function mkdir(path: string, options?: { recursive?: boolean }) {
23
- // Pure Bun approach: create directory by writing a temp file
24
- try {
25
- const tempFile = join(path, '.bun_mkdir_temp');
26
- await Bun.write(tempFile, '');
27
- // Clean up the temp file
28
- try {
29
- if (process.platform === 'win32') {
30
- await spawn(['cmd', '/c', 'del', '/f', '/q', tempFile.replace(/\//g, '\\')], {
31
- stdout: 'ignore',
32
- stderr: 'ignore'
33
- }).exited;
34
- } else {
35
- await spawn(['rm', '-f', tempFile], {
36
- stdout: 'ignore',
37
- stderr: 'ignore'
38
- }).exited;
39
- }
40
- } catch {
41
- // Ignore cleanup errors
42
- }
43
- } catch (error) {
44
- throw new Error(`Failed to create directory ${path}: ${error}`);
45
- }
46
- }
47
-
48
- async function copyFile(src: string, dest: string) {
49
- const srcFile = Bun.file(src);
50
- const content = await srcFile.arrayBuffer();
51
- await Bun.write(dest, content);
52
- }
53
-
54
- async function copyDirectory(src: string, dest: string) {
55
- // Create destination directory
56
- await mkdir(dest, { recursive: true });
57
-
58
- // List items in source
59
- const items = await readdir(src);
60
-
61
- for (const item of items) {
62
- const srcPath = join(src, item);
63
- const destPath = join(dest, item);
64
-
65
- const file = Bun.file(srcPath);
66
- try {
67
- const stats = await file.stat();
68
- if (stats.isDirectory()) {
69
- await copyDirectory(srcPath, destPath);
70
- } else {
71
- await copyFile(srcPath, destPath);
72
- }
73
- } catch {
74
- // Skip items that can't be read
75
- continue;
76
- }
77
- }
78
- }
79
-
80
- async function rename(oldPath: string, newPath: string) {
81
- if (process.platform === 'win32') {
82
- const proc = spawn(['cmd', '/c', 'move', oldPath.replace(/\//g, '\\'), newPath.replace(/\//g, '\\')], { stdout: 'ignore', stderr: 'ignore' });
83
- await proc.exited;
84
- } else {
85
- const proc = spawn(['mv', oldPath, newPath], { stdout: 'ignore', stderr: 'ignore' });
86
- await proc.exited;
87
- }
88
- }
89
-
90
- async function unlink(path: string) {
91
- if (process.platform === 'win32') {
92
- const proc = spawn(['cmd', '/c', 'del', '/f', '/q', path.replace(/\//g, '\\')], { stdout: 'ignore', stderr: 'ignore' });
93
- await proc.exited;
94
- } else {
95
- const proc = spawn(['rm', '-f', path], { stdout: 'ignore', stderr: 'ignore' });
96
- await proc.exited;
97
- }
98
- }
99
-
100
- async function rm(path: string, options?: { recursive?: boolean }) {
101
- if (process.platform === 'win32') {
102
- const proc = spawn(['cmd', '/c', 'rmdir', '/s', '/q', path.replace(/\//g, '\\')], { stdout: 'ignore', stderr: 'ignore' });
103
- await proc.exited;
104
- } else {
105
- const proc = spawn(['rm', '-rf', path], { stdout: 'ignore', stderr: 'ignore' });
106
- await proc.exited;
107
- }
108
- }
109
-
110
- async function rmdir(path: string, options?: { recursive?: boolean }) {
111
- return rm(path, options);
112
- }
113
-
114
- export async function writeFileOperation(filePath: string, content: string) {
115
- if (!filePath) {
116
- throw new Error('File path is required');
117
- }
118
-
119
- if (typeof content !== 'string') {
120
- throw new Error('Content must be a string');
121
- }
122
-
123
- try {
124
- debug.log('file', 'Writing file:', { filePath, contentLength: content.length });
125
- await Bun.write(filePath, content);
126
- const file = Bun.file(filePath);
127
- const stats = await file.stat();
128
- debug.log('file', 'File written successfully:', { size: stats.size });
129
-
130
- return {
131
- message: 'File saved successfully',
132
- size: stats.size,
133
- modified: stats.mtime.toISOString()
134
- };
135
- } catch (error) {
136
- debug.error('file', 'Write file error:', error);
137
- throw new Error(`Failed to write file: ${error}`);
138
- }
139
- }
140
-
141
- export async function createFileOperation(filePath: string, content: string = '') {
142
- if (!filePath) {
143
- throw new Error('File path is required');
144
- }
145
-
146
- try {
147
- // Normalize path for Windows only
148
- const normalizedFilePath = process.platform === 'win32' ?
149
- filePath.replace(/\//g, '\\') : filePath;
150
-
151
- // Create parent directory if it doesn't exist
152
- const parentDir = dirname(normalizedFilePath);
153
- const parentFile = Bun.file(parentDir);
154
- if (!(await parentFile.exists())) {
155
- await mkdir(parentDir, { recursive: true });
156
- }
157
-
158
- // Create empty file if it doesn't exist
159
- const file = Bun.file(normalizedFilePath);
160
- if (!(await file.exists())) {
161
- await Bun.write(normalizedFilePath, content);
162
- const stats = await file.stat();
163
-
164
- return {
165
- message: 'File created successfully',
166
- path: normalizedFilePath,
167
- size: stats.size,
168
- modified: stats.mtime.toISOString()
169
- };
170
- } else {
171
- throw new Error('File already exists');
172
- }
173
- } catch (error) {
174
- debug.error('file', 'Create file error:', error);
175
- if (error instanceof Error) {
176
- if (error.message === 'File already exists') {
177
- throw error;
178
- }
179
- if (error.message.includes('EPERM')) {
180
- throw new Error('Permission denied - you may not have sufficient permissions to create files in this location');
181
- } else if (error.message.includes('ENOENT')) {
182
- throw new Error('Invalid path or parent directory does not exist');
183
- }
184
- throw error;
185
- }
186
- throw new Error('Failed to create file');
187
- }
188
- }
189
-
190
- export async function createDirectoryOperation(dirPath: string) {
191
- if (!dirPath) {
192
- throw new Error('Directory path is required');
193
- }
194
-
195
- try {
196
- // Normalize path for Windows only
197
- const normalizedDirPath = process.platform === 'win32' ?
198
- dirPath.replace(/\//g, '\\') : dirPath;
199
-
200
- const dirFile = Bun.file(normalizedDirPath);
201
- if (!(await dirFile.exists())) {
202
- await mkdir(normalizedDirPath, { recursive: true });
203
- const stats = await dirFile.stat();
204
-
205
- return {
206
- message: 'Directory created successfully',
207
- path: normalizedDirPath,
208
- modified: stats.mtime.toISOString()
209
- };
210
- } else {
211
- throw new Error('Directory already exists');
212
- }
213
- } catch (error) {
214
- debug.error('file', 'Create directory error:', error);
215
- if (error instanceof Error) {
216
- if (error.message === 'Directory already exists') {
217
- throw error;
218
- }
219
- if (error.message.includes('EPERM')) {
220
- throw new Error('Permission denied - you may not have sufficient permissions to create directories in this location');
221
- } else if (error.message.includes('ENOENT')) {
222
- throw new Error('Invalid path or parent directory does not exist');
223
- }
224
- throw error;
225
- }
226
- throw new Error('Failed to create directory');
227
- }
228
- }
229
-
230
- export async function renameOperation(oldPath: string, newPath: string) {
231
- if (!oldPath || !newPath) {
232
- throw new Error('Both old and new paths are required');
233
- }
234
-
235
- try {
236
- // Normalize paths for Windows only
237
- const normalizedOldPath = process.platform === 'win32' ?
238
- oldPath.replace(/\//g, '\\') : oldPath;
239
- const normalizedNewPath = process.platform === 'win32' ?
240
- newPath.replace(/\//g, '\\') : newPath;
241
-
242
- const oldFile = Bun.file(normalizedOldPath);
243
- const newFile = Bun.file(normalizedNewPath);
244
-
245
- if (!(await oldFile.exists())) {
246
- throw new Error('Source path does not exist');
247
- }
248
-
249
- if (await newFile.exists()) {
250
- throw new Error('Destination path already exists');
251
- }
252
-
253
- // On Windows, add a small delay to handle file locks
254
- if (process.platform === 'win32') {
255
- await new Promise(resolve => setTimeout(resolve, 100));
256
- }
257
-
258
- await rename(normalizedOldPath, normalizedNewPath);
259
- const stats = await newFile.stat();
260
-
261
- return {
262
- message: 'File/directory renamed successfully',
263
- oldPath: normalizedOldPath,
264
- newPath: normalizedNewPath,
265
- modified: stats.mtime.toISOString()
266
- };
267
- } catch (error) {
268
- debug.error('file', 'Rename error:', error);
269
- if (error instanceof Error) {
270
- if (error.message === 'Source path does not exist' ||
271
- error.message === 'Destination path already exists') {
272
- throw error;
273
- }
274
- if (error.message.includes('EPERM')) {
275
- throw new Error('Permission denied - file may be in use or you may not have sufficient permissions');
276
- } else if (error.message.includes('ENOENT')) {
277
- throw new Error('File or directory not found');
278
- } else if (error.message.includes('EEXIST')) {
279
- throw new Error('Destination already exists');
280
- }
281
- throw error;
282
- }
283
- throw new Error('Failed to rename');
284
- }
285
- }
286
-
287
- export async function duplicateOperation(sourcePath: string, targetPath: string) {
288
- if (!sourcePath || !targetPath) {
289
- throw new Error('Both source and target paths are required');
290
- }
291
-
292
- try {
293
- // Normalize paths for Windows only
294
- const normalizedSourcePath = process.platform === 'win32' ?
295
- sourcePath.replace(/\//g, '\\') : sourcePath;
296
- const normalizedTargetPath = process.platform === 'win32' ?
297
- targetPath.replace(/\//g, '\\') : targetPath;
298
-
299
- // Use node:fs stat to check existence (works for both files AND directories)
300
- let sourceStats;
301
- try {
302
- sourceStats = await fsStat(normalizedSourcePath);
303
- } catch {
304
- throw new Error('Source file does not exist');
305
- }
306
-
307
- let targetExists = false;
308
- try {
309
- await fsStat(normalizedTargetPath);
310
- targetExists = true;
311
- } catch {
312
- // Target doesn't exist - good
313
- }
314
-
315
- if (targetExists) {
316
- throw new Error('Target file already exists');
317
- }
318
-
319
- // On Windows, add a small delay to handle file locks
320
- if (process.platform === 'win32') {
321
- await new Promise(resolve => setTimeout(resolve, 100));
322
- }
323
-
324
- if (sourceStats.isFile()) {
325
- await copyFile(normalizedSourcePath, normalizedTargetPath);
326
- } else if (sourceStats.isDirectory()) {
327
- await copyDirectory(normalizedSourcePath, normalizedTargetPath);
328
- } else {
329
- throw new Error('Unsupported file type');
330
- }
331
-
332
- const targetStats = await fsStat(normalizedTargetPath);
333
-
334
- return {
335
- message: sourceStats.isDirectory() ? 'Folder duplicated successfully' : 'File duplicated successfully',
336
- sourcePath: normalizedSourcePath,
337
- targetPath: normalizedTargetPath,
338
- size: sourceStats.isDirectory() ? 0 : targetStats.size,
339
- modified: targetStats.mtime.toISOString()
340
- };
341
- } catch (error) {
342
- debug.error('file', 'Duplicate error:', error);
343
- if (error instanceof Error) {
344
- if (error.message === 'Source file does not exist' ||
345
- error.message === 'Target file already exists') {
346
- throw error;
347
- }
348
- if (error.message.includes('EPERM')) {
349
- throw new Error('Permission denied - file may be in use or you may not have sufficient permissions');
350
- } else if (error.message.includes('ENOENT')) {
351
- throw new Error('File or directory not found');
352
- } else if (error.message.includes('EEXIST')) {
353
- throw new Error('Target file already exists');
354
- }
355
- throw error;
356
- }
357
- throw new Error('Failed to duplicate file');
358
- }
359
- }
360
-
361
- export async function uploadFileOperation(file: { name: string; type: string; size: number; data: Uint8Array } | any, targetPath: string) {
362
- if (!file || !file.name || !file.data) {
363
- throw new Error('File is required for upload');
364
- }
365
-
366
- if (!targetPath) {
367
- throw new Error('Target path is required');
368
- }
369
-
370
- try {
371
- // Normalize target path for Windows only
372
- const normalizedTargetPath = process.platform === 'win32' ?
373
- targetPath.replace(/\//g, '\\') : targetPath;
374
- const finalPath = join(normalizedTargetPath, file.name);
375
-
376
- // Create parent directory if it doesn't exist
377
- const targetDir = Bun.file(normalizedTargetPath);
378
- if (!(await targetDir.exists())) {
379
- await mkdir(normalizedTargetPath, { recursive: true });
380
- }
381
-
382
- // Check if file already exists
383
- const finalFile = Bun.file(finalPath);
384
- if (await finalFile.exists()) {
385
- throw new Error('File already exists');
386
- }
387
-
388
- // Write file
389
- await Bun.write(finalPath, file.data);
390
- const stats = await finalFile.stat();
391
-
392
- return {
393
- message: 'File uploaded successfully',
394
- path: finalPath,
395
- size: stats.size,
396
- modified: stats.mtime.toISOString()
397
- };
398
- } catch (error) {
399
- debug.error('file', 'Upload file error:', error);
400
- if (error instanceof Error) {
401
- if (error.message === 'File already exists') {
402
- throw error;
403
- }
404
- if (error.message.includes('EPERM')) {
405
- throw new Error('Permission denied - you may not have sufficient permissions to upload files to this location');
406
- } else if (error.message.includes('ENOENT')) {
407
- throw new Error('Target directory does not exist or is invalid');
408
- } else if (error.message.includes('ENOSPC')) {
409
- throw new Error('Not enough space on disk');
410
- }
411
- throw error;
412
- }
413
- throw new Error('Failed to upload file');
414
- }
415
- }
416
-
417
- export async function deleteOperation(filePath: string, force: boolean = false) {
418
- if (!filePath) {
419
- throw new Error('File path is required');
420
- }
421
-
422
- const file = Bun.file(filePath);
423
- let stats;
424
- try {
425
- stats = await file.stat();
426
- } catch {
427
- throw new Error('File or directory does not exist');
428
- }
429
-
430
- try {
431
- if (stats.isFile()) {
432
- await unlink(filePath);
433
- return {
434
- message: 'File deleted successfully',
435
- path: filePath
436
- };
437
- } else if (stats.isDirectory()) {
438
- // Check if directory is empty
439
- const items = await readdir(filePath);
440
- if (items.length > 0 && !force) {
441
- throw new Error('Directory is not empty. Empty the directory first or use force delete.');
442
- }
443
-
444
- if (force && items.length > 0) {
445
- // Use rm with recursive option for force delete
446
- // This is safer than manual recursion and handles edge cases better
447
- await rm(filePath, { recursive: true });
448
- } else {
449
- // For empty directories, use rmdir
450
- await rmdir(filePath);
451
- }
452
-
453
- return {
454
- message: 'Directory deleted successfully',
455
- path: filePath
456
- };
457
- } else {
458
- throw new Error('Path is neither a file nor directory');
459
- }
460
- } catch (error) {
461
- debug.error('file', 'Delete error:', error);
462
- if (error instanceof Error) {
463
- if (error.message === 'Directory is not empty. Empty the directory first or use force delete.' ||
464
- error.message === 'Path is neither a file nor directory') {
465
- throw error;
466
- }
467
- if (error.message.includes('EPERM')) {
468
- throw new Error('Permission denied - the folder may be in use or protected');
469
- } else if (error.message.includes('ENOTEMPTY')) {
470
- throw new Error('Directory is not empty');
471
- } else if (error.message.includes('EBUSY')) {
472
- throw new Error('Resource is busy or locked');
473
- }
474
- throw error;
475
- }
476
- throw new Error('Failed to delete');
477
- }
478
- }
1
+ import { dirname, extname, join } from 'path';
2
+ import { stat as fsStat } from 'node:fs/promises';
3
+
4
+ import { debug } from '$shared/utils/logger';
5
+
6
+ // Import Bun native functions and create wrappers for better API compatibility
7
+ const { spawn } = Bun;
8
+
9
+ // Bun-compatible readdir implementation (cross-platform)
10
+ async function readdir(path: string): Promise<string[]> {
11
+ let proc;
12
+ if (process.platform === 'win32') {
13
+ proc = Bun.spawn(['cmd', '/c', 'dir', '/b', path], { stdout: 'pipe', stderr: 'ignore' });
14
+ } else {
15
+ proc = Bun.spawn(['ls', '-1', path], { stdout: 'pipe', stderr: 'ignore' });
16
+ }
17
+ const result = await new Response(proc.stdout).text();
18
+ // Split and clean up, removing \r characters for Windows compatibility
19
+ return result.trim().split(/\r?\n/).map(line => line.trim()).filter(Boolean);
20
+ }
21
+
22
+ async function mkdir(path: string, options?: { recursive?: boolean }) {
23
+ // Pure Bun approach: create directory by writing a temp file
24
+ try {
25
+ const tempFile = join(path, '.bun_mkdir_temp');
26
+ await Bun.write(tempFile, '');
27
+ // Clean up the temp file
28
+ try {
29
+ if (process.platform === 'win32') {
30
+ await spawn(['cmd', '/c', 'del', '/f', '/q', tempFile.replace(/\//g, '\\')], {
31
+ stdout: 'ignore',
32
+ stderr: 'ignore'
33
+ }).exited;
34
+ } else {
35
+ await spawn(['rm', '-f', tempFile], {
36
+ stdout: 'ignore',
37
+ stderr: 'ignore'
38
+ }).exited;
39
+ }
40
+ } catch {
41
+ // Ignore cleanup errors
42
+ }
43
+ } catch (error) {
44
+ throw new Error(`Failed to create directory ${path}: ${error}`);
45
+ }
46
+ }
47
+
48
+ async function copyFile(src: string, dest: string) {
49
+ const srcFile = Bun.file(src);
50
+ const content = await srcFile.arrayBuffer();
51
+ await Bun.write(dest, content);
52
+ }
53
+
54
+ async function copyDirectory(src: string, dest: string) {
55
+ // Create destination directory
56
+ await mkdir(dest, { recursive: true });
57
+
58
+ // List items in source
59
+ const items = await readdir(src);
60
+
61
+ for (const item of items) {
62
+ const srcPath = join(src, item);
63
+ const destPath = join(dest, item);
64
+
65
+ const file = Bun.file(srcPath);
66
+ try {
67
+ const stats = await file.stat();
68
+ if (stats.isDirectory()) {
69
+ await copyDirectory(srcPath, destPath);
70
+ } else {
71
+ await copyFile(srcPath, destPath);
72
+ }
73
+ } catch {
74
+ // Skip items that can't be read
75
+ continue;
76
+ }
77
+ }
78
+ }
79
+
80
+ async function rename(oldPath: string, newPath: string) {
81
+ if (process.platform === 'win32') {
82
+ const proc = spawn(['cmd', '/c', 'move', oldPath.replace(/\//g, '\\'), newPath.replace(/\//g, '\\')], { stdout: 'ignore', stderr: 'ignore' });
83
+ await proc.exited;
84
+ } else {
85
+ const proc = spawn(['mv', oldPath, newPath], { stdout: 'ignore', stderr: 'ignore' });
86
+ await proc.exited;
87
+ }
88
+ }
89
+
90
+ async function unlink(path: string) {
91
+ if (process.platform === 'win32') {
92
+ const proc = spawn(['cmd', '/c', 'del', '/f', '/q', path.replace(/\//g, '\\')], { stdout: 'ignore', stderr: 'ignore' });
93
+ await proc.exited;
94
+ } else {
95
+ const proc = spawn(['rm', '-f', path], { stdout: 'ignore', stderr: 'ignore' });
96
+ await proc.exited;
97
+ }
98
+ }
99
+
100
+ async function rm(path: string, options?: { recursive?: boolean }) {
101
+ if (process.platform === 'win32') {
102
+ const proc = spawn(['cmd', '/c', 'rmdir', '/s', '/q', path.replace(/\//g, '\\')], { stdout: 'ignore', stderr: 'ignore' });
103
+ await proc.exited;
104
+ } else {
105
+ const proc = spawn(['rm', '-rf', path], { stdout: 'ignore', stderr: 'ignore' });
106
+ await proc.exited;
107
+ }
108
+ }
109
+
110
+ async function rmdir(path: string, options?: { recursive?: boolean }) {
111
+ return rm(path, options);
112
+ }
113
+
114
+ export async function writeFileOperation(filePath: string, content: string) {
115
+ if (!filePath) {
116
+ throw new Error('File path is required');
117
+ }
118
+
119
+ if (typeof content !== 'string') {
120
+ throw new Error('Content must be a string');
121
+ }
122
+
123
+ try {
124
+ debug.log('file', 'Writing file:', { filePath, contentLength: content.length });
125
+ await Bun.write(filePath, content);
126
+ const file = Bun.file(filePath);
127
+ const stats = await file.stat();
128
+ debug.log('file', 'File written successfully:', { size: stats.size });
129
+
130
+ return {
131
+ message: 'File saved successfully',
132
+ size: stats.size,
133
+ modified: stats.mtime.toISOString()
134
+ };
135
+ } catch (error) {
136
+ debug.error('file', 'Write file error:', error);
137
+ throw new Error(`Failed to write file: ${error}`);
138
+ }
139
+ }
140
+
141
+ export async function createFileOperation(filePath: string, content: string = '') {
142
+ if (!filePath) {
143
+ throw new Error('File path is required');
144
+ }
145
+
146
+ try {
147
+ // Normalize path for Windows only
148
+ const normalizedFilePath = process.platform === 'win32' ?
149
+ filePath.replace(/\//g, '\\') : filePath;
150
+
151
+ // Create parent directory if it doesn't exist
152
+ const parentDir = dirname(normalizedFilePath);
153
+ const parentFile = Bun.file(parentDir);
154
+ if (!(await parentFile.exists())) {
155
+ await mkdir(parentDir, { recursive: true });
156
+ }
157
+
158
+ // Create empty file if it doesn't exist
159
+ const file = Bun.file(normalizedFilePath);
160
+ if (!(await file.exists())) {
161
+ await Bun.write(normalizedFilePath, content);
162
+ const stats = await file.stat();
163
+
164
+ return {
165
+ message: 'File created successfully',
166
+ path: normalizedFilePath,
167
+ size: stats.size,
168
+ modified: stats.mtime.toISOString()
169
+ };
170
+ } else {
171
+ throw new Error('File already exists');
172
+ }
173
+ } catch (error) {
174
+ debug.error('file', 'Create file error:', error);
175
+ if (error instanceof Error) {
176
+ if (error.message === 'File already exists') {
177
+ throw error;
178
+ }
179
+ if (error.message.includes('EPERM')) {
180
+ throw new Error('Permission denied - you may not have sufficient permissions to create files in this location');
181
+ } else if (error.message.includes('ENOENT')) {
182
+ throw new Error('Invalid path or parent directory does not exist');
183
+ }
184
+ throw error;
185
+ }
186
+ throw new Error('Failed to create file');
187
+ }
188
+ }
189
+
190
+ export async function createDirectoryOperation(dirPath: string) {
191
+ if (!dirPath) {
192
+ throw new Error('Directory path is required');
193
+ }
194
+
195
+ try {
196
+ // Normalize path for Windows only
197
+ const normalizedDirPath = process.platform === 'win32' ?
198
+ dirPath.replace(/\//g, '\\') : dirPath;
199
+
200
+ const dirFile = Bun.file(normalizedDirPath);
201
+ if (!(await dirFile.exists())) {
202
+ await mkdir(normalizedDirPath, { recursive: true });
203
+ const stats = await dirFile.stat();
204
+
205
+ return {
206
+ message: 'Directory created successfully',
207
+ path: normalizedDirPath,
208
+ modified: stats.mtime.toISOString()
209
+ };
210
+ } else {
211
+ throw new Error('Directory already exists');
212
+ }
213
+ } catch (error) {
214
+ debug.error('file', 'Create directory error:', error);
215
+ if (error instanceof Error) {
216
+ if (error.message === 'Directory already exists') {
217
+ throw error;
218
+ }
219
+ if (error.message.includes('EPERM')) {
220
+ throw new Error('Permission denied - you may not have sufficient permissions to create directories in this location');
221
+ } else if (error.message.includes('ENOENT')) {
222
+ throw new Error('Invalid path or parent directory does not exist');
223
+ }
224
+ throw error;
225
+ }
226
+ throw new Error('Failed to create directory');
227
+ }
228
+ }
229
+
230
+ export async function renameOperation(oldPath: string, newPath: string) {
231
+ if (!oldPath || !newPath) {
232
+ throw new Error('Both old and new paths are required');
233
+ }
234
+
235
+ try {
236
+ // Normalize paths for Windows only
237
+ const normalizedOldPath = process.platform === 'win32' ?
238
+ oldPath.replace(/\//g, '\\') : oldPath;
239
+ const normalizedNewPath = process.platform === 'win32' ?
240
+ newPath.replace(/\//g, '\\') : newPath;
241
+
242
+ const oldFile = Bun.file(normalizedOldPath);
243
+ const newFile = Bun.file(normalizedNewPath);
244
+
245
+ if (!(await oldFile.exists())) {
246
+ throw new Error('Source path does not exist');
247
+ }
248
+
249
+ if (await newFile.exists()) {
250
+ throw new Error('Destination path already exists');
251
+ }
252
+
253
+ // On Windows, add a small delay to handle file locks
254
+ if (process.platform === 'win32') {
255
+ await new Promise(resolve => setTimeout(resolve, 100));
256
+ }
257
+
258
+ await rename(normalizedOldPath, normalizedNewPath);
259
+ const stats = await newFile.stat();
260
+
261
+ return {
262
+ message: 'File/directory renamed successfully',
263
+ oldPath: normalizedOldPath,
264
+ newPath: normalizedNewPath,
265
+ modified: stats.mtime.toISOString()
266
+ };
267
+ } catch (error) {
268
+ debug.error('file', 'Rename error:', error);
269
+ if (error instanceof Error) {
270
+ if (error.message === 'Source path does not exist' ||
271
+ error.message === 'Destination path already exists') {
272
+ throw error;
273
+ }
274
+ if (error.message.includes('EPERM')) {
275
+ throw new Error('Permission denied - file may be in use or you may not have sufficient permissions');
276
+ } else if (error.message.includes('ENOENT')) {
277
+ throw new Error('File or directory not found');
278
+ } else if (error.message.includes('EEXIST')) {
279
+ throw new Error('Destination already exists');
280
+ }
281
+ throw error;
282
+ }
283
+ throw new Error('Failed to rename');
284
+ }
285
+ }
286
+
287
+ export async function duplicateOperation(sourcePath: string, targetPath: string) {
288
+ if (!sourcePath || !targetPath) {
289
+ throw new Error('Both source and target paths are required');
290
+ }
291
+
292
+ try {
293
+ // Normalize paths for Windows only
294
+ const normalizedSourcePath = process.platform === 'win32' ?
295
+ sourcePath.replace(/\//g, '\\') : sourcePath;
296
+ const normalizedTargetPath = process.platform === 'win32' ?
297
+ targetPath.replace(/\//g, '\\') : targetPath;
298
+
299
+ // Use node:fs stat to check existence (works for both files AND directories)
300
+ let sourceStats;
301
+ try {
302
+ sourceStats = await fsStat(normalizedSourcePath);
303
+ } catch {
304
+ throw new Error('Source file does not exist');
305
+ }
306
+
307
+ let targetExists = false;
308
+ try {
309
+ await fsStat(normalizedTargetPath);
310
+ targetExists = true;
311
+ } catch {
312
+ // Target doesn't exist - good
313
+ }
314
+
315
+ if (targetExists) {
316
+ throw new Error('Target file already exists');
317
+ }
318
+
319
+ // On Windows, add a small delay to handle file locks
320
+ if (process.platform === 'win32') {
321
+ await new Promise(resolve => setTimeout(resolve, 100));
322
+ }
323
+
324
+ if (sourceStats.isFile()) {
325
+ await copyFile(normalizedSourcePath, normalizedTargetPath);
326
+ } else if (sourceStats.isDirectory()) {
327
+ await copyDirectory(normalizedSourcePath, normalizedTargetPath);
328
+ } else {
329
+ throw new Error('Unsupported file type');
330
+ }
331
+
332
+ const targetStats = await fsStat(normalizedTargetPath);
333
+
334
+ return {
335
+ message: sourceStats.isDirectory() ? 'Folder duplicated successfully' : 'File duplicated successfully',
336
+ sourcePath: normalizedSourcePath,
337
+ targetPath: normalizedTargetPath,
338
+ size: sourceStats.isDirectory() ? 0 : targetStats.size,
339
+ modified: targetStats.mtime.toISOString()
340
+ };
341
+ } catch (error) {
342
+ debug.error('file', 'Duplicate error:', error);
343
+ if (error instanceof Error) {
344
+ if (error.message === 'Source file does not exist' ||
345
+ error.message === 'Target file already exists') {
346
+ throw error;
347
+ }
348
+ if (error.message.includes('EPERM')) {
349
+ throw new Error('Permission denied - file may be in use or you may not have sufficient permissions');
350
+ } else if (error.message.includes('ENOENT')) {
351
+ throw new Error('File or directory not found');
352
+ } else if (error.message.includes('EEXIST')) {
353
+ throw new Error('Target file already exists');
354
+ }
355
+ throw error;
356
+ }
357
+ throw new Error('Failed to duplicate file');
358
+ }
359
+ }
360
+
361
+ export async function uploadFileOperation(file: { name: string; type: string; size: number; data: Uint8Array } | any, targetPath: string) {
362
+ if (!file || !file.name || !file.data) {
363
+ throw new Error('File is required for upload');
364
+ }
365
+
366
+ if (!targetPath) {
367
+ throw new Error('Target path is required');
368
+ }
369
+
370
+ try {
371
+ // Normalize target path for Windows only
372
+ const normalizedTargetPath = process.platform === 'win32' ?
373
+ targetPath.replace(/\//g, '\\') : targetPath;
374
+ const finalPath = join(normalizedTargetPath, file.name);
375
+
376
+ // Create parent directory if it doesn't exist
377
+ const targetDir = Bun.file(normalizedTargetPath);
378
+ if (!(await targetDir.exists())) {
379
+ await mkdir(normalizedTargetPath, { recursive: true });
380
+ }
381
+
382
+ // Check if file already exists
383
+ const finalFile = Bun.file(finalPath);
384
+ if (await finalFile.exists()) {
385
+ throw new Error('File already exists');
386
+ }
387
+
388
+ // Write file
389
+ await Bun.write(finalPath, file.data);
390
+ const stats = await finalFile.stat();
391
+
392
+ return {
393
+ message: 'File uploaded successfully',
394
+ path: finalPath,
395
+ size: stats.size,
396
+ modified: stats.mtime.toISOString()
397
+ };
398
+ } catch (error) {
399
+ debug.error('file', 'Upload file error:', error);
400
+ if (error instanceof Error) {
401
+ if (error.message === 'File already exists') {
402
+ throw error;
403
+ }
404
+ if (error.message.includes('EPERM')) {
405
+ throw new Error('Permission denied - you may not have sufficient permissions to upload files to this location');
406
+ } else if (error.message.includes('ENOENT')) {
407
+ throw new Error('Target directory does not exist or is invalid');
408
+ } else if (error.message.includes('ENOSPC')) {
409
+ throw new Error('Not enough space on disk');
410
+ }
411
+ throw error;
412
+ }
413
+ throw new Error('Failed to upload file');
414
+ }
415
+ }
416
+
417
+ export async function deleteOperation(filePath: string, force: boolean = false) {
418
+ if (!filePath) {
419
+ throw new Error('File path is required');
420
+ }
421
+
422
+ const file = Bun.file(filePath);
423
+ let stats;
424
+ try {
425
+ stats = await file.stat();
426
+ } catch {
427
+ throw new Error('File or directory does not exist');
428
+ }
429
+
430
+ try {
431
+ if (stats.isFile()) {
432
+ await unlink(filePath);
433
+ return {
434
+ message: 'File deleted successfully',
435
+ path: filePath
436
+ };
437
+ } else if (stats.isDirectory()) {
438
+ // Check if directory is empty
439
+ const items = await readdir(filePath);
440
+ if (items.length > 0 && !force) {
441
+ throw new Error('Directory is not empty. Empty the directory first or use force delete.');
442
+ }
443
+
444
+ if (force && items.length > 0) {
445
+ // Use rm with recursive option for force delete
446
+ // This is safer than manual recursion and handles edge cases better
447
+ await rm(filePath, { recursive: true });
448
+ } else {
449
+ // For empty directories, use rmdir
450
+ await rmdir(filePath);
451
+ }
452
+
453
+ return {
454
+ message: 'Directory deleted successfully',
455
+ path: filePath
456
+ };
457
+ } else {
458
+ throw new Error('Path is neither a file nor directory');
459
+ }
460
+ } catch (error) {
461
+ debug.error('file', 'Delete error:', error);
462
+ if (error instanceof Error) {
463
+ if (error.message === 'Directory is not empty. Empty the directory first or use force delete.' ||
464
+ error.message === 'Path is neither a file nor directory') {
465
+ throw error;
466
+ }
467
+ if (error.message.includes('EPERM')) {
468
+ throw new Error('Permission denied - the folder may be in use or protected');
469
+ } else if (error.message.includes('ENOTEMPTY')) {
470
+ throw new Error('Directory is not empty');
471
+ } else if (error.message.includes('EBUSY')) {
472
+ throw new Error('Resource is busy or locked');
473
+ }
474
+ throw error;
475
+ }
476
+ throw new Error('Failed to delete');
477
+ }
478
+ }