@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.
- package/.env.example +5 -5
- package/.github/workflows/{release.yml → ci.yml} +86 -60
- package/CONTRIBUTING.md +499 -499
- package/LICENSE +21 -21
- package/README.md +209 -209
- package/backend/index.ts +168 -156
- package/backend/lib/chat/helpers.ts +42 -42
- package/backend/lib/chat/index.ts +1 -1
- package/backend/lib/chat/stream-manager.ts +1126 -1126
- package/backend/lib/database/README.md +76 -76
- package/backend/lib/database/index.ts +118 -118
- package/backend/lib/database/migrations/001_create_projects_table.ts +30 -30
- package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +32 -32
- package/backend/lib/database/migrations/003_create_messages_table.ts +31 -31
- package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -34
- package/backend/lib/database/migrations/005_create_settings_table.ts +23 -23
- package/backend/lib/database/migrations/006_add_user_to_messages.ts +57 -57
- package/backend/lib/database/migrations/007_create_stream_states_table.ts +40 -40
- package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +61 -61
- package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -41
- package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -70
- package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -156
- package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -41
- package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -118
- package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -18
- package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -18
- package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -34
- package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -32
- package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -24
- package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -18
- package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -32
- package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -33
- package/backend/lib/database/migrations/index.ts +153 -153
- package/backend/lib/database/queries/checkpoint-queries.ts +87 -87
- package/backend/lib/database/queries/engine-queries.ts +75 -75
- package/backend/lib/database/queries/index.ts +8 -8
- package/backend/lib/database/queries/message-queries.ts +471 -471
- package/backend/lib/database/queries/project-queries.ts +117 -117
- package/backend/lib/database/queries/session-queries.ts +270 -270
- package/backend/lib/database/queries/settings-queries.ts +33 -33
- package/backend/lib/database/queries/snapshot-queries.ts +325 -325
- package/backend/lib/database/queries/utils-queries.ts +58 -58
- package/backend/lib/database/seeders/index.ts +12 -12
- package/backend/lib/database/seeders/settings_seeder.ts +83 -83
- package/backend/lib/database/utils/connection.ts +173 -173
- package/backend/lib/database/utils/index.ts +3 -3
- package/backend/lib/database/utils/migration-runner.ts +117 -117
- package/backend/lib/database/utils/seeder-runner.ts +120 -120
- package/backend/lib/engine/adapters/claude/environment.ts +160 -164
- package/backend/lib/engine/adapters/claude/error-handler.ts +60 -60
- package/backend/lib/engine/adapters/claude/index.ts +1 -1
- package/backend/lib/engine/adapters/claude/path-utils.ts +38 -38
- package/backend/lib/engine/adapters/claude/stream.ts +177 -177
- package/backend/lib/engine/adapters/opencode/index.ts +2 -2
- package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -862
- package/backend/lib/engine/adapters/opencode/server.ts +104 -104
- package/backend/lib/engine/adapters/opencode/stream.ts +755 -755
- package/backend/lib/engine/index.ts +196 -196
- package/backend/lib/engine/types.ts +58 -58
- package/backend/lib/files/file-operations.ts +478 -478
- package/backend/lib/files/file-reading.ts +308 -308
- package/backend/lib/files/file-watcher.ts +383 -383
- package/backend/lib/files/path-browsing.ts +382 -382
- package/backend/lib/git/git-executor.ts +89 -88
- package/backend/lib/git/git-parser.ts +411 -411
- package/backend/lib/git/git-service.ts +505 -505
- package/backend/lib/mcp/README.md +1144 -1144
- package/backend/lib/mcp/config.ts +317 -316
- package/backend/lib/mcp/index.ts +35 -35
- package/backend/lib/mcp/project-context.ts +236 -236
- package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -156
- package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -419
- package/backend/lib/mcp/servers/browser-automation/index.ts +791 -791
- package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -501
- package/backend/lib/mcp/servers/helper.ts +143 -143
- package/backend/lib/mcp/servers/index.ts +44 -44
- package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -56
- package/backend/lib/mcp/servers/weather/index.ts +31 -31
- package/backend/lib/mcp/stdio-server.ts +103 -103
- package/backend/lib/mcp/types.ts +65 -65
- package/backend/lib/preview/browser/browser-audio-capture.ts +86 -86
- package/backend/lib/preview/browser/browser-console-manager.ts +262 -262
- package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -222
- package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -421
- package/backend/lib/preview/browser/browser-mcp-control.ts +415 -415
- package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -512
- package/backend/lib/preview/browser/browser-navigation-tracker.ts +103 -103
- package/backend/lib/preview/browser/browser-pool.ts +357 -357
- package/backend/lib/preview/browser/browser-preview-service.ts +882 -882
- package/backend/lib/preview/browser/browser-tab-manager.ts +935 -935
- package/backend/lib/preview/browser/browser-video-capture.ts +695 -695
- package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -292
- package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -85
- package/backend/lib/preview/browser/scripts/video-stream.ts +438 -438
- package/backend/lib/preview/browser/types.ts +359 -359
- package/backend/lib/preview/index.ts +23 -23
- package/backend/lib/project/index.ts +1 -1
- package/backend/lib/project/status-manager.ts +181 -181
- package/backend/lib/shared/env.ts +117 -0
- package/backend/lib/shared/index.ts +5 -2
- package/backend/lib/shared/port-utils.ts +25 -25
- package/backend/lib/shared/process-manager.ts +280 -280
- package/backend/lib/snapshot/blob-store.ts +227 -227
- package/backend/lib/snapshot/gitignore.ts +307 -307
- package/backend/lib/snapshot/helpers.ts +397 -397
- package/backend/lib/snapshot/snapshot-service.ts +483 -483
- package/backend/lib/terminal/helpers.ts +14 -14
- package/backend/lib/terminal/index.ts +7 -7
- package/backend/lib/terminal/pty-manager.ts +3 -3
- package/backend/lib/terminal/pty-session-manager.ts +370 -387
- package/backend/lib/terminal/shell-utils.ts +315 -312
- package/backend/lib/terminal/stream-manager.ts +292 -292
- package/backend/lib/tunnel/global-tunnel-manager.ts +266 -243
- package/backend/lib/tunnel/project-tunnel-manager.ts +311 -311
- package/backend/lib/user/helpers.ts +87 -87
- package/backend/lib/utils/ws.ts +944 -944
- package/backend/lib/vite-dev.ts +295 -295
- package/backend/middleware/cors.ts +16 -15
- package/backend/middleware/error-handler.ts +50 -49
- package/backend/middleware/logger.ts +9 -9
- package/backend/types/api.ts +24 -24
- package/backend/ws/README.md +1505 -1505
- package/backend/ws/chat/background.ts +198 -198
- package/backend/ws/chat/index.ts +21 -21
- package/backend/ws/chat/stream.ts +707 -707
- package/backend/ws/engine/claude/accounts.ts +399 -401
- package/backend/ws/engine/claude/index.ts +13 -13
- package/backend/ws/engine/claude/status.ts +43 -43
- package/backend/ws/engine/index.ts +14 -14
- package/backend/ws/engine/opencode/index.ts +11 -11
- package/backend/ws/engine/opencode/status.ts +30 -30
- package/backend/ws/engine/utils.ts +36 -36
- package/backend/ws/files/index.ts +30 -30
- package/backend/ws/files/read.ts +189 -189
- package/backend/ws/files/search.ts +453 -453
- package/backend/ws/files/watch.ts +124 -124
- package/backend/ws/files/write.ts +143 -143
- package/backend/ws/git/branch.ts +106 -106
- package/backend/ws/git/commit.ts +39 -39
- package/backend/ws/git/conflict.ts +68 -68
- package/backend/ws/git/diff.ts +69 -69
- package/backend/ws/git/index.ts +24 -24
- package/backend/ws/git/log.ts +41 -41
- package/backend/ws/git/remote.ts +214 -214
- package/backend/ws/git/staging.ts +84 -84
- package/backend/ws/git/status.ts +90 -90
- package/backend/ws/index.ts +69 -69
- package/backend/ws/mcp/index.ts +61 -61
- package/backend/ws/messages/crud.ts +74 -74
- package/backend/ws/messages/index.ts +14 -14
- package/backend/ws/preview/browser/cleanup.ts +129 -129
- package/backend/ws/preview/browser/console.ts +114 -114
- package/backend/ws/preview/browser/interact.ts +513 -513
- package/backend/ws/preview/browser/mcp.ts +129 -129
- package/backend/ws/preview/browser/native-ui.ts +235 -235
- package/backend/ws/preview/browser/stats.ts +55 -55
- package/backend/ws/preview/browser/tab-info.ts +126 -126
- package/backend/ws/preview/browser/tab.ts +166 -166
- package/backend/ws/preview/browser/webcodecs.ts +293 -293
- package/backend/ws/preview/index.ts +146 -146
- package/backend/ws/projects/crud.ts +113 -113
- package/backend/ws/projects/index.ts +25 -25
- package/backend/ws/projects/presence.ts +46 -46
- package/backend/ws/projects/status.ts +116 -116
- package/backend/ws/sessions/crud.ts +327 -327
- package/backend/ws/sessions/index.ts +33 -33
- package/backend/ws/settings/crud.ts +112 -112
- package/backend/ws/settings/index.ts +14 -14
- package/backend/ws/snapshot/index.ts +17 -17
- package/backend/ws/snapshot/restore.ts +173 -173
- package/backend/ws/snapshot/timeline.ts +141 -141
- package/backend/ws/system/index.ts +14 -14
- package/backend/ws/system/operations.ts +49 -49
- package/backend/ws/terminal/index.ts +40 -40
- package/backend/ws/terminal/persistence.ts +153 -153
- package/backend/ws/terminal/session.ts +382 -382
- package/backend/ws/terminal/stream.ts +79 -79
- package/backend/ws/tunnel/index.ts +14 -14
- package/backend/ws/tunnel/operations.ts +91 -91
- package/backend/ws/types.ts +20 -20
- package/backend/ws/user/crud.ts +156 -156
- package/backend/ws/user/index.ts +14 -14
- package/bin/clopen.ts +307 -307
- package/bun.lock +1353 -1352
- package/frontend/App.svelte +38 -34
- package/frontend/app.css +313 -313
- package/frontend/lib/app-environment.ts +10 -10
- package/frontend/lib/components/chat/ChatInterface.svelte +406 -406
- package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +56 -56
- package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +223 -223
- package/frontend/lib/components/chat/formatters/TextMessage.svelte +394 -394
- package/frontend/lib/components/chat/formatters/Tools.svelte +69 -69
- package/frontend/lib/components/chat/formatters/index.ts +2 -2
- package/frontend/lib/components/chat/input/ChatInput.svelte +421 -421
- package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -78
- package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -30
- package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -33
- package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -619
- package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -48
- package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -31
- package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -201
- package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -148
- package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -216
- package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -357
- package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -57
- package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -478
- package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -541
- package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -86
- package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -86
- package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -157
- package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -59
- package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -124
- package/frontend/lib/components/chat/shared/index.ts +1 -1
- package/frontend/lib/components/chat/shared/utils.ts +115 -115
- package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -35
- package/frontend/lib/components/chat/tools/BashTool.svelte +45 -45
- package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -139
- package/frontend/lib/components/chat/tools/EditTool.svelte +47 -47
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +31 -31
- package/frontend/lib/components/chat/tools/GlobTool.svelte +50 -50
- package/frontend/lib/components/chat/tools/GrepTool.svelte +89 -89
- package/frontend/lib/components/chat/tools/KillShellTool.svelte +25 -25
- package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +30 -30
- package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +37 -37
- package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +33 -33
- package/frontend/lib/components/chat/tools/ReadTool.svelte +40 -40
- package/frontend/lib/components/chat/tools/TaskTool.svelte +63 -63
- package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +74 -74
- package/frontend/lib/components/chat/tools/WebFetchTool.svelte +34 -34
- package/frontend/lib/components/chat/tools/WebSearchTool.svelte +83 -83
- package/frontend/lib/components/chat/tools/WriteTool.svelte +32 -32
- package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +78 -78
- package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +407 -407
- package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -45
- package/frontend/lib/components/chat/tools/components/InfoLine.svelte +18 -18
- package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +26 -26
- package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +53 -53
- package/frontend/lib/components/chat/tools/components/index.ts +7 -7
- package/frontend/lib/components/chat/tools/index.ts +25 -25
- package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +248 -248
- package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -78
- package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -391
- package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -26
- package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +86 -86
- package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -108
- package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -59
- package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -168
- package/frontend/lib/components/checkpoint/timeline/config.ts +44 -44
- package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -304
- package/frontend/lib/components/checkpoint/timeline/types.ts +65 -65
- package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -53
- package/frontend/lib/components/common/Alert.svelte +138 -138
- package/frontend/lib/components/common/AvatarBubble.svelte +55 -55
- package/frontend/lib/components/common/Button.svelte +71 -71
- package/frontend/lib/components/common/Card.svelte +102 -102
- package/frontend/lib/components/common/Checkbox.svelte +48 -48
- package/frontend/lib/components/common/Dialog.svelte +248 -248
- package/frontend/lib/components/common/FolderBrowser.svelte +842 -842
- package/frontend/lib/components/common/Icon.svelte +57 -57
- package/frontend/lib/components/common/Input.svelte +72 -72
- package/frontend/lib/components/common/Lightbox.svelte +232 -232
- package/frontend/lib/components/common/LoadingScreen.svelte +52 -52
- package/frontend/lib/components/common/LoadingSpinner.svelte +48 -48
- package/frontend/lib/components/common/Modal.svelte +177 -177
- package/frontend/lib/components/common/ModalProvider.svelte +27 -27
- package/frontend/lib/components/common/ModelSelector.svelte +110 -110
- package/frontend/lib/components/common/MonacoEditor.svelte +568 -568
- package/frontend/lib/components/common/NotificationToast.svelte +113 -113
- package/frontend/lib/components/common/PageTemplate.svelte +75 -75
- package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -79
- package/frontend/lib/components/common/Select.svelte +97 -97
- package/frontend/lib/components/common/Textarea.svelte +79 -79
- package/frontend/lib/components/common/ThemeToggle.svelte +44 -44
- package/frontend/lib/components/common/lucide-icons.ts +1642 -1642
- package/frontend/lib/components/common/material-icons.ts +1082 -1082
- package/frontend/lib/components/common/xterm/XTerm.svelte +809 -795
- package/frontend/lib/components/common/xterm/index.ts +15 -15
- package/frontend/lib/components/common/xterm/terminal-config.ts +67 -67
- package/frontend/lib/components/common/xterm/types.ts +30 -30
- package/frontend/lib/components/common/xterm/xterm-service.ts +379 -353
- package/frontend/lib/components/files/FileNode.svelte +383 -383
- package/frontend/lib/components/files/FileTree.svelte +681 -681
- package/frontend/lib/components/files/FileViewer.svelte +728 -728
- package/frontend/lib/components/files/SearchResults.svelte +303 -303
- package/frontend/lib/components/git/BranchManager.svelte +458 -458
- package/frontend/lib/components/git/ChangesSection.svelte +107 -107
- package/frontend/lib/components/git/CommitForm.svelte +76 -76
- package/frontend/lib/components/git/ConflictResolver.svelte +158 -158
- package/frontend/lib/components/git/DiffViewer.svelte +364 -364
- package/frontend/lib/components/git/FileChangeItem.svelte +97 -97
- package/frontend/lib/components/git/GitButton.svelte +33 -33
- package/frontend/lib/components/git/GitLog.svelte +361 -361
- package/frontend/lib/components/git/GitModal.svelte +80 -80
- package/frontend/lib/components/history/HistoryModal.svelte +563 -563
- package/frontend/lib/components/history/HistoryView.svelte +614 -614
- package/frontend/lib/components/index.ts +34 -34
- package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -549
- package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -1058
- package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +756 -756
- package/frontend/lib/components/preview/browser/components/Container.svelte +450 -450
- package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -236
- package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -224
- package/frontend/lib/components/preview/browser/components/Toolbar.svelte +338 -338
- package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +35 -35
- package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -155
- package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -837
- package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -113
- package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -296
- package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -391
- package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -231
- package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -210
- package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -239
- package/frontend/lib/components/preview/index.ts +1 -1
- package/frontend/lib/components/settings/SettingsModal.svelte +235 -235
- package/frontend/lib/components/settings/SettingsView.svelte +36 -36
- package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -51
- package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -160
- package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -76
- package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -917
- package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -187
- package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -203
- package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -10
- package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -357
- package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -205
- package/frontend/lib/components/settings/user/UserSettings.svelte +197 -197
- package/frontend/lib/components/terminal/Terminal.svelte +367 -367
- package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -87
- package/frontend/lib/components/terminal/TerminalView.svelte +54 -54
- package/frontend/lib/components/tunnel/TunnelActive.svelte +157 -142
- package/frontend/lib/components/tunnel/TunnelButton.svelte +60 -54
- package/frontend/lib/components/tunnel/TunnelInactive.svelte +285 -284
- package/frontend/lib/components/tunnel/TunnelModal.svelte +48 -47
- package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -49
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -382
- package/frontend/lib/components/workspace/MobileNavigator.svelte +394 -403
- package/frontend/lib/components/workspace/PanelContainer.svelte +100 -100
- package/frontend/lib/components/workspace/PanelHeader.svelte +505 -505
- package/frontend/lib/components/workspace/ViewMenu.svelte +162 -162
- package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -169
- package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -15
- package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -17
- package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -42
- package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +84 -84
- package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -37
- package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -274
- package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -1261
- package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -1560
- package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -150
- package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -73
- package/frontend/lib/constants/preview.ts +44 -44
- package/frontend/lib/services/chat/chat.service.ts +704 -704
- package/frontend/lib/services/chat/index.ts +6 -6
- package/frontend/lib/services/notification/global-stream-monitor.ts +86 -86
- package/frontend/lib/services/notification/index.ts +7 -7
- package/frontend/lib/services/notification/push.service.ts +143 -143
- package/frontend/lib/services/notification/sound.service.ts +126 -126
- package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -61
- package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -1499
- package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -67
- package/frontend/lib/services/preview/index.ts +22 -22
- package/frontend/lib/services/project/index.ts +7 -7
- package/frontend/lib/services/project/status.service.ts +159 -159
- package/frontend/lib/services/snapshot/snapshot.service.ts +47 -47
- package/frontend/lib/services/terminal/background/index.ts +129 -129
- package/frontend/lib/services/terminal/background/session-restore.ts +273 -273
- package/frontend/lib/services/terminal/background/stream-manager.ts +285 -285
- package/frontend/lib/services/terminal/index.ts +13 -13
- package/frontend/lib/services/terminal/persistence.service.ts +260 -260
- package/frontend/lib/services/terminal/project.service.ts +952 -952
- package/frontend/lib/services/terminal/session.service.ts +363 -363
- package/frontend/lib/services/terminal/terminal.service.ts +369 -369
- package/frontend/lib/stores/core/app.svelte.ts +117 -117
- package/frontend/lib/stores/core/files.svelte.ts +72 -72
- package/frontend/lib/stores/core/presence.svelte.ts +48 -48
- package/frontend/lib/stores/core/projects.svelte.ts +317 -317
- package/frontend/lib/stores/core/sessions.svelte.ts +383 -383
- package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -58
- package/frontend/lib/stores/features/models.svelte.ts +89 -89
- package/frontend/lib/stores/features/settings.svelte.ts +87 -87
- package/frontend/lib/stores/features/terminal.svelte.ts +700 -700
- package/frontend/lib/stores/features/tunnel.svelte.ts +163 -161
- package/frontend/lib/stores/features/user.svelte.ts +95 -95
- package/frontend/lib/stores/ui/chat-input.svelte.ts +56 -56
- package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -61
- package/frontend/lib/stores/ui/dialog.svelte.ts +58 -58
- package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -214
- package/frontend/lib/stores/ui/notification.svelte.ts +166 -166
- package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -88
- package/frontend/lib/stores/ui/theme.svelte.ts +179 -179
- package/frontend/lib/stores/ui/workspace.svelte.ts +754 -754
- package/frontend/lib/types/native-ui.ts +73 -73
- package/frontend/lib/utils/chat/date-separator.ts +38 -38
- package/frontend/lib/utils/chat/message-grouper.ts +218 -218
- package/frontend/lib/utils/chat/message-processor.ts +134 -134
- package/frontend/lib/utils/chat/tool-handler.ts +160 -160
- package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -142
- package/frontend/lib/utils/click-outside.ts +20 -20
- package/frontend/lib/utils/context-manager.ts +256 -256
- package/frontend/lib/utils/file-icon-mappings.ts +768 -768
- package/frontend/lib/utils/folder-icon-mappings.ts +1029 -1029
- package/frontend/lib/utils/git-status.ts +68 -68
- package/frontend/lib/utils/platform.ts +112 -112
- package/frontend/lib/utils/port-check.ts +64 -64
- package/frontend/lib/utils/terminalFormatter.ts +206 -206
- package/frontend/lib/utils/theme.ts +6 -6
- package/frontend/lib/utils/tree-visualizer.ts +320 -320
- package/frontend/lib/utils/ws.ts +44 -44
- package/frontend/main.ts +13 -13
- package/index.html +70 -70
- package/package.json +111 -111
- package/scripts/generate-icons.ts +86 -86
- package/scripts/pre-publish-check.sh +142 -142
- package/scripts/setup-hooks.sh +134 -134
- package/scripts/validate-branch-name.sh +47 -47
- package/scripts/validate-commit-msg.sh +42 -42
- package/shared/constants/engines.ts +134 -134
- package/shared/types/database/connection.ts +15 -15
- package/shared/types/database/index.ts +5 -5
- package/shared/types/database/schema.ts +140 -140
- package/shared/types/engine/index.ts +45 -45
- package/shared/types/filesystem/index.ts +21 -21
- package/shared/types/git.ts +171 -171
- package/shared/types/messaging/index.ts +238 -238
- package/shared/types/messaging/tool.ts +525 -525
- package/shared/types/network/api.ts +17 -17
- package/shared/types/network/index.ts +4 -4
- package/shared/types/stores/app.ts +22 -22
- package/shared/types/stores/dialog.ts +20 -20
- package/shared/types/stores/index.ts +2 -2
- package/shared/types/stores/settings.ts +15 -15
- package/shared/types/terminal/index.ts +43 -43
- package/shared/types/ui/components.ts +60 -60
- package/shared/types/ui/icons.ts +22 -22
- package/shared/types/ui/index.ts +21 -21
- package/shared/types/ui/notifications.ts +13 -13
- package/shared/types/ui/theme.ts +11 -11
- package/shared/types/websocket/index.ts +43 -43
- package/shared/types/window.d.ts +12 -12
- package/shared/utils/anonymous-user.ts +167 -167
- package/shared/utils/async.ts +10 -10
- package/shared/utils/diff-calculator.ts +184 -184
- package/shared/utils/file-type-detection.ts +165 -165
- package/shared/utils/logger.ts +144 -158
- package/shared/utils/message-formatter.ts +79 -79
- package/shared/utils/path.ts +47 -47
- package/shared/utils/ws-client.ts +768 -768
- package/shared/utils/ws-server.ts +660 -660
- package/static/favicon.svg +7 -7
- package/static/fonts/dm-sans.css +96 -96
- package/svelte.config.js +20 -20
- package/tsconfig.json +41 -41
- package/vite.config.ts +33 -33
- package/.github/workflows/test.yml +0 -40
|
@@ -1,307 +1,307 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Gitignore-aware file filtering
|
|
3
|
-
*
|
|
4
|
-
* Two strategies:
|
|
5
|
-
* 1. Git repo: Use `git ls-files -co --exclude-standard` (perfect accuracy)
|
|
6
|
-
* 2. Non-git repo: Parse .gitignore files manually (fallback)
|
|
7
|
-
*
|
|
8
|
-
* Handles nested .gitignore files in subdirectories with proper scoping.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import fs from 'fs/promises';
|
|
12
|
-
import path from 'path';
|
|
13
|
-
import { debug } from '$shared/utils/logger';
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Safety-net directories to always exclude regardless of .gitignore.
|
|
17
|
-
* These are never useful in snapshots and could be massive.
|
|
18
|
-
*/
|
|
19
|
-
const ALWAYS_EXCLUDE_DIRS = new Set([
|
|
20
|
-
'.git',
|
|
21
|
-
'node_modules',
|
|
22
|
-
]);
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Get list of snapshot-eligible files using git (preferred) or manual scan.
|
|
26
|
-
* Returns full absolute paths.
|
|
27
|
-
*/
|
|
28
|
-
export async function getSnapshotFiles(projectPath: string): Promise<string[]> {
|
|
29
|
-
// Try git-based scan first (handles all .gitignore rules perfectly)
|
|
30
|
-
const gitFiles = await scanWithGit(projectPath);
|
|
31
|
-
if (gitFiles !== null) {
|
|
32
|
-
return gitFiles;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Fallback: manual scan with .gitignore parsing
|
|
36
|
-
return scanWithGitignoreParsing(projectPath);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// ============================================================================
|
|
40
|
-
// Strategy 1: Git-based scanning
|
|
41
|
-
// ============================================================================
|
|
42
|
-
|
|
43
|
-
async function scanWithGit(dirPath: string): Promise<string[] | null> {
|
|
44
|
-
// Check if this is a git repo
|
|
45
|
-
try {
|
|
46
|
-
await fs.access(path.join(dirPath, '.git'));
|
|
47
|
-
} catch {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
try {
|
|
52
|
-
const proc = Bun.spawn(
|
|
53
|
-
['git', 'ls-files', '-co', '--exclude-standard'],
|
|
54
|
-
{ cwd: dirPath, stdout: 'pipe', stderr: 'pipe' }
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
const output = await new Response(proc.stdout).text();
|
|
58
|
-
const exitCode = await proc.exited;
|
|
59
|
-
|
|
60
|
-
if (exitCode !== 0) return null;
|
|
61
|
-
|
|
62
|
-
const files: string[] = [];
|
|
63
|
-
for (const line of output.split('\n')) {
|
|
64
|
-
const relativePath = line.trim();
|
|
65
|
-
if (!relativePath) continue;
|
|
66
|
-
|
|
67
|
-
// Skip always-excluded directories
|
|
68
|
-
const firstSegment = relativePath.split('/')[0];
|
|
69
|
-
if (ALWAYS_EXCLUDE_DIRS.has(firstSegment)) continue;
|
|
70
|
-
|
|
71
|
-
files.push(path.join(dirPath, relativePath));
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
debug.log('snapshot', `Git scan found ${files.length} files`);
|
|
75
|
-
return files;
|
|
76
|
-
} catch (err) {
|
|
77
|
-
debug.warn('snapshot', 'git ls-files failed, falling back to manual scan:', err);
|
|
78
|
-
return null;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ============================================================================
|
|
83
|
-
// Strategy 2: Manual scan with .gitignore parsing
|
|
84
|
-
// ============================================================================
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Rule from a .gitignore file.
|
|
88
|
-
* scope = relative directory containing the .gitignore ('' for root).
|
|
89
|
-
*/
|
|
90
|
-
interface IgnoreRule {
|
|
91
|
-
pattern: RegExp;
|
|
92
|
-
negate: boolean;
|
|
93
|
-
dirOnly: boolean; // pattern ends with /
|
|
94
|
-
scope: string; // relative dir of the .gitignore that defined this rule
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Parse a single .gitignore line into an IgnoreRule (or null if comment/blank).
|
|
99
|
-
*/
|
|
100
|
-
function parseGitignoreLine(line: string, scope: string): IgnoreRule | null {
|
|
101
|
-
// Strip trailing whitespace (unless escaped)
|
|
102
|
-
let trimmed = line.replace(/(?<!\\)\s+$/, '');
|
|
103
|
-
|
|
104
|
-
// Skip empty lines and comments
|
|
105
|
-
if (!trimmed || trimmed.startsWith('#')) return null;
|
|
106
|
-
|
|
107
|
-
// Check for negation
|
|
108
|
-
let negate = false;
|
|
109
|
-
if (trimmed.startsWith('!')) {
|
|
110
|
-
negate = true;
|
|
111
|
-
trimmed = trimmed.slice(1);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Check for directory-only marker
|
|
115
|
-
let dirOnly = false;
|
|
116
|
-
if (trimmed.endsWith('/')) {
|
|
117
|
-
dirOnly = true;
|
|
118
|
-
trimmed = trimmed.slice(0, -1);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Determine if pattern is anchored (contains / other than at end)
|
|
122
|
-
const anchored = trimmed.includes('/');
|
|
123
|
-
|
|
124
|
-
// Build regex from glob pattern
|
|
125
|
-
const regexStr = globToRegex(trimmed, anchored, scope);
|
|
126
|
-
try {
|
|
127
|
-
const pattern = new RegExp(regexStr);
|
|
128
|
-
return { pattern, negate, dirOnly, scope };
|
|
129
|
-
} catch {
|
|
130
|
-
return null;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Convert a gitignore glob pattern to a regex string.
|
|
136
|
-
*
|
|
137
|
-
* Rules:
|
|
138
|
-
* - `*` matches anything except /
|
|
139
|
-
* - `**` matches everything (including /)
|
|
140
|
-
* - `?` matches any single char except /
|
|
141
|
-
* - If pattern contains / (anchored), match from scope root
|
|
142
|
-
* - If pattern has no / (unanchored), match basename anywhere
|
|
143
|
-
*/
|
|
144
|
-
function globToRegex(pattern: string, anchored: boolean, scope: string): string {
|
|
145
|
-
let result = '';
|
|
146
|
-
|
|
147
|
-
// Process pattern character by character
|
|
148
|
-
let i = 0;
|
|
149
|
-
while (i < pattern.length) {
|
|
150
|
-
const ch = pattern[i];
|
|
151
|
-
|
|
152
|
-
if (ch === '*') {
|
|
153
|
-
if (pattern[i + 1] === '*') {
|
|
154
|
-
// ** pattern
|
|
155
|
-
if (pattern[i + 2] === '/') {
|
|
156
|
-
// **/ = match zero or more directories
|
|
157
|
-
result += '(?:.*/)?';
|
|
158
|
-
i += 3;
|
|
159
|
-
} else if (i + 2 === pattern.length) {
|
|
160
|
-
// ** at end = match everything
|
|
161
|
-
result += '.*';
|
|
162
|
-
i += 2;
|
|
163
|
-
} else {
|
|
164
|
-
// ** followed by something else
|
|
165
|
-
result += '.*';
|
|
166
|
-
i += 2;
|
|
167
|
-
}
|
|
168
|
-
} else {
|
|
169
|
-
// single * = match anything except /
|
|
170
|
-
result += '[^/]*';
|
|
171
|
-
i++;
|
|
172
|
-
}
|
|
173
|
-
} else if (ch === '?') {
|
|
174
|
-
result += '[^/]';
|
|
175
|
-
i++;
|
|
176
|
-
} else if (ch === '[') {
|
|
177
|
-
// Character class - pass through
|
|
178
|
-
const end = pattern.indexOf(']', i + 1);
|
|
179
|
-
if (end !== -1) {
|
|
180
|
-
result += pattern.slice(i, end + 1);
|
|
181
|
-
i = end + 1;
|
|
182
|
-
} else {
|
|
183
|
-
result += '\\[';
|
|
184
|
-
i++;
|
|
185
|
-
}
|
|
186
|
-
} else if ('.+^${}()|\\'.includes(ch)) {
|
|
187
|
-
// Escape regex special chars
|
|
188
|
-
result += '\\' + ch;
|
|
189
|
-
i++;
|
|
190
|
-
} else if (ch === '/') {
|
|
191
|
-
result += '/';
|
|
192
|
-
i++;
|
|
193
|
-
} else {
|
|
194
|
-
result += ch;
|
|
195
|
-
i++;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Build final regex based on anchoring
|
|
200
|
-
if (anchored) {
|
|
201
|
-
// Pattern is relative to the .gitignore scope
|
|
202
|
-
const prefix = scope ? scope + '/' : '';
|
|
203
|
-
return '^' + escapeRegex(prefix) + result + '(?:/.*)?$';
|
|
204
|
-
} else {
|
|
205
|
-
// Unanchored: match basename anywhere, or as a path segment
|
|
206
|
-
return '(?:^|/)' + result + '(?:/.*)?$';
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
function escapeRegex(str: string): string {
|
|
211
|
-
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Gitignore rule collector.
|
|
216
|
-
* Accumulates rules from multiple .gitignore files during directory traversal.
|
|
217
|
-
*/
|
|
218
|
-
class GitignoreFilter {
|
|
219
|
-
private rules: IgnoreRule[] = [];
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Load and parse a .gitignore file
|
|
223
|
-
*/
|
|
224
|
-
async loadFromFile(filepath: string, scope: string): Promise<void> {
|
|
225
|
-
try {
|
|
226
|
-
const content = await fs.readFile(filepath, 'utf-8');
|
|
227
|
-
for (const line of content.split('\n')) {
|
|
228
|
-
const rule = parseGitignoreLine(line, scope);
|
|
229
|
-
if (rule) this.rules.push(rule);
|
|
230
|
-
}
|
|
231
|
-
} catch {
|
|
232
|
-
// File doesn't exist or can't be read - no rules to add
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Check if a path should be ignored.
|
|
238
|
-
* @param relativePath - Path relative to project root (forward slashes)
|
|
239
|
-
* @param isDirectory - Whether the path is a directory
|
|
240
|
-
*/
|
|
241
|
-
isIgnored(relativePath: string, isDirectory: boolean): boolean {
|
|
242
|
-
let ignored = false;
|
|
243
|
-
|
|
244
|
-
for (const rule of this.rules) {
|
|
245
|
-
// Skip dir-only rules for files
|
|
246
|
-
if (rule.dirOnly && !isDirectory) continue;
|
|
247
|
-
|
|
248
|
-
// Check scope: rule only applies within its .gitignore directory
|
|
249
|
-
if (rule.scope && !relativePath.startsWith(rule.scope + '/') && relativePath !== rule.scope) {
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (rule.pattern.test(relativePath)) {
|
|
254
|
-
ignored = !rule.negate;
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return ignored;
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Scan directory manually, parsing .gitignore files at each level.
|
|
264
|
-
*/
|
|
265
|
-
async function scanWithGitignoreParsing(projectPath: string): Promise<string[]> {
|
|
266
|
-
const files: string[] = [];
|
|
267
|
-
const filter = new GitignoreFilter();
|
|
268
|
-
|
|
269
|
-
// Load root .gitignore
|
|
270
|
-
await filter.loadFromFile(path.join(projectPath, '.gitignore'), '');
|
|
271
|
-
|
|
272
|
-
const scan = async (currentPath: string): Promise<void> => {
|
|
273
|
-
try {
|
|
274
|
-
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
275
|
-
const relativeDir = path.relative(projectPath, currentPath).replace(/\\/g, '/');
|
|
276
|
-
|
|
277
|
-
// Load .gitignore in this directory (if not root - root already loaded)
|
|
278
|
-
if (relativeDir) {
|
|
279
|
-
await filter.loadFromFile(path.join(currentPath, '.gitignore'), relativeDir);
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
for (const entry of entries) {
|
|
283
|
-
const fullPath = path.join(currentPath, entry.name);
|
|
284
|
-
const relativePath = path.relative(projectPath, fullPath).replace(/\\/g, '/');
|
|
285
|
-
|
|
286
|
-
// Always exclude certain directories
|
|
287
|
-
if (ALWAYS_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
288
|
-
|
|
289
|
-
if (entry.isDirectory()) {
|
|
290
|
-
if (!filter.isIgnored(relativePath, true)) {
|
|
291
|
-
await scan(fullPath);
|
|
292
|
-
}
|
|
293
|
-
} else if (entry.isFile()) {
|
|
294
|
-
if (!filter.isIgnored(relativePath, false)) {
|
|
295
|
-
files.push(fullPath);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
} catch (err) {
|
|
300
|
-
debug.warn('snapshot', `Could not read directory ${currentPath}:`, err);
|
|
301
|
-
}
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
await scan(projectPath);
|
|
305
|
-
debug.log('snapshot', `Manual scan found ${files.length} files`);
|
|
306
|
-
return files;
|
|
307
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Gitignore-aware file filtering
|
|
3
|
+
*
|
|
4
|
+
* Two strategies:
|
|
5
|
+
* 1. Git repo: Use `git ls-files -co --exclude-standard` (perfect accuracy)
|
|
6
|
+
* 2. Non-git repo: Parse .gitignore files manually (fallback)
|
|
7
|
+
*
|
|
8
|
+
* Handles nested .gitignore files in subdirectories with proper scoping.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import fs from 'fs/promises';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { debug } from '$shared/utils/logger';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Safety-net directories to always exclude regardless of .gitignore.
|
|
17
|
+
* These are never useful in snapshots and could be massive.
|
|
18
|
+
*/
|
|
19
|
+
const ALWAYS_EXCLUDE_DIRS = new Set([
|
|
20
|
+
'.git',
|
|
21
|
+
'node_modules',
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Get list of snapshot-eligible files using git (preferred) or manual scan.
|
|
26
|
+
* Returns full absolute paths.
|
|
27
|
+
*/
|
|
28
|
+
export async function getSnapshotFiles(projectPath: string): Promise<string[]> {
|
|
29
|
+
// Try git-based scan first (handles all .gitignore rules perfectly)
|
|
30
|
+
const gitFiles = await scanWithGit(projectPath);
|
|
31
|
+
if (gitFiles !== null) {
|
|
32
|
+
return gitFiles;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Fallback: manual scan with .gitignore parsing
|
|
36
|
+
return scanWithGitignoreParsing(projectPath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ============================================================================
|
|
40
|
+
// Strategy 1: Git-based scanning
|
|
41
|
+
// ============================================================================
|
|
42
|
+
|
|
43
|
+
async function scanWithGit(dirPath: string): Promise<string[] | null> {
|
|
44
|
+
// Check if this is a git repo
|
|
45
|
+
try {
|
|
46
|
+
await fs.access(path.join(dirPath, '.git'));
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const proc = Bun.spawn(
|
|
53
|
+
['git', 'ls-files', '-co', '--exclude-standard'],
|
|
54
|
+
{ cwd: dirPath, stdout: 'pipe', stderr: 'pipe' }
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const output = await new Response(proc.stdout).text();
|
|
58
|
+
const exitCode = await proc.exited;
|
|
59
|
+
|
|
60
|
+
if (exitCode !== 0) return null;
|
|
61
|
+
|
|
62
|
+
const files: string[] = [];
|
|
63
|
+
for (const line of output.split('\n')) {
|
|
64
|
+
const relativePath = line.trim();
|
|
65
|
+
if (!relativePath) continue;
|
|
66
|
+
|
|
67
|
+
// Skip always-excluded directories
|
|
68
|
+
const firstSegment = relativePath.split('/')[0];
|
|
69
|
+
if (ALWAYS_EXCLUDE_DIRS.has(firstSegment)) continue;
|
|
70
|
+
|
|
71
|
+
files.push(path.join(dirPath, relativePath));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
debug.log('snapshot', `Git scan found ${files.length} files`);
|
|
75
|
+
return files;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
debug.warn('snapshot', 'git ls-files failed, falling back to manual scan:', err);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================================================
|
|
83
|
+
// Strategy 2: Manual scan with .gitignore parsing
|
|
84
|
+
// ============================================================================
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Rule from a .gitignore file.
|
|
88
|
+
* scope = relative directory containing the .gitignore ('' for root).
|
|
89
|
+
*/
|
|
90
|
+
interface IgnoreRule {
|
|
91
|
+
pattern: RegExp;
|
|
92
|
+
negate: boolean;
|
|
93
|
+
dirOnly: boolean; // pattern ends with /
|
|
94
|
+
scope: string; // relative dir of the .gitignore that defined this rule
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Parse a single .gitignore line into an IgnoreRule (or null if comment/blank).
|
|
99
|
+
*/
|
|
100
|
+
function parseGitignoreLine(line: string, scope: string): IgnoreRule | null {
|
|
101
|
+
// Strip trailing whitespace (unless escaped)
|
|
102
|
+
let trimmed = line.replace(/(?<!\\)\s+$/, '');
|
|
103
|
+
|
|
104
|
+
// Skip empty lines and comments
|
|
105
|
+
if (!trimmed || trimmed.startsWith('#')) return null;
|
|
106
|
+
|
|
107
|
+
// Check for negation
|
|
108
|
+
let negate = false;
|
|
109
|
+
if (trimmed.startsWith('!')) {
|
|
110
|
+
negate = true;
|
|
111
|
+
trimmed = trimmed.slice(1);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Check for directory-only marker
|
|
115
|
+
let dirOnly = false;
|
|
116
|
+
if (trimmed.endsWith('/')) {
|
|
117
|
+
dirOnly = true;
|
|
118
|
+
trimmed = trimmed.slice(0, -1);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Determine if pattern is anchored (contains / other than at end)
|
|
122
|
+
const anchored = trimmed.includes('/');
|
|
123
|
+
|
|
124
|
+
// Build regex from glob pattern
|
|
125
|
+
const regexStr = globToRegex(trimmed, anchored, scope);
|
|
126
|
+
try {
|
|
127
|
+
const pattern = new RegExp(regexStr);
|
|
128
|
+
return { pattern, negate, dirOnly, scope };
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Convert a gitignore glob pattern to a regex string.
|
|
136
|
+
*
|
|
137
|
+
* Rules:
|
|
138
|
+
* - `*` matches anything except /
|
|
139
|
+
* - `**` matches everything (including /)
|
|
140
|
+
* - `?` matches any single char except /
|
|
141
|
+
* - If pattern contains / (anchored), match from scope root
|
|
142
|
+
* - If pattern has no / (unanchored), match basename anywhere
|
|
143
|
+
*/
|
|
144
|
+
function globToRegex(pattern: string, anchored: boolean, scope: string): string {
|
|
145
|
+
let result = '';
|
|
146
|
+
|
|
147
|
+
// Process pattern character by character
|
|
148
|
+
let i = 0;
|
|
149
|
+
while (i < pattern.length) {
|
|
150
|
+
const ch = pattern[i];
|
|
151
|
+
|
|
152
|
+
if (ch === '*') {
|
|
153
|
+
if (pattern[i + 1] === '*') {
|
|
154
|
+
// ** pattern
|
|
155
|
+
if (pattern[i + 2] === '/') {
|
|
156
|
+
// **/ = match zero or more directories
|
|
157
|
+
result += '(?:.*/)?';
|
|
158
|
+
i += 3;
|
|
159
|
+
} else if (i + 2 === pattern.length) {
|
|
160
|
+
// ** at end = match everything
|
|
161
|
+
result += '.*';
|
|
162
|
+
i += 2;
|
|
163
|
+
} else {
|
|
164
|
+
// ** followed by something else
|
|
165
|
+
result += '.*';
|
|
166
|
+
i += 2;
|
|
167
|
+
}
|
|
168
|
+
} else {
|
|
169
|
+
// single * = match anything except /
|
|
170
|
+
result += '[^/]*';
|
|
171
|
+
i++;
|
|
172
|
+
}
|
|
173
|
+
} else if (ch === '?') {
|
|
174
|
+
result += '[^/]';
|
|
175
|
+
i++;
|
|
176
|
+
} else if (ch === '[') {
|
|
177
|
+
// Character class - pass through
|
|
178
|
+
const end = pattern.indexOf(']', i + 1);
|
|
179
|
+
if (end !== -1) {
|
|
180
|
+
result += pattern.slice(i, end + 1);
|
|
181
|
+
i = end + 1;
|
|
182
|
+
} else {
|
|
183
|
+
result += '\\[';
|
|
184
|
+
i++;
|
|
185
|
+
}
|
|
186
|
+
} else if ('.+^${}()|\\'.includes(ch)) {
|
|
187
|
+
// Escape regex special chars
|
|
188
|
+
result += '\\' + ch;
|
|
189
|
+
i++;
|
|
190
|
+
} else if (ch === '/') {
|
|
191
|
+
result += '/';
|
|
192
|
+
i++;
|
|
193
|
+
} else {
|
|
194
|
+
result += ch;
|
|
195
|
+
i++;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Build final regex based on anchoring
|
|
200
|
+
if (anchored) {
|
|
201
|
+
// Pattern is relative to the .gitignore scope
|
|
202
|
+
const prefix = scope ? scope + '/' : '';
|
|
203
|
+
return '^' + escapeRegex(prefix) + result + '(?:/.*)?$';
|
|
204
|
+
} else {
|
|
205
|
+
// Unanchored: match basename anywhere, or as a path segment
|
|
206
|
+
return '(?:^|/)' + result + '(?:/.*)?$';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function escapeRegex(str: string): string {
|
|
211
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Gitignore rule collector.
|
|
216
|
+
* Accumulates rules from multiple .gitignore files during directory traversal.
|
|
217
|
+
*/
|
|
218
|
+
class GitignoreFilter {
|
|
219
|
+
private rules: IgnoreRule[] = [];
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Load and parse a .gitignore file
|
|
223
|
+
*/
|
|
224
|
+
async loadFromFile(filepath: string, scope: string): Promise<void> {
|
|
225
|
+
try {
|
|
226
|
+
const content = await fs.readFile(filepath, 'utf-8');
|
|
227
|
+
for (const line of content.split('\n')) {
|
|
228
|
+
const rule = parseGitignoreLine(line, scope);
|
|
229
|
+
if (rule) this.rules.push(rule);
|
|
230
|
+
}
|
|
231
|
+
} catch {
|
|
232
|
+
// File doesn't exist or can't be read - no rules to add
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Check if a path should be ignored.
|
|
238
|
+
* @param relativePath - Path relative to project root (forward slashes)
|
|
239
|
+
* @param isDirectory - Whether the path is a directory
|
|
240
|
+
*/
|
|
241
|
+
isIgnored(relativePath: string, isDirectory: boolean): boolean {
|
|
242
|
+
let ignored = false;
|
|
243
|
+
|
|
244
|
+
for (const rule of this.rules) {
|
|
245
|
+
// Skip dir-only rules for files
|
|
246
|
+
if (rule.dirOnly && !isDirectory) continue;
|
|
247
|
+
|
|
248
|
+
// Check scope: rule only applies within its .gitignore directory
|
|
249
|
+
if (rule.scope && !relativePath.startsWith(rule.scope + '/') && relativePath !== rule.scope) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (rule.pattern.test(relativePath)) {
|
|
254
|
+
ignored = !rule.negate;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return ignored;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Scan directory manually, parsing .gitignore files at each level.
|
|
264
|
+
*/
|
|
265
|
+
async function scanWithGitignoreParsing(projectPath: string): Promise<string[]> {
|
|
266
|
+
const files: string[] = [];
|
|
267
|
+
const filter = new GitignoreFilter();
|
|
268
|
+
|
|
269
|
+
// Load root .gitignore
|
|
270
|
+
await filter.loadFromFile(path.join(projectPath, '.gitignore'), '');
|
|
271
|
+
|
|
272
|
+
const scan = async (currentPath: string): Promise<void> => {
|
|
273
|
+
try {
|
|
274
|
+
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
|
275
|
+
const relativeDir = path.relative(projectPath, currentPath).replace(/\\/g, '/');
|
|
276
|
+
|
|
277
|
+
// Load .gitignore in this directory (if not root - root already loaded)
|
|
278
|
+
if (relativeDir) {
|
|
279
|
+
await filter.loadFromFile(path.join(currentPath, '.gitignore'), relativeDir);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
for (const entry of entries) {
|
|
283
|
+
const fullPath = path.join(currentPath, entry.name);
|
|
284
|
+
const relativePath = path.relative(projectPath, fullPath).replace(/\\/g, '/');
|
|
285
|
+
|
|
286
|
+
// Always exclude certain directories
|
|
287
|
+
if (ALWAYS_EXCLUDE_DIRS.has(entry.name)) continue;
|
|
288
|
+
|
|
289
|
+
if (entry.isDirectory()) {
|
|
290
|
+
if (!filter.isIgnored(relativePath, true)) {
|
|
291
|
+
await scan(fullPath);
|
|
292
|
+
}
|
|
293
|
+
} else if (entry.isFile()) {
|
|
294
|
+
if (!filter.isIgnored(relativePath, false)) {
|
|
295
|
+
files.push(fullPath);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} catch (err) {
|
|
300
|
+
debug.warn('snapshot', `Could not read directory ${currentPath}:`, err);
|
|
301
|
+
}
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
await scan(projectPath);
|
|
305
|
+
debug.log('snapshot', `Manual scan found ${files.length} files`);
|
|
306
|
+
return files;
|
|
307
|
+
}
|