@myrialabs/clopen 0.0.5 → 0.0.7

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 +12 -6
  2. package/.github/workflows/ci.yml +86 -86
  3. package/CONTRIBUTING.md +499 -499
  4. package/LICENSE +21 -21
  5. package/README.md +209 -209
  6. package/backend/index.ts +144 -165
  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 +124 -0
  100. package/backend/lib/shared/index.ts +5 -2
  101. package/backend/lib/shared/port-utils.ts +35 -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/middleware/cors.ts +16 -15
  118. package/backend/middleware/error-handler.ts +50 -49
  119. package/backend/middleware/logger.ts +9 -9
  120. package/backend/types/api.ts +24 -24
  121. package/backend/ws/README.md +1505 -1505
  122. package/backend/ws/chat/background.ts +198 -198
  123. package/backend/ws/chat/index.ts +21 -21
  124. package/backend/ws/chat/stream.ts +707 -707
  125. package/backend/ws/engine/claude/accounts.ts +399 -401
  126. package/backend/ws/engine/claude/index.ts +13 -13
  127. package/backend/ws/engine/claude/status.ts +43 -43
  128. package/backend/ws/engine/index.ts +14 -14
  129. package/backend/ws/engine/opencode/index.ts +11 -11
  130. package/backend/ws/engine/opencode/status.ts +30 -30
  131. package/backend/ws/engine/utils.ts +36 -36
  132. package/backend/ws/files/index.ts +30 -30
  133. package/backend/ws/files/read.ts +189 -189
  134. package/backend/ws/files/search.ts +453 -453
  135. package/backend/ws/files/watch.ts +124 -124
  136. package/backend/ws/files/write.ts +143 -143
  137. package/backend/ws/git/branch.ts +106 -106
  138. package/backend/ws/git/commit.ts +39 -39
  139. package/backend/ws/git/conflict.ts +68 -68
  140. package/backend/ws/git/diff.ts +69 -69
  141. package/backend/ws/git/index.ts +24 -24
  142. package/backend/ws/git/log.ts +41 -41
  143. package/backend/ws/git/remote.ts +214 -214
  144. package/backend/ws/git/staging.ts +84 -84
  145. package/backend/ws/git/status.ts +90 -90
  146. package/backend/ws/index.ts +69 -69
  147. package/backend/ws/mcp/index.ts +61 -61
  148. package/backend/ws/messages/crud.ts +74 -74
  149. package/backend/ws/messages/index.ts +14 -14
  150. package/backend/ws/preview/browser/cleanup.ts +129 -129
  151. package/backend/ws/preview/browser/console.ts +114 -114
  152. package/backend/ws/preview/browser/interact.ts +513 -513
  153. package/backend/ws/preview/browser/mcp.ts +129 -129
  154. package/backend/ws/preview/browser/native-ui.ts +235 -235
  155. package/backend/ws/preview/browser/stats.ts +55 -55
  156. package/backend/ws/preview/browser/tab-info.ts +126 -126
  157. package/backend/ws/preview/browser/tab.ts +166 -166
  158. package/backend/ws/preview/browser/webcodecs.ts +293 -293
  159. package/backend/ws/preview/index.ts +146 -146
  160. package/backend/ws/projects/crud.ts +113 -113
  161. package/backend/ws/projects/index.ts +25 -25
  162. package/backend/ws/projects/presence.ts +46 -46
  163. package/backend/ws/projects/status.ts +116 -116
  164. package/backend/ws/sessions/crud.ts +327 -327
  165. package/backend/ws/sessions/index.ts +33 -33
  166. package/backend/ws/settings/crud.ts +112 -112
  167. package/backend/ws/settings/index.ts +14 -14
  168. package/backend/ws/snapshot/index.ts +17 -17
  169. package/backend/ws/snapshot/restore.ts +173 -173
  170. package/backend/ws/snapshot/timeline.ts +141 -141
  171. package/backend/ws/system/index.ts +14 -14
  172. package/backend/ws/system/operations.ts +49 -49
  173. package/backend/ws/terminal/index.ts +40 -40
  174. package/backend/ws/terminal/persistence.ts +153 -153
  175. package/backend/ws/terminal/session.ts +382 -382
  176. package/backend/ws/terminal/stream.ts +79 -79
  177. package/backend/ws/tunnel/index.ts +14 -14
  178. package/backend/ws/tunnel/operations.ts +91 -91
  179. package/backend/ws/types.ts +20 -20
  180. package/backend/ws/user/crud.ts +156 -156
  181. package/backend/ws/user/index.ts +14 -14
  182. package/bin/clopen.ts +307 -307
  183. package/bun.lock +1364 -1352
  184. package/frontend/App.svelte +38 -34
  185. package/frontend/app.css +313 -313
  186. package/frontend/lib/app-environment.ts +10 -10
  187. package/frontend/lib/components/chat/ChatInterface.svelte +406 -406
  188. package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +56 -56
  189. package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +223 -223
  190. package/frontend/lib/components/chat/formatters/TextMessage.svelte +394 -394
  191. package/frontend/lib/components/chat/formatters/Tools.svelte +69 -69
  192. package/frontend/lib/components/chat/formatters/index.ts +2 -2
  193. package/frontend/lib/components/chat/input/ChatInput.svelte +421 -421
  194. package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -78
  195. package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -30
  196. package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -33
  197. package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -619
  198. package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -48
  199. package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -31
  200. package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -201
  201. package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -148
  202. package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -216
  203. package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -357
  204. package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -57
  205. package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -478
  206. package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -541
  207. package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -86
  208. package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -86
  209. package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -157
  210. package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -59
  211. package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -124
  212. package/frontend/lib/components/chat/shared/index.ts +1 -1
  213. package/frontend/lib/components/chat/shared/utils.ts +115 -115
  214. package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -35
  215. package/frontend/lib/components/chat/tools/BashTool.svelte +45 -45
  216. package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -139
  217. package/frontend/lib/components/chat/tools/EditTool.svelte +47 -47
  218. package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +31 -31
  219. package/frontend/lib/components/chat/tools/GlobTool.svelte +50 -50
  220. package/frontend/lib/components/chat/tools/GrepTool.svelte +89 -89
  221. package/frontend/lib/components/chat/tools/KillShellTool.svelte +25 -25
  222. package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +30 -30
  223. package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +37 -37
  224. package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +33 -33
  225. package/frontend/lib/components/chat/tools/ReadTool.svelte +40 -40
  226. package/frontend/lib/components/chat/tools/TaskTool.svelte +63 -63
  227. package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +74 -74
  228. package/frontend/lib/components/chat/tools/WebFetchTool.svelte +34 -34
  229. package/frontend/lib/components/chat/tools/WebSearchTool.svelte +83 -83
  230. package/frontend/lib/components/chat/tools/WriteTool.svelte +32 -32
  231. package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +78 -78
  232. package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +407 -407
  233. package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -45
  234. package/frontend/lib/components/chat/tools/components/InfoLine.svelte +18 -18
  235. package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +26 -26
  236. package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +53 -53
  237. package/frontend/lib/components/chat/tools/components/index.ts +7 -7
  238. package/frontend/lib/components/chat/tools/index.ts +25 -25
  239. package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +248 -248
  240. package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -78
  241. package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -391
  242. package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -26
  243. package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +86 -86
  244. package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -108
  245. package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -59
  246. package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -168
  247. package/frontend/lib/components/checkpoint/timeline/config.ts +44 -44
  248. package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -304
  249. package/frontend/lib/components/checkpoint/timeline/types.ts +65 -65
  250. package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -53
  251. package/frontend/lib/components/common/Alert.svelte +138 -138
  252. package/frontend/lib/components/common/AvatarBubble.svelte +55 -55
  253. package/frontend/lib/components/common/Button.svelte +71 -71
  254. package/frontend/lib/components/common/Card.svelte +102 -102
  255. package/frontend/lib/components/common/Checkbox.svelte +48 -48
  256. package/frontend/lib/components/common/Dialog.svelte +248 -248
  257. package/frontend/lib/components/common/FolderBrowser.svelte +842 -842
  258. package/frontend/lib/components/common/Icon.svelte +57 -57
  259. package/frontend/lib/components/common/Input.svelte +72 -72
  260. package/frontend/lib/components/common/Lightbox.svelte +232 -232
  261. package/frontend/lib/components/common/LoadingScreen.svelte +52 -52
  262. package/frontend/lib/components/common/LoadingSpinner.svelte +48 -48
  263. package/frontend/lib/components/common/Modal.svelte +177 -177
  264. package/frontend/lib/components/common/ModalProvider.svelte +27 -27
  265. package/frontend/lib/components/common/ModelSelector.svelte +110 -110
  266. package/frontend/lib/components/common/MonacoEditor.svelte +568 -568
  267. package/frontend/lib/components/common/NotificationToast.svelte +113 -113
  268. package/frontend/lib/components/common/PageTemplate.svelte +75 -75
  269. package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -79
  270. package/frontend/lib/components/common/Select.svelte +97 -97
  271. package/frontend/lib/components/common/Textarea.svelte +79 -79
  272. package/frontend/lib/components/common/ThemeToggle.svelte +44 -44
  273. package/frontend/lib/components/common/lucide-icons.ts +1642 -1642
  274. package/frontend/lib/components/common/material-icons.ts +1082 -1082
  275. package/frontend/lib/components/common/xterm/XTerm.svelte +809 -795
  276. package/frontend/lib/components/common/xterm/index.ts +15 -15
  277. package/frontend/lib/components/common/xterm/terminal-config.ts +67 -67
  278. package/frontend/lib/components/common/xterm/types.ts +30 -30
  279. package/frontend/lib/components/common/xterm/xterm-service.ts +379 -353
  280. package/frontend/lib/components/files/FileNode.svelte +383 -383
  281. package/frontend/lib/components/files/FileTree.svelte +681 -681
  282. package/frontend/lib/components/files/FileViewer.svelte +728 -728
  283. package/frontend/lib/components/files/SearchResults.svelte +303 -303
  284. package/frontend/lib/components/git/BranchManager.svelte +458 -458
  285. package/frontend/lib/components/git/ChangesSection.svelte +107 -107
  286. package/frontend/lib/components/git/CommitForm.svelte +76 -76
  287. package/frontend/lib/components/git/ConflictResolver.svelte +158 -158
  288. package/frontend/lib/components/git/DiffViewer.svelte +364 -364
  289. package/frontend/lib/components/git/FileChangeItem.svelte +97 -97
  290. package/frontend/lib/components/git/GitButton.svelte +33 -33
  291. package/frontend/lib/components/git/GitLog.svelte +361 -361
  292. package/frontend/lib/components/git/GitModal.svelte +80 -80
  293. package/frontend/lib/components/history/HistoryModal.svelte +563 -563
  294. package/frontend/lib/components/history/HistoryView.svelte +614 -614
  295. package/frontend/lib/components/index.ts +34 -34
  296. package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -549
  297. package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -1058
  298. package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +756 -756
  299. package/frontend/lib/components/preview/browser/components/Container.svelte +450 -450
  300. package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -236
  301. package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -224
  302. package/frontend/lib/components/preview/browser/components/Toolbar.svelte +338 -338
  303. package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +35 -35
  304. package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -155
  305. package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -837
  306. package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -113
  307. package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -296
  308. package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -391
  309. package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -231
  310. package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -210
  311. package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -239
  312. package/frontend/lib/components/preview/index.ts +1 -1
  313. package/frontend/lib/components/settings/SettingsModal.svelte +235 -235
  314. package/frontend/lib/components/settings/SettingsView.svelte +36 -36
  315. package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -51
  316. package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -160
  317. package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -76
  318. package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -917
  319. package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -187
  320. package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -203
  321. package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -10
  322. package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -357
  323. package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -205
  324. package/frontend/lib/components/settings/user/UserSettings.svelte +197 -197
  325. package/frontend/lib/components/terminal/Terminal.svelte +367 -367
  326. package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -87
  327. package/frontend/lib/components/terminal/TerminalView.svelte +54 -54
  328. package/frontend/lib/components/tunnel/TunnelActive.svelte +157 -142
  329. package/frontend/lib/components/tunnel/TunnelButton.svelte +60 -54
  330. package/frontend/lib/components/tunnel/TunnelInactive.svelte +285 -284
  331. package/frontend/lib/components/tunnel/TunnelModal.svelte +48 -47
  332. package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -49
  333. package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -382
  334. package/frontend/lib/components/workspace/MobileNavigator.svelte +394 -403
  335. package/frontend/lib/components/workspace/PanelContainer.svelte +100 -100
  336. package/frontend/lib/components/workspace/PanelHeader.svelte +505 -505
  337. package/frontend/lib/components/workspace/ViewMenu.svelte +162 -162
  338. package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -169
  339. package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -15
  340. package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -17
  341. package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -42
  342. package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +84 -84
  343. package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -37
  344. package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -274
  345. package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -1261
  346. package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -1560
  347. package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -150
  348. package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -73
  349. package/frontend/lib/constants/preview.ts +44 -44
  350. package/frontend/lib/services/chat/chat.service.ts +704 -704
  351. package/frontend/lib/services/chat/index.ts +6 -6
  352. package/frontend/lib/services/notification/global-stream-monitor.ts +86 -86
  353. package/frontend/lib/services/notification/index.ts +7 -7
  354. package/frontend/lib/services/notification/push.service.ts +143 -143
  355. package/frontend/lib/services/notification/sound.service.ts +126 -126
  356. package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -61
  357. package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -1499
  358. package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -67
  359. package/frontend/lib/services/preview/index.ts +22 -22
  360. package/frontend/lib/services/project/index.ts +7 -7
  361. package/frontend/lib/services/project/status.service.ts +159 -159
  362. package/frontend/lib/services/snapshot/snapshot.service.ts +47 -47
  363. package/frontend/lib/services/terminal/background/index.ts +129 -129
  364. package/frontend/lib/services/terminal/background/session-restore.ts +273 -273
  365. package/frontend/lib/services/terminal/background/stream-manager.ts +285 -285
  366. package/frontend/lib/services/terminal/index.ts +13 -13
  367. package/frontend/lib/services/terminal/persistence.service.ts +260 -260
  368. package/frontend/lib/services/terminal/project.service.ts +952 -952
  369. package/frontend/lib/services/terminal/session.service.ts +363 -363
  370. package/frontend/lib/services/terminal/terminal.service.ts +369 -369
  371. package/frontend/lib/stores/core/app.svelte.ts +117 -117
  372. package/frontend/lib/stores/core/files.svelte.ts +72 -72
  373. package/frontend/lib/stores/core/presence.svelte.ts +48 -48
  374. package/frontend/lib/stores/core/projects.svelte.ts +317 -317
  375. package/frontend/lib/stores/core/sessions.svelte.ts +383 -383
  376. package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -58
  377. package/frontend/lib/stores/features/models.svelte.ts +89 -89
  378. package/frontend/lib/stores/features/settings.svelte.ts +87 -87
  379. package/frontend/lib/stores/features/terminal.svelte.ts +700 -700
  380. package/frontend/lib/stores/features/tunnel.svelte.ts +163 -161
  381. package/frontend/lib/stores/features/user.svelte.ts +95 -95
  382. package/frontend/lib/stores/ui/chat-input.svelte.ts +56 -56
  383. package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -61
  384. package/frontend/lib/stores/ui/dialog.svelte.ts +58 -58
  385. package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -214
  386. package/frontend/lib/stores/ui/notification.svelte.ts +166 -166
  387. package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -88
  388. package/frontend/lib/stores/ui/theme.svelte.ts +179 -179
  389. package/frontend/lib/stores/ui/workspace.svelte.ts +754 -754
  390. package/frontend/lib/types/native-ui.ts +73 -73
  391. package/frontend/lib/utils/chat/date-separator.ts +38 -38
  392. package/frontend/lib/utils/chat/message-grouper.ts +218 -218
  393. package/frontend/lib/utils/chat/message-processor.ts +134 -134
  394. package/frontend/lib/utils/chat/tool-handler.ts +160 -160
  395. package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -142
  396. package/frontend/lib/utils/click-outside.ts +20 -20
  397. package/frontend/lib/utils/context-manager.ts +256 -256
  398. package/frontend/lib/utils/file-icon-mappings.ts +768 -768
  399. package/frontend/lib/utils/folder-icon-mappings.ts +1029 -1029
  400. package/frontend/lib/utils/git-status.ts +68 -68
  401. package/frontend/lib/utils/platform.ts +112 -112
  402. package/frontend/lib/utils/port-check.ts +64 -64
  403. package/frontend/lib/utils/terminalFormatter.ts +206 -206
  404. package/frontend/lib/utils/theme.ts +6 -6
  405. package/frontend/lib/utils/tree-visualizer.ts +320 -320
  406. package/frontend/lib/utils/ws.ts +44 -44
  407. package/frontend/main.ts +13 -13
  408. package/index.html +70 -70
  409. package/package.json +114 -111
  410. package/scripts/dev.ts +45 -0
  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 -144
  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 +50 -33
  453. package/backend/lib/vite-dev.ts +0 -295
@@ -1,505 +1,505 @@
1
- /**
2
- * Git Service
3
- * High-level git operations built on top of executor and parser
4
- */
5
-
6
- import { execGit, isGitRepo, getGitRoot } from './git-executor';
7
- import {
8
- parseStatus,
9
- parseBranches,
10
- parseAheadBehind,
11
- parseDiff,
12
- parseLog,
13
- parseRemotes,
14
- parseStashList,
15
- parseConflictMarkers
16
- } from './git-parser';
17
- import type {
18
- GitStatus,
19
- GitBranchInfo,
20
- GitFileDiff,
21
- GitLogResult,
22
- GitRemote,
23
- GitStashEntry,
24
- GitConflictFile
25
- } from '$shared/types/git';
26
- import { debug } from '$shared/utils/logger';
27
-
28
- export class GitService {
29
- /**
30
- * Check if path is a git repo
31
- */
32
- async isRepo(cwd: string): Promise<boolean> {
33
- return isGitRepo(cwd);
34
- }
35
-
36
- /**
37
- * Get repository root
38
- */
39
- async getRoot(cwd: string): Promise<string | null> {
40
- return getGitRoot(cwd);
41
- }
42
-
43
- /**
44
- * Initialize a new git repository
45
- */
46
- async init(cwd: string, defaultBranch?: string): Promise<void> {
47
- const args = ['init'];
48
- if (defaultBranch) args.push('-b', defaultBranch);
49
- const result = await execGit(args, cwd);
50
- if (result.exitCode !== 0) {
51
- throw new Error(`git init failed: ${result.stderr}`);
52
- }
53
- }
54
-
55
- // ============================================
56
- // Status
57
- // ============================================
58
-
59
- async getStatus(cwd: string): Promise<GitStatus> {
60
- const result = await execGit(['status', '--porcelain=v1', '-u'], cwd);
61
- if (result.exitCode !== 0) {
62
- throw new Error(`git status failed: ${result.stderr}`);
63
- }
64
- return parseStatus(result.stdout);
65
- }
66
-
67
- // ============================================
68
- // Staging
69
- // ============================================
70
-
71
- async stageFile(cwd: string, filePath: string): Promise<void> {
72
- const result = await execGit(['add', '--', filePath], cwd);
73
- if (result.exitCode !== 0) {
74
- throw new Error(`git add failed: ${result.stderr}`);
75
- }
76
- }
77
-
78
- async stageAll(cwd: string): Promise<void> {
79
- const result = await execGit(['add', '-A'], cwd);
80
- if (result.exitCode !== 0) {
81
- throw new Error(`git add -A failed: ${result.stderr}`);
82
- }
83
- }
84
-
85
- async unstageFile(cwd: string, filePath: string): Promise<void> {
86
- // Try normal reset first, fall back to rm --cached for initial commit
87
- const result = await execGit(['reset', 'HEAD', '--', filePath], cwd);
88
- if (result.exitCode !== 0) {
89
- const fallback = await execGit(['rm', '--cached', '--', filePath], cwd);
90
- if (fallback.exitCode !== 0) {
91
- throw new Error(`git unstage failed: ${result.stderr}`);
92
- }
93
- }
94
- }
95
-
96
- async unstageAll(cwd: string): Promise<void> {
97
- // Try normal reset first, fall back to rm --cached for initial commit
98
- const result = await execGit(['reset', 'HEAD'], cwd);
99
- if (result.exitCode !== 0) {
100
- const fallback = await execGit(['rm', '-r', '--cached', '.'], cwd);
101
- if (fallback.exitCode !== 0) {
102
- throw new Error(`git unstage all failed: ${result.stderr}`);
103
- }
104
- }
105
- }
106
-
107
- async discardFile(cwd: string, filePath: string): Promise<void> {
108
- // Check if file is untracked
109
- const statusResult = await execGit(['status', '--porcelain=v1', '--', filePath], cwd);
110
- const statusLine = statusResult.stdout.trim();
111
-
112
- if (statusLine.startsWith('??')) {
113
- // Untracked file - delete it
114
- const { unlink } = await import('node:fs/promises');
115
- const { join } = await import('node:path');
116
- await unlink(join(cwd, filePath));
117
- } else {
118
- // Tracked file - restore it
119
- const result = await execGit(['checkout', '--', filePath], cwd);
120
- if (result.exitCode !== 0) {
121
- throw new Error(`git checkout failed: ${result.stderr}`);
122
- }
123
- }
124
- }
125
-
126
- async discardAll(cwd: string): Promise<void> {
127
- // Restore tracked files
128
- await execGit(['checkout', '--', '.'], cwd);
129
- // Remove untracked files
130
- await execGit(['clean', '-fd'], cwd);
131
- }
132
-
133
- // ============================================
134
- // Commit
135
- // ============================================
136
-
137
- async commit(cwd: string, message: string): Promise<string> {
138
- const result = await execGit(['commit', '-m', message], cwd);
139
- if (result.exitCode !== 0) {
140
- throw new Error(`git commit failed: ${result.stderr}`);
141
- }
142
- // Return the commit hash
143
- const hashResult = await execGit(['rev-parse', 'HEAD'], cwd);
144
- return hashResult.stdout.trim();
145
- }
146
-
147
- async amendCommit(cwd: string, message?: string): Promise<string> {
148
- const args = ['commit', '--amend'];
149
- if (message) {
150
- args.push('-m', message);
151
- } else {
152
- args.push('--no-edit');
153
- }
154
- const result = await execGit(args, cwd);
155
- if (result.exitCode !== 0) {
156
- throw new Error(`git commit --amend failed: ${result.stderr}`);
157
- }
158
- const hashResult = await execGit(['rev-parse', 'HEAD'], cwd);
159
- return hashResult.stdout.trim();
160
- }
161
-
162
- // ============================================
163
- // Diff
164
- // ============================================
165
-
166
- async getDiffUnstaged(cwd: string, filePath?: string): Promise<GitFileDiff[]> {
167
- const args = ['diff'];
168
- if (filePath) args.push('--', filePath);
169
- const result = await execGit(args, cwd);
170
- return parseDiff(result.stdout);
171
- }
172
-
173
- async getDiffStaged(cwd: string, filePath?: string): Promise<GitFileDiff[]> {
174
- const args = ['diff', '--cached'];
175
- if (filePath) args.push('--', filePath);
176
- const result = await execGit(args, cwd);
177
- return parseDiff(result.stdout);
178
- }
179
-
180
- async getDiffCommit(cwd: string, commitHash: string): Promise<GitFileDiff[]> {
181
- const result = await execGit(['diff', `${commitHash}^`, commitHash], cwd);
182
- return parseDiff(result.stdout);
183
- }
184
-
185
- async getDiffBetween(cwd: string, from: string, to: string): Promise<GitFileDiff[]> {
186
- const result = await execGit(['diff', from, to], cwd);
187
- return parseDiff(result.stdout);
188
- }
189
-
190
- // ============================================
191
- // Branches
192
- // ============================================
193
-
194
- async getBranches(cwd: string): Promise<GitBranchInfo> {
195
- const [localResult, remoteResult] = await Promise.all([
196
- execGit(['branch', '-v', '--no-color'], cwd),
197
- execGit(['branch', '-r', '-v', '--no-color'], cwd)
198
- ]);
199
-
200
- // Handle empty repo (no commits yet) — branch command returns empty
201
- if (!localResult.stdout.trim()) {
202
- // Try to get the initial branch name from HEAD
203
- const headResult = await execGit(['symbolic-ref', '--short', 'HEAD'], cwd);
204
- const initialBranch = headResult.exitCode === 0 ? headResult.stdout.trim() : 'main';
205
- return { current: initialBranch, local: [], remote: [], ahead: 0, behind: 0 };
206
- }
207
-
208
- const branchInfo = parseBranches(localResult.stdout, remoteResult.stdout);
209
-
210
- // Get ahead/behind for current branch
211
- if (branchInfo.current) {
212
- try {
213
- const abResult = await execGit(
214
- ['rev-list', '--left-right', '--count', `${branchInfo.current}...@{upstream}`],
215
- cwd
216
- );
217
- if (abResult.exitCode === 0) {
218
- const { ahead, behind } = parseAheadBehind(abResult.stdout);
219
- branchInfo.ahead = ahead;
220
- branchInfo.behind = behind;
221
-
222
- // Update the current branch entry too
223
- const currentBranch = branchInfo.local.find(b => b.isCurrent);
224
- if (currentBranch) {
225
- currentBranch.ahead = ahead;
226
- currentBranch.behind = behind;
227
- }
228
- }
229
- } catch {
230
- // No upstream configured
231
- }
232
- }
233
-
234
- return branchInfo;
235
- }
236
-
237
- async createBranch(cwd: string, name: string, startPoint?: string): Promise<void> {
238
- const args = ['checkout', '-b', name];
239
- if (startPoint) args.push(startPoint);
240
- const result = await execGit(args, cwd);
241
- if (result.exitCode !== 0) {
242
- throw new Error(`git checkout -b failed: ${result.stderr}`);
243
- }
244
- }
245
-
246
- async switchBranch(cwd: string, name: string): Promise<void> {
247
- const result = await execGit(['checkout', name], cwd);
248
- if (result.exitCode !== 0) {
249
- throw new Error(`git checkout failed: ${result.stderr}`);
250
- }
251
- }
252
-
253
- async deleteBranch(cwd: string, name: string, force = false): Promise<void> {
254
- const flag = force ? '-D' : '-d';
255
- const result = await execGit(['branch', flag, name], cwd);
256
- if (result.exitCode !== 0) {
257
- throw new Error(`git branch ${flag} failed: ${result.stderr}`);
258
- }
259
- }
260
-
261
- async renameBranch(cwd: string, oldName: string, newName: string): Promise<void> {
262
- const result = await execGit(['branch', '-m', oldName, newName], cwd);
263
- if (result.exitCode !== 0) {
264
- throw new Error(`git branch -m failed: ${result.stderr}`);
265
- }
266
- }
267
-
268
- async mergeBranch(cwd: string, branchName: string): Promise<{ success: boolean; message: string }> {
269
- const result = await execGit(['merge', branchName], cwd);
270
- return {
271
- success: result.exitCode === 0,
272
- message: result.exitCode === 0 ? result.stdout : result.stderr
273
- };
274
- }
275
-
276
- // ============================================
277
- // Log
278
- // ============================================
279
-
280
- async getLog(cwd: string, limit = 50, skip = 0, branch?: string): Promise<GitLogResult> {
281
- const SEPARATOR = '|||';
282
- const format = `%H${SEPARATOR}%h${SEPARATOR}%an${SEPARATOR}%ae${SEPARATOR}%aI${SEPARATOR}%P${SEPARATOR}%D%n%s%x00`;
283
-
284
- const args = [
285
- 'log',
286
- `--format=${format}`,
287
- `--max-count=${limit + 1}`, // +1 to check if there are more
288
- `--skip=${skip}`
289
- ];
290
-
291
- if (branch) args.push(branch);
292
-
293
- const result = await execGit(args, cwd);
294
- if (result.exitCode !== 0) {
295
- throw new Error(`git log failed: ${result.stderr}`);
296
- }
297
-
298
- const commits = parseLog(result.stdout);
299
- const hasMore = commits.length > limit;
300
- if (hasMore) commits.pop(); // Remove the extra one
301
-
302
- // Get total count
303
- const countResult = await execGit(['rev-list', '--count', branch || 'HEAD'], cwd);
304
- const total = parseInt(countResult.stdout.trim()) || commits.length;
305
-
306
- return { commits, total, hasMore };
307
- }
308
-
309
- // ============================================
310
- // Remote Operations
311
- // ============================================
312
-
313
- async getRemotes(cwd: string): Promise<GitRemote[]> {
314
- const result = await execGit(['remote', '-v'], cwd);
315
- return parseRemotes(result.stdout);
316
- }
317
-
318
- async fetch(cwd: string, remote = 'origin'): Promise<string> {
319
- const result = await execGit(['fetch', remote, '--prune'], cwd, 60000);
320
- if (result.exitCode !== 0) {
321
- throw new Error(`git fetch failed: ${result.stderr}`);
322
- }
323
- return result.stderr || result.stdout; // git fetch outputs to stderr
324
- }
325
-
326
- async pull(cwd: string, remote = 'origin', branch?: string): Promise<{ success: boolean; message: string }> {
327
- const args = ['pull', remote];
328
- if (branch) args.push(branch);
329
- const result = await execGit(args, cwd, 60000);
330
- return {
331
- success: result.exitCode === 0,
332
- message: result.exitCode === 0 ? result.stdout : result.stderr
333
- };
334
- }
335
-
336
- async push(cwd: string, remote = 'origin', branch?: string, force = false): Promise<{ success: boolean; message: string }> {
337
- const args = ['push', remote];
338
- if (branch) args.push(branch);
339
- if (force) args.push('--force-with-lease');
340
- // Set upstream if needed
341
- args.push('-u');
342
- const result = await execGit(args, cwd, 60000);
343
- return {
344
- success: result.exitCode === 0,
345
- message: result.exitCode === 0 ? (result.stderr || result.stdout) : result.stderr
346
- };
347
- }
348
-
349
- async addRemote(cwd: string, name: string, url: string): Promise<void> {
350
- const result = await execGit(['remote', 'add', name, url], cwd);
351
- if (result.exitCode !== 0) {
352
- throw new Error(`git remote add failed: ${result.stderr}`);
353
- }
354
- }
355
-
356
- async removeRemote(cwd: string, name: string): Promise<void> {
357
- const result = await execGit(['remote', 'remove', name], cwd);
358
- if (result.exitCode !== 0) {
359
- throw new Error(`git remote remove failed: ${result.stderr}`);
360
- }
361
- }
362
-
363
- // ============================================
364
- // Stash
365
- // ============================================
366
-
367
- async stashList(cwd: string): Promise<GitStashEntry[]> {
368
- const result = await execGit(['stash', 'list'], cwd);
369
- return parseStashList(result.stdout);
370
- }
371
-
372
- async stashSave(cwd: string, message?: string): Promise<void> {
373
- const args = ['stash', 'push'];
374
- if (message) args.push('-m', message);
375
- const result = await execGit(args, cwd);
376
- if (result.exitCode !== 0) {
377
- throw new Error(`git stash failed: ${result.stderr}`);
378
- }
379
- }
380
-
381
- async stashPop(cwd: string, index = 0): Promise<void> {
382
- const result = await execGit(['stash', 'pop', `stash@{${index}}`], cwd);
383
- if (result.exitCode !== 0) {
384
- throw new Error(`git stash pop failed: ${result.stderr}`);
385
- }
386
- }
387
-
388
- async stashDrop(cwd: string, index = 0): Promise<void> {
389
- const result = await execGit(['stash', 'drop', `stash@{${index}}`], cwd);
390
- if (result.exitCode !== 0) {
391
- throw new Error(`git stash drop failed: ${result.stderr}`);
392
- }
393
- }
394
-
395
- // ============================================
396
- // Tags
397
- // ============================================
398
-
399
- async getTags(cwd: string): Promise<{ name: string; hash: string; message: string; date: string; isAnnotated: boolean }[]> {
400
- const result = await execGit(
401
- ['tag', '-l', '--sort=-creatordate', '--format=%(refname:short)|||%(objectname:short)|||%(contents:subject)|||%(creatordate:iso-strict)|||%(objecttype)'],
402
- cwd
403
- );
404
- if (result.exitCode !== 0) return [];
405
-
406
- const tags: { name: string; hash: string; message: string; date: string; isAnnotated: boolean }[] = [];
407
- const lines = result.stdout.split('\n').filter(Boolean);
408
- for (const line of lines) {
409
- const parts = line.split('|||');
410
- if (parts.length >= 2) {
411
- tags.push({
412
- name: parts[0],
413
- hash: parts[1],
414
- message: parts[2] || '',
415
- date: parts[3] || '',
416
- isAnnotated: parts[4] === 'tag'
417
- });
418
- }
419
- }
420
- return tags;
421
- }
422
-
423
- async createTag(cwd: string, name: string, message?: string, commitHash?: string): Promise<void> {
424
- const args = ['tag'];
425
- if (message) {
426
- args.push('-a', name, '-m', message);
427
- } else {
428
- args.push(name);
429
- }
430
- if (commitHash) args.push(commitHash);
431
- const result = await execGit(args, cwd);
432
- if (result.exitCode !== 0) {
433
- throw new Error(`git tag failed: ${result.stderr}`);
434
- }
435
- }
436
-
437
- async deleteTag(cwd: string, name: string): Promise<void> {
438
- const result = await execGit(['tag', '-d', name], cwd);
439
- if (result.exitCode !== 0) {
440
- throw new Error(`git tag -d failed: ${result.stderr}`);
441
- }
442
- }
443
-
444
- async pushTag(cwd: string, name: string, remote = 'origin'): Promise<{ success: boolean; message: string }> {
445
- const result = await execGit(['push', remote, name], cwd, 60000);
446
- return {
447
- success: result.exitCode === 0,
448
- message: result.exitCode === 0 ? (result.stderr || result.stdout) : result.stderr
449
- };
450
- }
451
-
452
- // ============================================
453
- // Conflict Resolution
454
- // ============================================
455
-
456
- async getConflictFiles(cwd: string): Promise<GitConflictFile[]> {
457
- const status = await this.getStatus(cwd);
458
- const conflicts: GitConflictFile[] = [];
459
-
460
- for (const file of status.conflicted) {
461
- try {
462
- const { readFile } = await import('node:fs/promises');
463
- const { join } = await import('node:path');
464
- const content = await readFile(join(cwd, file.path), 'utf-8');
465
- const markers = parseConflictMarkers(content);
466
- conflicts.push({ path: file.path, content, markers });
467
- } catch (err) {
468
- debug.error('git', `Failed to read conflict file: ${file.path}`, err);
469
- }
470
- }
471
-
472
- return conflicts;
473
- }
474
-
475
- async resolveConflict(
476
- cwd: string,
477
- filePath: string,
478
- resolution: 'ours' | 'theirs' | 'custom',
479
- customContent?: string
480
- ): Promise<void> {
481
- if (resolution === 'custom' && customContent !== undefined) {
482
- // Write custom content
483
- const { writeFile } = await import('node:fs/promises');
484
- const { join } = await import('node:path');
485
- await writeFile(join(cwd, filePath), customContent, 'utf-8');
486
- } else if (resolution === 'ours') {
487
- await execGit(['checkout', '--ours', '--', filePath], cwd);
488
- } else if (resolution === 'theirs') {
489
- await execGit(['checkout', '--theirs', '--', filePath], cwd);
490
- }
491
-
492
- // Stage the resolved file
493
- await this.stageFile(cwd, filePath);
494
- }
495
-
496
- async abortMerge(cwd: string): Promise<void> {
497
- const result = await execGit(['merge', '--abort'], cwd);
498
- if (result.exitCode !== 0) {
499
- throw new Error(`git merge --abort failed: ${result.stderr}`);
500
- }
501
- }
502
- }
503
-
504
- // Singleton
505
- export const gitService = new GitService();
1
+ /**
2
+ * Git Service
3
+ * High-level git operations built on top of executor and parser
4
+ */
5
+
6
+ import { execGit, isGitRepo, getGitRoot } from './git-executor';
7
+ import {
8
+ parseStatus,
9
+ parseBranches,
10
+ parseAheadBehind,
11
+ parseDiff,
12
+ parseLog,
13
+ parseRemotes,
14
+ parseStashList,
15
+ parseConflictMarkers
16
+ } from './git-parser';
17
+ import type {
18
+ GitStatus,
19
+ GitBranchInfo,
20
+ GitFileDiff,
21
+ GitLogResult,
22
+ GitRemote,
23
+ GitStashEntry,
24
+ GitConflictFile
25
+ } from '$shared/types/git';
26
+ import { debug } from '$shared/utils/logger';
27
+
28
+ export class GitService {
29
+ /**
30
+ * Check if path is a git repo
31
+ */
32
+ async isRepo(cwd: string): Promise<boolean> {
33
+ return isGitRepo(cwd);
34
+ }
35
+
36
+ /**
37
+ * Get repository root
38
+ */
39
+ async getRoot(cwd: string): Promise<string | null> {
40
+ return getGitRoot(cwd);
41
+ }
42
+
43
+ /**
44
+ * Initialize a new git repository
45
+ */
46
+ async init(cwd: string, defaultBranch?: string): Promise<void> {
47
+ const args = ['init'];
48
+ if (defaultBranch) args.push('-b', defaultBranch);
49
+ const result = await execGit(args, cwd);
50
+ if (result.exitCode !== 0) {
51
+ throw new Error(`git init failed: ${result.stderr}`);
52
+ }
53
+ }
54
+
55
+ // ============================================
56
+ // Status
57
+ // ============================================
58
+
59
+ async getStatus(cwd: string): Promise<GitStatus> {
60
+ const result = await execGit(['status', '--porcelain=v1', '-u'], cwd);
61
+ if (result.exitCode !== 0) {
62
+ throw new Error(`git status failed: ${result.stderr}`);
63
+ }
64
+ return parseStatus(result.stdout);
65
+ }
66
+
67
+ // ============================================
68
+ // Staging
69
+ // ============================================
70
+
71
+ async stageFile(cwd: string, filePath: string): Promise<void> {
72
+ const result = await execGit(['add', '--', filePath], cwd);
73
+ if (result.exitCode !== 0) {
74
+ throw new Error(`git add failed: ${result.stderr}`);
75
+ }
76
+ }
77
+
78
+ async stageAll(cwd: string): Promise<void> {
79
+ const result = await execGit(['add', '-A'], cwd);
80
+ if (result.exitCode !== 0) {
81
+ throw new Error(`git add -A failed: ${result.stderr}`);
82
+ }
83
+ }
84
+
85
+ async unstageFile(cwd: string, filePath: string): Promise<void> {
86
+ // Try normal reset first, fall back to rm --cached for initial commit
87
+ const result = await execGit(['reset', 'HEAD', '--', filePath], cwd);
88
+ if (result.exitCode !== 0) {
89
+ const fallback = await execGit(['rm', '--cached', '--', filePath], cwd);
90
+ if (fallback.exitCode !== 0) {
91
+ throw new Error(`git unstage failed: ${result.stderr}`);
92
+ }
93
+ }
94
+ }
95
+
96
+ async unstageAll(cwd: string): Promise<void> {
97
+ // Try normal reset first, fall back to rm --cached for initial commit
98
+ const result = await execGit(['reset', 'HEAD'], cwd);
99
+ if (result.exitCode !== 0) {
100
+ const fallback = await execGit(['rm', '-r', '--cached', '.'], cwd);
101
+ if (fallback.exitCode !== 0) {
102
+ throw new Error(`git unstage all failed: ${result.stderr}`);
103
+ }
104
+ }
105
+ }
106
+
107
+ async discardFile(cwd: string, filePath: string): Promise<void> {
108
+ // Check if file is untracked
109
+ const statusResult = await execGit(['status', '--porcelain=v1', '--', filePath], cwd);
110
+ const statusLine = statusResult.stdout.trim();
111
+
112
+ if (statusLine.startsWith('??')) {
113
+ // Untracked file - delete it
114
+ const { unlink } = await import('node:fs/promises');
115
+ const { join } = await import('node:path');
116
+ await unlink(join(cwd, filePath));
117
+ } else {
118
+ // Tracked file - restore it
119
+ const result = await execGit(['checkout', '--', filePath], cwd);
120
+ if (result.exitCode !== 0) {
121
+ throw new Error(`git checkout failed: ${result.stderr}`);
122
+ }
123
+ }
124
+ }
125
+
126
+ async discardAll(cwd: string): Promise<void> {
127
+ // Restore tracked files
128
+ await execGit(['checkout', '--', '.'], cwd);
129
+ // Remove untracked files
130
+ await execGit(['clean', '-fd'], cwd);
131
+ }
132
+
133
+ // ============================================
134
+ // Commit
135
+ // ============================================
136
+
137
+ async commit(cwd: string, message: string): Promise<string> {
138
+ const result = await execGit(['commit', '-m', message], cwd);
139
+ if (result.exitCode !== 0) {
140
+ throw new Error(`git commit failed: ${result.stderr}`);
141
+ }
142
+ // Return the commit hash
143
+ const hashResult = await execGit(['rev-parse', 'HEAD'], cwd);
144
+ return hashResult.stdout.trim();
145
+ }
146
+
147
+ async amendCommit(cwd: string, message?: string): Promise<string> {
148
+ const args = ['commit', '--amend'];
149
+ if (message) {
150
+ args.push('-m', message);
151
+ } else {
152
+ args.push('--no-edit');
153
+ }
154
+ const result = await execGit(args, cwd);
155
+ if (result.exitCode !== 0) {
156
+ throw new Error(`git commit --amend failed: ${result.stderr}`);
157
+ }
158
+ const hashResult = await execGit(['rev-parse', 'HEAD'], cwd);
159
+ return hashResult.stdout.trim();
160
+ }
161
+
162
+ // ============================================
163
+ // Diff
164
+ // ============================================
165
+
166
+ async getDiffUnstaged(cwd: string, filePath?: string): Promise<GitFileDiff[]> {
167
+ const args = ['diff'];
168
+ if (filePath) args.push('--', filePath);
169
+ const result = await execGit(args, cwd);
170
+ return parseDiff(result.stdout);
171
+ }
172
+
173
+ async getDiffStaged(cwd: string, filePath?: string): Promise<GitFileDiff[]> {
174
+ const args = ['diff', '--cached'];
175
+ if (filePath) args.push('--', filePath);
176
+ const result = await execGit(args, cwd);
177
+ return parseDiff(result.stdout);
178
+ }
179
+
180
+ async getDiffCommit(cwd: string, commitHash: string): Promise<GitFileDiff[]> {
181
+ const result = await execGit(['diff', `${commitHash}^`, commitHash], cwd);
182
+ return parseDiff(result.stdout);
183
+ }
184
+
185
+ async getDiffBetween(cwd: string, from: string, to: string): Promise<GitFileDiff[]> {
186
+ const result = await execGit(['diff', from, to], cwd);
187
+ return parseDiff(result.stdout);
188
+ }
189
+
190
+ // ============================================
191
+ // Branches
192
+ // ============================================
193
+
194
+ async getBranches(cwd: string): Promise<GitBranchInfo> {
195
+ const [localResult, remoteResult] = await Promise.all([
196
+ execGit(['branch', '-v', '--no-color'], cwd),
197
+ execGit(['branch', '-r', '-v', '--no-color'], cwd)
198
+ ]);
199
+
200
+ // Handle empty repo (no commits yet) — branch command returns empty
201
+ if (!localResult.stdout.trim()) {
202
+ // Try to get the initial branch name from HEAD
203
+ const headResult = await execGit(['symbolic-ref', '--short', 'HEAD'], cwd);
204
+ const initialBranch = headResult.exitCode === 0 ? headResult.stdout.trim() : 'main';
205
+ return { current: initialBranch, local: [], remote: [], ahead: 0, behind: 0 };
206
+ }
207
+
208
+ const branchInfo = parseBranches(localResult.stdout, remoteResult.stdout);
209
+
210
+ // Get ahead/behind for current branch
211
+ if (branchInfo.current) {
212
+ try {
213
+ const abResult = await execGit(
214
+ ['rev-list', '--left-right', '--count', `${branchInfo.current}...@{upstream}`],
215
+ cwd
216
+ );
217
+ if (abResult.exitCode === 0) {
218
+ const { ahead, behind } = parseAheadBehind(abResult.stdout);
219
+ branchInfo.ahead = ahead;
220
+ branchInfo.behind = behind;
221
+
222
+ // Update the current branch entry too
223
+ const currentBranch = branchInfo.local.find(b => b.isCurrent);
224
+ if (currentBranch) {
225
+ currentBranch.ahead = ahead;
226
+ currentBranch.behind = behind;
227
+ }
228
+ }
229
+ } catch {
230
+ // No upstream configured
231
+ }
232
+ }
233
+
234
+ return branchInfo;
235
+ }
236
+
237
+ async createBranch(cwd: string, name: string, startPoint?: string): Promise<void> {
238
+ const args = ['checkout', '-b', name];
239
+ if (startPoint) args.push(startPoint);
240
+ const result = await execGit(args, cwd);
241
+ if (result.exitCode !== 0) {
242
+ throw new Error(`git checkout -b failed: ${result.stderr}`);
243
+ }
244
+ }
245
+
246
+ async switchBranch(cwd: string, name: string): Promise<void> {
247
+ const result = await execGit(['checkout', name], cwd);
248
+ if (result.exitCode !== 0) {
249
+ throw new Error(`git checkout failed: ${result.stderr}`);
250
+ }
251
+ }
252
+
253
+ async deleteBranch(cwd: string, name: string, force = false): Promise<void> {
254
+ const flag = force ? '-D' : '-d';
255
+ const result = await execGit(['branch', flag, name], cwd);
256
+ if (result.exitCode !== 0) {
257
+ throw new Error(`git branch ${flag} failed: ${result.stderr}`);
258
+ }
259
+ }
260
+
261
+ async renameBranch(cwd: string, oldName: string, newName: string): Promise<void> {
262
+ const result = await execGit(['branch', '-m', oldName, newName], cwd);
263
+ if (result.exitCode !== 0) {
264
+ throw new Error(`git branch -m failed: ${result.stderr}`);
265
+ }
266
+ }
267
+
268
+ async mergeBranch(cwd: string, branchName: string): Promise<{ success: boolean; message: string }> {
269
+ const result = await execGit(['merge', branchName], cwd);
270
+ return {
271
+ success: result.exitCode === 0,
272
+ message: result.exitCode === 0 ? result.stdout : result.stderr
273
+ };
274
+ }
275
+
276
+ // ============================================
277
+ // Log
278
+ // ============================================
279
+
280
+ async getLog(cwd: string, limit = 50, skip = 0, branch?: string): Promise<GitLogResult> {
281
+ const SEPARATOR = '|||';
282
+ const format = `%H${SEPARATOR}%h${SEPARATOR}%an${SEPARATOR}%ae${SEPARATOR}%aI${SEPARATOR}%P${SEPARATOR}%D%n%s%x00`;
283
+
284
+ const args = [
285
+ 'log',
286
+ `--format=${format}`,
287
+ `--max-count=${limit + 1}`, // +1 to check if there are more
288
+ `--skip=${skip}`
289
+ ];
290
+
291
+ if (branch) args.push(branch);
292
+
293
+ const result = await execGit(args, cwd);
294
+ if (result.exitCode !== 0) {
295
+ throw new Error(`git log failed: ${result.stderr}`);
296
+ }
297
+
298
+ const commits = parseLog(result.stdout);
299
+ const hasMore = commits.length > limit;
300
+ if (hasMore) commits.pop(); // Remove the extra one
301
+
302
+ // Get total count
303
+ const countResult = await execGit(['rev-list', '--count', branch || 'HEAD'], cwd);
304
+ const total = parseInt(countResult.stdout.trim()) || commits.length;
305
+
306
+ return { commits, total, hasMore };
307
+ }
308
+
309
+ // ============================================
310
+ // Remote Operations
311
+ // ============================================
312
+
313
+ async getRemotes(cwd: string): Promise<GitRemote[]> {
314
+ const result = await execGit(['remote', '-v'], cwd);
315
+ return parseRemotes(result.stdout);
316
+ }
317
+
318
+ async fetch(cwd: string, remote = 'origin'): Promise<string> {
319
+ const result = await execGit(['fetch', remote, '--prune'], cwd, 60000);
320
+ if (result.exitCode !== 0) {
321
+ throw new Error(`git fetch failed: ${result.stderr}`);
322
+ }
323
+ return result.stderr || result.stdout; // git fetch outputs to stderr
324
+ }
325
+
326
+ async pull(cwd: string, remote = 'origin', branch?: string): Promise<{ success: boolean; message: string }> {
327
+ const args = ['pull', remote];
328
+ if (branch) args.push(branch);
329
+ const result = await execGit(args, cwd, 60000);
330
+ return {
331
+ success: result.exitCode === 0,
332
+ message: result.exitCode === 0 ? result.stdout : result.stderr
333
+ };
334
+ }
335
+
336
+ async push(cwd: string, remote = 'origin', branch?: string, force = false): Promise<{ success: boolean; message: string }> {
337
+ const args = ['push', remote];
338
+ if (branch) args.push(branch);
339
+ if (force) args.push('--force-with-lease');
340
+ // Set upstream if needed
341
+ args.push('-u');
342
+ const result = await execGit(args, cwd, 60000);
343
+ return {
344
+ success: result.exitCode === 0,
345
+ message: result.exitCode === 0 ? (result.stderr || result.stdout) : result.stderr
346
+ };
347
+ }
348
+
349
+ async addRemote(cwd: string, name: string, url: string): Promise<void> {
350
+ const result = await execGit(['remote', 'add', name, url], cwd);
351
+ if (result.exitCode !== 0) {
352
+ throw new Error(`git remote add failed: ${result.stderr}`);
353
+ }
354
+ }
355
+
356
+ async removeRemote(cwd: string, name: string): Promise<void> {
357
+ const result = await execGit(['remote', 'remove', name], cwd);
358
+ if (result.exitCode !== 0) {
359
+ throw new Error(`git remote remove failed: ${result.stderr}`);
360
+ }
361
+ }
362
+
363
+ // ============================================
364
+ // Stash
365
+ // ============================================
366
+
367
+ async stashList(cwd: string): Promise<GitStashEntry[]> {
368
+ const result = await execGit(['stash', 'list'], cwd);
369
+ return parseStashList(result.stdout);
370
+ }
371
+
372
+ async stashSave(cwd: string, message?: string): Promise<void> {
373
+ const args = ['stash', 'push'];
374
+ if (message) args.push('-m', message);
375
+ const result = await execGit(args, cwd);
376
+ if (result.exitCode !== 0) {
377
+ throw new Error(`git stash failed: ${result.stderr}`);
378
+ }
379
+ }
380
+
381
+ async stashPop(cwd: string, index = 0): Promise<void> {
382
+ const result = await execGit(['stash', 'pop', `stash@{${index}}`], cwd);
383
+ if (result.exitCode !== 0) {
384
+ throw new Error(`git stash pop failed: ${result.stderr}`);
385
+ }
386
+ }
387
+
388
+ async stashDrop(cwd: string, index = 0): Promise<void> {
389
+ const result = await execGit(['stash', 'drop', `stash@{${index}}`], cwd);
390
+ if (result.exitCode !== 0) {
391
+ throw new Error(`git stash drop failed: ${result.stderr}`);
392
+ }
393
+ }
394
+
395
+ // ============================================
396
+ // Tags
397
+ // ============================================
398
+
399
+ async getTags(cwd: string): Promise<{ name: string; hash: string; message: string; date: string; isAnnotated: boolean }[]> {
400
+ const result = await execGit(
401
+ ['tag', '-l', '--sort=-creatordate', '--format=%(refname:short)|||%(objectname:short)|||%(contents:subject)|||%(creatordate:iso-strict)|||%(objecttype)'],
402
+ cwd
403
+ );
404
+ if (result.exitCode !== 0) return [];
405
+
406
+ const tags: { name: string; hash: string; message: string; date: string; isAnnotated: boolean }[] = [];
407
+ const lines = result.stdout.split('\n').filter(Boolean);
408
+ for (const line of lines) {
409
+ const parts = line.split('|||');
410
+ if (parts.length >= 2) {
411
+ tags.push({
412
+ name: parts[0],
413
+ hash: parts[1],
414
+ message: parts[2] || '',
415
+ date: parts[3] || '',
416
+ isAnnotated: parts[4] === 'tag'
417
+ });
418
+ }
419
+ }
420
+ return tags;
421
+ }
422
+
423
+ async createTag(cwd: string, name: string, message?: string, commitHash?: string): Promise<void> {
424
+ const args = ['tag'];
425
+ if (message) {
426
+ args.push('-a', name, '-m', message);
427
+ } else {
428
+ args.push(name);
429
+ }
430
+ if (commitHash) args.push(commitHash);
431
+ const result = await execGit(args, cwd);
432
+ if (result.exitCode !== 0) {
433
+ throw new Error(`git tag failed: ${result.stderr}`);
434
+ }
435
+ }
436
+
437
+ async deleteTag(cwd: string, name: string): Promise<void> {
438
+ const result = await execGit(['tag', '-d', name], cwd);
439
+ if (result.exitCode !== 0) {
440
+ throw new Error(`git tag -d failed: ${result.stderr}`);
441
+ }
442
+ }
443
+
444
+ async pushTag(cwd: string, name: string, remote = 'origin'): Promise<{ success: boolean; message: string }> {
445
+ const result = await execGit(['push', remote, name], cwd, 60000);
446
+ return {
447
+ success: result.exitCode === 0,
448
+ message: result.exitCode === 0 ? (result.stderr || result.stdout) : result.stderr
449
+ };
450
+ }
451
+
452
+ // ============================================
453
+ // Conflict Resolution
454
+ // ============================================
455
+
456
+ async getConflictFiles(cwd: string): Promise<GitConflictFile[]> {
457
+ const status = await this.getStatus(cwd);
458
+ const conflicts: GitConflictFile[] = [];
459
+
460
+ for (const file of status.conflicted) {
461
+ try {
462
+ const { readFile } = await import('node:fs/promises');
463
+ const { join } = await import('node:path');
464
+ const content = await readFile(join(cwd, file.path), 'utf-8');
465
+ const markers = parseConflictMarkers(content);
466
+ conflicts.push({ path: file.path, content, markers });
467
+ } catch (err) {
468
+ debug.error('git', `Failed to read conflict file: ${file.path}`, err);
469
+ }
470
+ }
471
+
472
+ return conflicts;
473
+ }
474
+
475
+ async resolveConflict(
476
+ cwd: string,
477
+ filePath: string,
478
+ resolution: 'ours' | 'theirs' | 'custom',
479
+ customContent?: string
480
+ ): Promise<void> {
481
+ if (resolution === 'custom' && customContent !== undefined) {
482
+ // Write custom content
483
+ const { writeFile } = await import('node:fs/promises');
484
+ const { join } = await import('node:path');
485
+ await writeFile(join(cwd, filePath), customContent, 'utf-8');
486
+ } else if (resolution === 'ours') {
487
+ await execGit(['checkout', '--ours', '--', filePath], cwd);
488
+ } else if (resolution === 'theirs') {
489
+ await execGit(['checkout', '--theirs', '--', filePath], cwd);
490
+ }
491
+
492
+ // Stage the resolved file
493
+ await this.stageFile(cwd, filePath);
494
+ }
495
+
496
+ async abortMerge(cwd: string): Promise<void> {
497
+ const result = await execGit(['merge', '--abort'], cwd);
498
+ if (result.exitCode !== 0) {
499
+ throw new Error(`git merge --abort failed: ${result.stderr}`);
500
+ }
501
+ }
502
+ }
503
+
504
+ // Singleton
505
+ export const gitService = new GitService();