@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,227 +1,227 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Content-Addressable Blob Store
|
|
3
|
-
* Git-like storage for snapshot file contents
|
|
4
|
-
*
|
|
5
|
-
* Structure:
|
|
6
|
-
* ~/.clopen/snapshots/blobs/{hash[0:2]}/{hash}.gz - compressed file blobs
|
|
7
|
-
* ~/.clopen/snapshots/trees/{snapshotId}.json - tree maps (filepath -> hash)
|
|
8
|
-
*
|
|
9
|
-
* Deduplication: Same file content across any snapshot is stored only once.
|
|
10
|
-
* Compression: All blobs are gzip compressed to minimize disk usage.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { join } from 'path';
|
|
14
|
-
import { homedir } from 'os';
|
|
15
|
-
import fs from 'fs/promises';
|
|
16
|
-
import { gzipSync, gunzipSync } from 'zlib';
|
|
17
|
-
import { debug } from '$shared/utils/logger';
|
|
18
|
-
|
|
19
|
-
const SNAPSHOTS_DIR = join(homedir(), '.clopen', 'snapshots');
|
|
20
|
-
const BLOBS_DIR = join(SNAPSHOTS_DIR, 'blobs');
|
|
21
|
-
const TREES_DIR = join(SNAPSHOTS_DIR, 'trees');
|
|
22
|
-
|
|
23
|
-
export interface TreeMap {
|
|
24
|
-
[filepath: string]: string; // filepath -> blob hash
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
interface FileHashCacheEntry {
|
|
28
|
-
mtimeMs: number;
|
|
29
|
-
size: number;
|
|
30
|
-
hash: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
class BlobStore {
|
|
34
|
-
private initialized = false;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Cache: filepath -> { mtimeMs, size, hash }
|
|
38
|
-
* Avoids re-reading files that haven't changed (based on mtime + size).
|
|
39
|
-
*/
|
|
40
|
-
private fileHashCache = new Map<string, FileHashCacheEntry>();
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Ensure storage directories exist
|
|
44
|
-
*/
|
|
45
|
-
async init(): Promise<void> {
|
|
46
|
-
if (this.initialized) return;
|
|
47
|
-
await fs.mkdir(BLOBS_DIR, { recursive: true });
|
|
48
|
-
await fs.mkdir(TREES_DIR, { recursive: true });
|
|
49
|
-
this.initialized = true;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Compute SHA-256 hash of content (Buffer for binary safety)
|
|
54
|
-
*/
|
|
55
|
-
hashContent(content: Buffer): string {
|
|
56
|
-
const hasher = new Bun.CryptoHasher('sha256');
|
|
57
|
-
hasher.update(content);
|
|
58
|
-
return hasher.digest('hex');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get blob file path from hash (using 2-char prefix subdirectory like git)
|
|
63
|
-
*/
|
|
64
|
-
private getBlobPath(hash: string): string {
|
|
65
|
-
const prefix = hash.substring(0, 2);
|
|
66
|
-
return join(BLOBS_DIR, prefix, hash + '.gz');
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Check if a blob exists
|
|
71
|
-
*/
|
|
72
|
-
async hasBlob(hash: string): Promise<boolean> {
|
|
73
|
-
try {
|
|
74
|
-
await fs.access(this.getBlobPath(hash));
|
|
75
|
-
return true;
|
|
76
|
-
} catch {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Store content as a blob. Returns the hash.
|
|
83
|
-
* If blob already exists (same hash), it's a no-op (deduplication).
|
|
84
|
-
* Accepts Buffer to safely handle both text and binary files.
|
|
85
|
-
*/
|
|
86
|
-
async storeBlob(content: Buffer): Promise<string> {
|
|
87
|
-
await this.init();
|
|
88
|
-
const hash = this.hashContent(content);
|
|
89
|
-
|
|
90
|
-
// Check if already exists (deduplication)
|
|
91
|
-
if (await this.hasBlob(hash)) {
|
|
92
|
-
return hash;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Create prefix directory
|
|
96
|
-
const prefixDir = join(BLOBS_DIR, hash.substring(0, 2));
|
|
97
|
-
await fs.mkdir(prefixDir, { recursive: true });
|
|
98
|
-
|
|
99
|
-
// Compress and write (Buffer directly, no encoding conversion)
|
|
100
|
-
const compressed = gzipSync(content);
|
|
101
|
-
await fs.writeFile(this.getBlobPath(hash), compressed);
|
|
102
|
-
|
|
103
|
-
return hash;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Read blob content by hash. Returns Buffer to safely handle binary files.
|
|
108
|
-
*/
|
|
109
|
-
async readBlob(hash: string): Promise<Buffer> {
|
|
110
|
-
const blobPath = this.getBlobPath(hash);
|
|
111
|
-
const compressed = await fs.readFile(blobPath);
|
|
112
|
-
return gunzipSync(compressed);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Store a tree (snapshot state) as a JSON file.
|
|
117
|
-
* Returns the tree hash for reference.
|
|
118
|
-
*/
|
|
119
|
-
async storeTree(snapshotId: string, tree: TreeMap): Promise<string> {
|
|
120
|
-
await this.init();
|
|
121
|
-
const treePath = join(TREES_DIR, `${snapshotId}.json`);
|
|
122
|
-
const content = JSON.stringify(tree);
|
|
123
|
-
const treeHash = this.hashContent(Buffer.from(content, 'utf-8'));
|
|
124
|
-
await fs.writeFile(treePath, content, 'utf-8');
|
|
125
|
-
return treeHash;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Read a tree by snapshot ID
|
|
130
|
-
*/
|
|
131
|
-
async readTree(snapshotId: string): Promise<TreeMap> {
|
|
132
|
-
const treePath = join(TREES_DIR, `${snapshotId}.json`);
|
|
133
|
-
const content = await fs.readFile(treePath, 'utf-8');
|
|
134
|
-
return JSON.parse(content) as TreeMap;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* Check if a tree exists
|
|
139
|
-
*/
|
|
140
|
-
async hasTree(snapshotId: string): Promise<boolean> {
|
|
141
|
-
try {
|
|
142
|
-
await fs.access(join(TREES_DIR, `${snapshotId}.json`));
|
|
143
|
-
return true;
|
|
144
|
-
} catch {
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Resolve a tree to full file contents (as Buffers).
|
|
151
|
-
* Reads all blobs in parallel for performance.
|
|
152
|
-
* Returns { filepath: Buffer } map for binary-safe handling.
|
|
153
|
-
*/
|
|
154
|
-
async resolveTree(tree: TreeMap): Promise<Record<string, Buffer>> {
|
|
155
|
-
const result: Record<string, Buffer> = {};
|
|
156
|
-
|
|
157
|
-
const entries = Object.entries(tree);
|
|
158
|
-
const blobPromises = entries.map(async ([filepath, hash]) => {
|
|
159
|
-
try {
|
|
160
|
-
const content = await this.readBlob(hash);
|
|
161
|
-
return { filepath, content };
|
|
162
|
-
} catch (err) {
|
|
163
|
-
debug.warn('snapshot', `Could not read blob ${hash} for ${filepath}:`, err);
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
const results = await Promise.all(blobPromises);
|
|
169
|
-
for (const r of results) {
|
|
170
|
-
if (r) {
|
|
171
|
-
result[r.filepath] = r.content;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return result;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Hash a file using mtime cache. Returns { hash, content? }.
|
|
180
|
-
* If the file hasn't changed (same mtime+size), returns cached hash without reading content.
|
|
181
|
-
* If the file has changed, reads content, hashes it, stores blob, and caches.
|
|
182
|
-
* Reads as Buffer to safely handle binary files (images, PDFs, etc.).
|
|
183
|
-
*
|
|
184
|
-
* @returns hash and content Buffer (content is null if cache hit and blob already exists)
|
|
185
|
-
*/
|
|
186
|
-
async hashFile(filepath: string, fullPath: string): Promise<{ hash: string; content: Buffer | null; cached: boolean }> {
|
|
187
|
-
await this.init();
|
|
188
|
-
|
|
189
|
-
const stat = await fs.stat(fullPath);
|
|
190
|
-
|
|
191
|
-
// Check mtime cache
|
|
192
|
-
const cached = this.fileHashCache.get(filepath);
|
|
193
|
-
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
|
|
194
|
-
return { hash: cached.hash, content: null, cached: true };
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// File changed - read as Buffer (binary-safe, no encoding conversion)
|
|
198
|
-
const content = await fs.readFile(fullPath);
|
|
199
|
-
const hash = this.hashContent(content);
|
|
200
|
-
|
|
201
|
-
// Store blob (deduplication handled internally)
|
|
202
|
-
await this.storeBlob(content);
|
|
203
|
-
|
|
204
|
-
// Update cache
|
|
205
|
-
this.fileHashCache.set(filepath, {
|
|
206
|
-
mtimeMs: stat.mtimeMs,
|
|
207
|
-
size: stat.size,
|
|
208
|
-
hash
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
return { hash, content, cached: false };
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
/**
|
|
215
|
-
* Delete a tree file (cleanup)
|
|
216
|
-
*/
|
|
217
|
-
async deleteTree(snapshotId: string): Promise<void> {
|
|
218
|
-
try {
|
|
219
|
-
await fs.unlink(join(TREES_DIR, `${snapshotId}.json`));
|
|
220
|
-
} catch {
|
|
221
|
-
// Ignore - might not exist
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Export singleton
|
|
227
|
-
export const blobStore = new BlobStore();
|
|
1
|
+
/**
|
|
2
|
+
* Content-Addressable Blob Store
|
|
3
|
+
* Git-like storage for snapshot file contents
|
|
4
|
+
*
|
|
5
|
+
* Structure:
|
|
6
|
+
* ~/.clopen/snapshots/blobs/{hash[0:2]}/{hash}.gz - compressed file blobs
|
|
7
|
+
* ~/.clopen/snapshots/trees/{snapshotId}.json - tree maps (filepath -> hash)
|
|
8
|
+
*
|
|
9
|
+
* Deduplication: Same file content across any snapshot is stored only once.
|
|
10
|
+
* Compression: All blobs are gzip compressed to minimize disk usage.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
import { homedir } from 'os';
|
|
15
|
+
import fs from 'fs/promises';
|
|
16
|
+
import { gzipSync, gunzipSync } from 'zlib';
|
|
17
|
+
import { debug } from '$shared/utils/logger';
|
|
18
|
+
|
|
19
|
+
const SNAPSHOTS_DIR = join(homedir(), '.clopen', 'snapshots');
|
|
20
|
+
const BLOBS_DIR = join(SNAPSHOTS_DIR, 'blobs');
|
|
21
|
+
const TREES_DIR = join(SNAPSHOTS_DIR, 'trees');
|
|
22
|
+
|
|
23
|
+
export interface TreeMap {
|
|
24
|
+
[filepath: string]: string; // filepath -> blob hash
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface FileHashCacheEntry {
|
|
28
|
+
mtimeMs: number;
|
|
29
|
+
size: number;
|
|
30
|
+
hash: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
class BlobStore {
|
|
34
|
+
private initialized = false;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Cache: filepath -> { mtimeMs, size, hash }
|
|
38
|
+
* Avoids re-reading files that haven't changed (based on mtime + size).
|
|
39
|
+
*/
|
|
40
|
+
private fileHashCache = new Map<string, FileHashCacheEntry>();
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Ensure storage directories exist
|
|
44
|
+
*/
|
|
45
|
+
async init(): Promise<void> {
|
|
46
|
+
if (this.initialized) return;
|
|
47
|
+
await fs.mkdir(BLOBS_DIR, { recursive: true });
|
|
48
|
+
await fs.mkdir(TREES_DIR, { recursive: true });
|
|
49
|
+
this.initialized = true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Compute SHA-256 hash of content (Buffer for binary safety)
|
|
54
|
+
*/
|
|
55
|
+
hashContent(content: Buffer): string {
|
|
56
|
+
const hasher = new Bun.CryptoHasher('sha256');
|
|
57
|
+
hasher.update(content);
|
|
58
|
+
return hasher.digest('hex');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get blob file path from hash (using 2-char prefix subdirectory like git)
|
|
63
|
+
*/
|
|
64
|
+
private getBlobPath(hash: string): string {
|
|
65
|
+
const prefix = hash.substring(0, 2);
|
|
66
|
+
return join(BLOBS_DIR, prefix, hash + '.gz');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Check if a blob exists
|
|
71
|
+
*/
|
|
72
|
+
async hasBlob(hash: string): Promise<boolean> {
|
|
73
|
+
try {
|
|
74
|
+
await fs.access(this.getBlobPath(hash));
|
|
75
|
+
return true;
|
|
76
|
+
} catch {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Store content as a blob. Returns the hash.
|
|
83
|
+
* If blob already exists (same hash), it's a no-op (deduplication).
|
|
84
|
+
* Accepts Buffer to safely handle both text and binary files.
|
|
85
|
+
*/
|
|
86
|
+
async storeBlob(content: Buffer): Promise<string> {
|
|
87
|
+
await this.init();
|
|
88
|
+
const hash = this.hashContent(content);
|
|
89
|
+
|
|
90
|
+
// Check if already exists (deduplication)
|
|
91
|
+
if (await this.hasBlob(hash)) {
|
|
92
|
+
return hash;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Create prefix directory
|
|
96
|
+
const prefixDir = join(BLOBS_DIR, hash.substring(0, 2));
|
|
97
|
+
await fs.mkdir(prefixDir, { recursive: true });
|
|
98
|
+
|
|
99
|
+
// Compress and write (Buffer directly, no encoding conversion)
|
|
100
|
+
const compressed = gzipSync(content);
|
|
101
|
+
await fs.writeFile(this.getBlobPath(hash), compressed);
|
|
102
|
+
|
|
103
|
+
return hash;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Read blob content by hash. Returns Buffer to safely handle binary files.
|
|
108
|
+
*/
|
|
109
|
+
async readBlob(hash: string): Promise<Buffer> {
|
|
110
|
+
const blobPath = this.getBlobPath(hash);
|
|
111
|
+
const compressed = await fs.readFile(blobPath);
|
|
112
|
+
return gunzipSync(compressed);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Store a tree (snapshot state) as a JSON file.
|
|
117
|
+
* Returns the tree hash for reference.
|
|
118
|
+
*/
|
|
119
|
+
async storeTree(snapshotId: string, tree: TreeMap): Promise<string> {
|
|
120
|
+
await this.init();
|
|
121
|
+
const treePath = join(TREES_DIR, `${snapshotId}.json`);
|
|
122
|
+
const content = JSON.stringify(tree);
|
|
123
|
+
const treeHash = this.hashContent(Buffer.from(content, 'utf-8'));
|
|
124
|
+
await fs.writeFile(treePath, content, 'utf-8');
|
|
125
|
+
return treeHash;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Read a tree by snapshot ID
|
|
130
|
+
*/
|
|
131
|
+
async readTree(snapshotId: string): Promise<TreeMap> {
|
|
132
|
+
const treePath = join(TREES_DIR, `${snapshotId}.json`);
|
|
133
|
+
const content = await fs.readFile(treePath, 'utf-8');
|
|
134
|
+
return JSON.parse(content) as TreeMap;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Check if a tree exists
|
|
139
|
+
*/
|
|
140
|
+
async hasTree(snapshotId: string): Promise<boolean> {
|
|
141
|
+
try {
|
|
142
|
+
await fs.access(join(TREES_DIR, `${snapshotId}.json`));
|
|
143
|
+
return true;
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Resolve a tree to full file contents (as Buffers).
|
|
151
|
+
* Reads all blobs in parallel for performance.
|
|
152
|
+
* Returns { filepath: Buffer } map for binary-safe handling.
|
|
153
|
+
*/
|
|
154
|
+
async resolveTree(tree: TreeMap): Promise<Record<string, Buffer>> {
|
|
155
|
+
const result: Record<string, Buffer> = {};
|
|
156
|
+
|
|
157
|
+
const entries = Object.entries(tree);
|
|
158
|
+
const blobPromises = entries.map(async ([filepath, hash]) => {
|
|
159
|
+
try {
|
|
160
|
+
const content = await this.readBlob(hash);
|
|
161
|
+
return { filepath, content };
|
|
162
|
+
} catch (err) {
|
|
163
|
+
debug.warn('snapshot', `Could not read blob ${hash} for ${filepath}:`, err);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const results = await Promise.all(blobPromises);
|
|
169
|
+
for (const r of results) {
|
|
170
|
+
if (r) {
|
|
171
|
+
result[r.filepath] = r.content;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Hash a file using mtime cache. Returns { hash, content? }.
|
|
180
|
+
* If the file hasn't changed (same mtime+size), returns cached hash without reading content.
|
|
181
|
+
* If the file has changed, reads content, hashes it, stores blob, and caches.
|
|
182
|
+
* Reads as Buffer to safely handle binary files (images, PDFs, etc.).
|
|
183
|
+
*
|
|
184
|
+
* @returns hash and content Buffer (content is null if cache hit and blob already exists)
|
|
185
|
+
*/
|
|
186
|
+
async hashFile(filepath: string, fullPath: string): Promise<{ hash: string; content: Buffer | null; cached: boolean }> {
|
|
187
|
+
await this.init();
|
|
188
|
+
|
|
189
|
+
const stat = await fs.stat(fullPath);
|
|
190
|
+
|
|
191
|
+
// Check mtime cache
|
|
192
|
+
const cached = this.fileHashCache.get(filepath);
|
|
193
|
+
if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
|
|
194
|
+
return { hash: cached.hash, content: null, cached: true };
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// File changed - read as Buffer (binary-safe, no encoding conversion)
|
|
198
|
+
const content = await fs.readFile(fullPath);
|
|
199
|
+
const hash = this.hashContent(content);
|
|
200
|
+
|
|
201
|
+
// Store blob (deduplication handled internally)
|
|
202
|
+
await this.storeBlob(content);
|
|
203
|
+
|
|
204
|
+
// Update cache
|
|
205
|
+
this.fileHashCache.set(filepath, {
|
|
206
|
+
mtimeMs: stat.mtimeMs,
|
|
207
|
+
size: stat.size,
|
|
208
|
+
hash
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
return { hash, content, cached: false };
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Delete a tree file (cleanup)
|
|
216
|
+
*/
|
|
217
|
+
async deleteTree(snapshotId: string): Promise<void> {
|
|
218
|
+
try {
|
|
219
|
+
await fs.unlink(join(TREES_DIR, `${snapshotId}.json`));
|
|
220
|
+
} catch {
|
|
221
|
+
// Ignore - might not exist
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Export singleton
|
|
227
|
+
export const blobStore = new BlobStore();
|