@myrialabs/clopen 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +6 -0
- package/.github/workflows/release.yml +60 -0
- package/.github/workflows/test.yml +40 -0
- package/CONTRIBUTING.md +499 -0
- package/LICENSE +21 -0
- package/README.md +209 -0
- package/backend/index.ts +156 -0
- package/backend/lib/chat/helpers.ts +42 -0
- package/backend/lib/chat/index.ts +2 -0
- package/backend/lib/chat/stream-manager.ts +1126 -0
- package/backend/lib/database/README.md +77 -0
- package/backend/lib/database/index.ts +119 -0
- package/backend/lib/database/migrations/001_create_projects_table.ts +31 -0
- package/backend/lib/database/migrations/002_create_chat_sessions_table.ts +33 -0
- package/backend/lib/database/migrations/003_create_messages_table.ts +32 -0
- package/backend/lib/database/migrations/004_create_prompt_templates_table.ts +34 -0
- package/backend/lib/database/migrations/005_create_settings_table.ts +24 -0
- package/backend/lib/database/migrations/006_add_user_to_messages.ts +58 -0
- package/backend/lib/database/migrations/007_create_stream_states_table.ts +41 -0
- package/backend/lib/database/migrations/008_create_message_snapshots_table.ts +62 -0
- package/backend/lib/database/migrations/009_add_delta_snapshot_fields.ts +41 -0
- package/backend/lib/database/migrations/010_add_soft_delete_and_branch_support.ts +70 -0
- package/backend/lib/database/migrations/011_git_like_commit_graph.ts +156 -0
- package/backend/lib/database/migrations/012_add_file_change_statistics.ts +41 -0
- package/backend/lib/database/migrations/013_checkpoint_tree_state.ts +118 -0
- package/backend/lib/database/migrations/014_add_engine_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/015_add_model_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/016_create_user_projects_table.ts +34 -0
- package/backend/lib/database/migrations/017_add_current_session_to_user_projects.ts +32 -0
- package/backend/lib/database/migrations/018_create_claude_accounts_table.ts +24 -0
- package/backend/lib/database/migrations/019_add_claude_account_to_sessions.ts +18 -0
- package/backend/lib/database/migrations/020_add_snapshot_tree_hash.ts +32 -0
- package/backend/lib/database/migrations/021_drop_prompt_templates_table.ts +33 -0
- package/backend/lib/database/migrations/index.ts +154 -0
- package/backend/lib/database/queries/checkpoint-queries.ts +87 -0
- package/backend/lib/database/queries/engine-queries.ts +75 -0
- package/backend/lib/database/queries/index.ts +9 -0
- package/backend/lib/database/queries/message-queries.ts +472 -0
- package/backend/lib/database/queries/project-queries.ts +117 -0
- package/backend/lib/database/queries/session-queries.ts +271 -0
- package/backend/lib/database/queries/settings-queries.ts +34 -0
- package/backend/lib/database/queries/snapshot-queries.ts +326 -0
- package/backend/lib/database/queries/utils-queries.ts +59 -0
- package/backend/lib/database/seeders/index.ts +13 -0
- package/backend/lib/database/seeders/settings_seeder.ts +84 -0
- package/backend/lib/database/utils/connection.ts +174 -0
- package/backend/lib/database/utils/index.ts +4 -0
- package/backend/lib/database/utils/migration-runner.ts +118 -0
- package/backend/lib/database/utils/seeder-runner.ts +121 -0
- package/backend/lib/engine/adapters/claude/environment.ts +164 -0
- package/backend/lib/engine/adapters/claude/error-handler.ts +60 -0
- package/backend/lib/engine/adapters/claude/index.ts +1 -0
- package/backend/lib/engine/adapters/claude/path-utils.ts +38 -0
- package/backend/lib/engine/adapters/claude/stream.ts +177 -0
- package/backend/lib/engine/adapters/opencode/index.ts +2 -0
- package/backend/lib/engine/adapters/opencode/message-converter.ts +862 -0
- package/backend/lib/engine/adapters/opencode/server.ts +104 -0
- package/backend/lib/engine/adapters/opencode/stream.ts +755 -0
- package/backend/lib/engine/index.ts +196 -0
- package/backend/lib/engine/types.ts +58 -0
- package/backend/lib/files/file-operations.ts +478 -0
- package/backend/lib/files/file-reading.ts +308 -0
- package/backend/lib/files/file-watcher.ts +383 -0
- package/backend/lib/files/path-browsing.ts +382 -0
- package/backend/lib/git/git-executor.ts +88 -0
- package/backend/lib/git/git-parser.ts +411 -0
- package/backend/lib/git/git-service.ts +505 -0
- package/backend/lib/mcp/README.md +1144 -0
- package/backend/lib/mcp/config.ts +316 -0
- package/backend/lib/mcp/index.ts +35 -0
- package/backend/lib/mcp/project-context.ts +236 -0
- package/backend/lib/mcp/servers/browser-automation/actions.ts +156 -0
- package/backend/lib/mcp/servers/browser-automation/browser.ts +419 -0
- package/backend/lib/mcp/servers/browser-automation/index.ts +791 -0
- package/backend/lib/mcp/servers/browser-automation/inspection.ts +501 -0
- package/backend/lib/mcp/servers/helper.ts +143 -0
- package/backend/lib/mcp/servers/index.ts +45 -0
- package/backend/lib/mcp/servers/weather/get-temperature.ts +56 -0
- package/backend/lib/mcp/servers/weather/index.ts +31 -0
- package/backend/lib/mcp/stdio-server.ts +103 -0
- package/backend/lib/mcp/types.ts +65 -0
- package/backend/lib/preview/browser/browser-audio-capture.ts +86 -0
- package/backend/lib/preview/browser/browser-console-manager.ts +263 -0
- package/backend/lib/preview/browser/browser-dialog-handler.ts +222 -0
- package/backend/lib/preview/browser/browser-interaction-handler.ts +421 -0
- package/backend/lib/preview/browser/browser-mcp-control.ts +415 -0
- package/backend/lib/preview/browser/browser-native-ui-handler.ts +512 -0
- package/backend/lib/preview/browser/browser-navigation-tracker.ts +104 -0
- package/backend/lib/preview/browser/browser-pool.ts +357 -0
- package/backend/lib/preview/browser/browser-preview-service.ts +882 -0
- package/backend/lib/preview/browser/browser-tab-manager.ts +935 -0
- package/backend/lib/preview/browser/browser-video-capture.ts +695 -0
- package/backend/lib/preview/browser/scripts/audio-stream.ts +292 -0
- package/backend/lib/preview/browser/scripts/cursor-tracking.ts +85 -0
- package/backend/lib/preview/browser/scripts/video-stream.ts +438 -0
- package/backend/lib/preview/browser/types.ts +359 -0
- package/backend/lib/preview/index.ts +24 -0
- package/backend/lib/project/index.ts +2 -0
- package/backend/lib/project/status-manager.ts +182 -0
- package/backend/lib/shared/index.ts +2 -0
- package/backend/lib/shared/port-utils.ts +25 -0
- package/backend/lib/shared/process-manager.ts +281 -0
- package/backend/lib/snapshot/blob-store.ts +227 -0
- package/backend/lib/snapshot/gitignore.ts +307 -0
- package/backend/lib/snapshot/helpers.ts +397 -0
- package/backend/lib/snapshot/snapshot-service.ts +483 -0
- package/backend/lib/terminal/helpers.ts +14 -0
- package/backend/lib/terminal/index.ts +8 -0
- package/backend/lib/terminal/pty-manager.ts +4 -0
- package/backend/lib/terminal/pty-session-manager.ts +387 -0
- package/backend/lib/terminal/shell-utils.ts +313 -0
- package/backend/lib/terminal/stream-manager.ts +293 -0
- package/backend/lib/tunnel/global-tunnel-manager.ts +243 -0
- package/backend/lib/tunnel/project-tunnel-manager.ts +311 -0
- package/backend/lib/user/helpers.ts +87 -0
- package/backend/lib/utils/ws.ts +944 -0
- package/backend/lib/vite-dev.ts +353 -0
- package/backend/middleware/cors.ts +15 -0
- package/backend/middleware/error-handler.ts +49 -0
- package/backend/middleware/logger.ts +9 -0
- package/backend/types/api.ts +24 -0
- package/backend/ws/README.md +1505 -0
- package/backend/ws/chat/background.ts +198 -0
- package/backend/ws/chat/index.ts +21 -0
- package/backend/ws/chat/stream.ts +707 -0
- package/backend/ws/engine/claude/accounts.ts +401 -0
- package/backend/ws/engine/claude/index.ts +13 -0
- package/backend/ws/engine/claude/status.ts +43 -0
- package/backend/ws/engine/index.ts +14 -0
- package/backend/ws/engine/opencode/index.ts +11 -0
- package/backend/ws/engine/opencode/status.ts +30 -0
- package/backend/ws/engine/utils.ts +36 -0
- package/backend/ws/files/index.ts +30 -0
- package/backend/ws/files/read.ts +189 -0
- package/backend/ws/files/search.ts +453 -0
- package/backend/ws/files/watch.ts +124 -0
- package/backend/ws/files/write.ts +143 -0
- package/backend/ws/git/branch.ts +106 -0
- package/backend/ws/git/commit.ts +39 -0
- package/backend/ws/git/conflict.ts +68 -0
- package/backend/ws/git/diff.ts +69 -0
- package/backend/ws/git/index.ts +24 -0
- package/backend/ws/git/log.ts +41 -0
- package/backend/ws/git/remote.ts +214 -0
- package/backend/ws/git/staging.ts +84 -0
- package/backend/ws/git/status.ts +90 -0
- package/backend/ws/index.ts +69 -0
- package/backend/ws/mcp/index.ts +61 -0
- package/backend/ws/messages/crud.ts +74 -0
- package/backend/ws/messages/index.ts +14 -0
- package/backend/ws/preview/browser/cleanup.ts +129 -0
- package/backend/ws/preview/browser/console.ts +114 -0
- package/backend/ws/preview/browser/interact.ts +513 -0
- package/backend/ws/preview/browser/mcp.ts +129 -0
- package/backend/ws/preview/browser/native-ui.ts +235 -0
- package/backend/ws/preview/browser/stats.ts +55 -0
- package/backend/ws/preview/browser/tab-info.ts +126 -0
- package/backend/ws/preview/browser/tab.ts +166 -0
- package/backend/ws/preview/browser/webcodecs.ts +293 -0
- package/backend/ws/preview/index.ts +146 -0
- package/backend/ws/projects/crud.ts +113 -0
- package/backend/ws/projects/index.ts +25 -0
- package/backend/ws/projects/presence.ts +46 -0
- package/backend/ws/projects/status.ts +116 -0
- package/backend/ws/sessions/crud.ts +327 -0
- package/backend/ws/sessions/index.ts +33 -0
- package/backend/ws/settings/crud.ts +112 -0
- package/backend/ws/settings/index.ts +14 -0
- package/backend/ws/snapshot/index.ts +17 -0
- package/backend/ws/snapshot/restore.ts +173 -0
- package/backend/ws/snapshot/timeline.ts +141 -0
- package/backend/ws/system/index.ts +14 -0
- package/backend/ws/system/operations.ts +49 -0
- package/backend/ws/terminal/index.ts +40 -0
- package/backend/ws/terminal/persistence.ts +153 -0
- package/backend/ws/terminal/session.ts +382 -0
- package/backend/ws/terminal/stream.ts +79 -0
- package/backend/ws/tunnel/index.ts +14 -0
- package/backend/ws/tunnel/operations.ts +91 -0
- package/backend/ws/types.ts +20 -0
- package/backend/ws/user/crud.ts +156 -0
- package/backend/ws/user/index.ts +14 -0
- package/bin/clopen.ts +307 -0
- package/bun.lock +1352 -0
- package/frontend/App.svelte +34 -0
- package/frontend/app.css +313 -0
- package/frontend/lib/app-environment.ts +10 -0
- package/frontend/lib/components/chat/ChatInterface.svelte +407 -0
- package/frontend/lib/components/chat/formatters/ErrorMessage.svelte +57 -0
- package/frontend/lib/components/chat/formatters/MessageFormatter.svelte +224 -0
- package/frontend/lib/components/chat/formatters/TextMessage.svelte +395 -0
- package/frontend/lib/components/chat/formatters/Tools.svelte +70 -0
- package/frontend/lib/components/chat/formatters/index.ts +3 -0
- package/frontend/lib/components/chat/input/ChatInput.svelte +421 -0
- package/frontend/lib/components/chat/input/components/ChatInputActions.svelte +78 -0
- package/frontend/lib/components/chat/input/components/DragDropOverlay.svelte +30 -0
- package/frontend/lib/components/chat/input/components/EditModeIndicator.svelte +33 -0
- package/frontend/lib/components/chat/input/components/EngineModelPicker.svelte +619 -0
- package/frontend/lib/components/chat/input/components/FileAttachmentPreview.svelte +48 -0
- package/frontend/lib/components/chat/input/components/LoadingIndicator.svelte +31 -0
- package/frontend/lib/components/chat/input/composables/use-animations.svelte.ts +201 -0
- package/frontend/lib/components/chat/input/composables/use-chat-actions.svelte.ts +148 -0
- package/frontend/lib/components/chat/input/composables/use-file-handling.svelte.ts +216 -0
- package/frontend/lib/components/chat/input/composables/use-input-state.svelte.ts +357 -0
- package/frontend/lib/components/chat/input/composables/use-textarea-resize.svelte.ts +57 -0
- package/frontend/lib/components/chat/message/ChatMessage.svelte +478 -0
- package/frontend/lib/components/chat/message/ChatMessages.svelte +541 -0
- package/frontend/lib/components/chat/message/DateSeparator.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageBubble.svelte +86 -0
- package/frontend/lib/components/chat/message/MessageHeader.svelte +157 -0
- package/frontend/lib/components/chat/modal/DebugModal.svelte +59 -0
- package/frontend/lib/components/chat/modal/TokenUsageModal.svelte +124 -0
- package/frontend/lib/components/chat/shared/index.ts +2 -0
- package/frontend/lib/components/chat/shared/utils.ts +116 -0
- package/frontend/lib/components/chat/tools/BashOutputTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/BashTool.svelte +46 -0
- package/frontend/lib/components/chat/tools/CustomMcpTool.svelte +139 -0
- package/frontend/lib/components/chat/tools/EditTool.svelte +48 -0
- package/frontend/lib/components/chat/tools/ExitPlanModeTool.svelte +32 -0
- package/frontend/lib/components/chat/tools/GlobTool.svelte +51 -0
- package/frontend/lib/components/chat/tools/GrepTool.svelte +90 -0
- package/frontend/lib/components/chat/tools/KillShellTool.svelte +26 -0
- package/frontend/lib/components/chat/tools/ListMcpResourcesTool.svelte +31 -0
- package/frontend/lib/components/chat/tools/NotebookEditTool.svelte +38 -0
- package/frontend/lib/components/chat/tools/ReadMcpResourceTool.svelte +34 -0
- package/frontend/lib/components/chat/tools/ReadTool.svelte +41 -0
- package/frontend/lib/components/chat/tools/TaskTool.svelte +64 -0
- package/frontend/lib/components/chat/tools/TodoWriteTool.svelte +75 -0
- package/frontend/lib/components/chat/tools/WebFetchTool.svelte +35 -0
- package/frontend/lib/components/chat/tools/WebSearchTool.svelte +84 -0
- package/frontend/lib/components/chat/tools/WriteTool.svelte +33 -0
- package/frontend/lib/components/chat/tools/components/CodeBlock.svelte +79 -0
- package/frontend/lib/components/chat/tools/components/DiffBlock.svelte +408 -0
- package/frontend/lib/components/chat/tools/components/FileHeader.svelte +45 -0
- package/frontend/lib/components/chat/tools/components/InfoLine.svelte +19 -0
- package/frontend/lib/components/chat/tools/components/StatsBadges.svelte +27 -0
- package/frontend/lib/components/chat/tools/components/TerminalCommand.svelte +54 -0
- package/frontend/lib/components/chat/tools/components/index.ts +7 -0
- package/frontend/lib/components/chat/tools/index.ts +26 -0
- package/frontend/lib/components/chat/widgets/FloatingTodoList.svelte +249 -0
- package/frontend/lib/components/chat/widgets/TokenUsage.svelte +78 -0
- package/frontend/lib/components/checkpoint/TimelineModal.svelte +391 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineEdge.svelte +26 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineGraph.svelte +87 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineNode.svelte +108 -0
- package/frontend/lib/components/checkpoint/timeline/TimelineVersionGroup.svelte +59 -0
- package/frontend/lib/components/checkpoint/timeline/animation.ts +168 -0
- package/frontend/lib/components/checkpoint/timeline/config.ts +44 -0
- package/frontend/lib/components/checkpoint/timeline/graph-builder.ts +304 -0
- package/frontend/lib/components/checkpoint/timeline/types.ts +65 -0
- package/frontend/lib/components/checkpoint/timeline/utils.ts +53 -0
- package/frontend/lib/components/common/Alert.svelte +139 -0
- package/frontend/lib/components/common/AvatarBubble.svelte +56 -0
- package/frontend/lib/components/common/Button.svelte +71 -0
- package/frontend/lib/components/common/Card.svelte +102 -0
- package/frontend/lib/components/common/Checkbox.svelte +48 -0
- package/frontend/lib/components/common/Dialog.svelte +249 -0
- package/frontend/lib/components/common/FolderBrowser.svelte +843 -0
- package/frontend/lib/components/common/Icon.svelte +58 -0
- package/frontend/lib/components/common/Input.svelte +72 -0
- package/frontend/lib/components/common/Lightbox.svelte +233 -0
- package/frontend/lib/components/common/LoadingScreen.svelte +52 -0
- package/frontend/lib/components/common/LoadingSpinner.svelte +48 -0
- package/frontend/lib/components/common/Modal.svelte +177 -0
- package/frontend/lib/components/common/ModalProvider.svelte +28 -0
- package/frontend/lib/components/common/ModelSelector.svelte +110 -0
- package/frontend/lib/components/common/MonacoEditor.svelte +569 -0
- package/frontend/lib/components/common/NotificationToast.svelte +113 -0
- package/frontend/lib/components/common/PageTemplate.svelte +76 -0
- package/frontend/lib/components/common/ProjectUserAvatars.svelte +79 -0
- package/frontend/lib/components/common/Select.svelte +98 -0
- package/frontend/lib/components/common/Textarea.svelte +80 -0
- package/frontend/lib/components/common/ThemeToggle.svelte +44 -0
- package/frontend/lib/components/common/lucide-icons.ts +1642 -0
- package/frontend/lib/components/common/material-icons.ts +1082 -0
- package/frontend/lib/components/common/xterm/XTerm.svelte +796 -0
- package/frontend/lib/components/common/xterm/index.ts +16 -0
- package/frontend/lib/components/common/xterm/terminal-config.ts +68 -0
- package/frontend/lib/components/common/xterm/types.ts +31 -0
- package/frontend/lib/components/common/xterm/xterm-service.ts +353 -0
- package/frontend/lib/components/files/FileNode.svelte +384 -0
- package/frontend/lib/components/files/FileTree.svelte +681 -0
- package/frontend/lib/components/files/FileViewer.svelte +728 -0
- package/frontend/lib/components/files/SearchResults.svelte +303 -0
- package/frontend/lib/components/git/BranchManager.svelte +458 -0
- package/frontend/lib/components/git/ChangesSection.svelte +107 -0
- package/frontend/lib/components/git/CommitForm.svelte +76 -0
- package/frontend/lib/components/git/ConflictResolver.svelte +158 -0
- package/frontend/lib/components/git/DiffViewer.svelte +364 -0
- package/frontend/lib/components/git/FileChangeItem.svelte +97 -0
- package/frontend/lib/components/git/GitButton.svelte +33 -0
- package/frontend/lib/components/git/GitLog.svelte +361 -0
- package/frontend/lib/components/git/GitModal.svelte +80 -0
- package/frontend/lib/components/history/HistoryModal.svelte +563 -0
- package/frontend/lib/components/history/HistoryView.svelte +615 -0
- package/frontend/lib/components/index.ts +34 -0
- package/frontend/lib/components/preview/browser/BrowserPreview.svelte +549 -0
- package/frontend/lib/components/preview/browser/components/Canvas.svelte +1058 -0
- package/frontend/lib/components/preview/browser/components/ConsolePanel.svelte +757 -0
- package/frontend/lib/components/preview/browser/components/Container.svelte +450 -0
- package/frontend/lib/components/preview/browser/components/ContextMenu.svelte +236 -0
- package/frontend/lib/components/preview/browser/components/SelectDropdown.svelte +224 -0
- package/frontend/lib/components/preview/browser/components/Toolbar.svelte +339 -0
- package/frontend/lib/components/preview/browser/components/VirtualCursor.svelte +36 -0
- package/frontend/lib/components/preview/browser/core/cleanup.svelte.ts +155 -0
- package/frontend/lib/components/preview/browser/core/coordinator.svelte.ts +837 -0
- package/frontend/lib/components/preview/browser/core/interactions.svelte.ts +113 -0
- package/frontend/lib/components/preview/browser/core/mcp-handlers.svelte.ts +296 -0
- package/frontend/lib/components/preview/browser/core/native-ui-handlers.svelte.ts +391 -0
- package/frontend/lib/components/preview/browser/core/stream-handler.svelte.ts +231 -0
- package/frontend/lib/components/preview/browser/core/tab-manager.svelte.ts +210 -0
- package/frontend/lib/components/preview/browser/core/tab-operations.svelte.ts +239 -0
- package/frontend/lib/components/preview/index.ts +2 -0
- package/frontend/lib/components/settings/SettingsModal.svelte +235 -0
- package/frontend/lib/components/settings/SettingsView.svelte +36 -0
- package/frontend/lib/components/settings/appearance/AppearanceSettings.svelte +51 -0
- package/frontend/lib/components/settings/appearance/LayoutPresetSettings.svelte +160 -0
- package/frontend/lib/components/settings/appearance/LayoutPreview.svelte +76 -0
- package/frontend/lib/components/settings/engines/AIEnginesSettings.svelte +917 -0
- package/frontend/lib/components/settings/general/AdvancedSettings.svelte +187 -0
- package/frontend/lib/components/settings/general/DataManagementSettings.svelte +203 -0
- package/frontend/lib/components/settings/general/GeneralSettings.svelte +10 -0
- package/frontend/lib/components/settings/model/ModelSettings.svelte +357 -0
- package/frontend/lib/components/settings/notifications/NotificationSettings.svelte +205 -0
- package/frontend/lib/components/settings/user/UserSettings.svelte +197 -0
- package/frontend/lib/components/terminal/Terminal.svelte +368 -0
- package/frontend/lib/components/terminal/TerminalTabs.svelte +87 -0
- package/frontend/lib/components/terminal/TerminalView.svelte +55 -0
- package/frontend/lib/components/tunnel/TunnelActive.svelte +142 -0
- package/frontend/lib/components/tunnel/TunnelButton.svelte +54 -0
- package/frontend/lib/components/tunnel/TunnelInactive.svelte +284 -0
- package/frontend/lib/components/tunnel/TunnelModal.svelte +47 -0
- package/frontend/lib/components/tunnel/TunnelQRCode.svelte +49 -0
- package/frontend/lib/components/workspace/DesktopNavigator.svelte +382 -0
- package/frontend/lib/components/workspace/MobileNavigator.svelte +403 -0
- package/frontend/lib/components/workspace/PanelContainer.svelte +100 -0
- package/frontend/lib/components/workspace/PanelHeader.svelte +505 -0
- package/frontend/lib/components/workspace/ViewMenu.svelte +162 -0
- package/frontend/lib/components/workspace/WorkspaceLayout.svelte +169 -0
- package/frontend/lib/components/workspace/layout/DesktopLayout.svelte +15 -0
- package/frontend/lib/components/workspace/layout/MobileLayout.svelte +17 -0
- package/frontend/lib/components/workspace/layout/split-pane/Container.svelte +42 -0
- package/frontend/lib/components/workspace/layout/split-pane/Handle.svelte +85 -0
- package/frontend/lib/components/workspace/layout/split-pane/Layout.svelte +37 -0
- package/frontend/lib/components/workspace/panels/ChatPanel.svelte +274 -0
- package/frontend/lib/components/workspace/panels/FilesPanel.svelte +1261 -0
- package/frontend/lib/components/workspace/panels/GitPanel.svelte +1560 -0
- package/frontend/lib/components/workspace/panels/PreviewPanel.svelte +150 -0
- package/frontend/lib/components/workspace/panels/TerminalPanel.svelte +73 -0
- package/frontend/lib/constants/preview.ts +45 -0
- package/frontend/lib/services/chat/chat.service.ts +704 -0
- package/frontend/lib/services/chat/index.ts +7 -0
- package/frontend/lib/services/notification/global-stream-monitor.ts +86 -0
- package/frontend/lib/services/notification/index.ts +8 -0
- package/frontend/lib/services/notification/push.service.ts +144 -0
- package/frontend/lib/services/notification/sound.service.ts +127 -0
- package/frontend/lib/services/preview/browser/browser-console.service.ts +61 -0
- package/frontend/lib/services/preview/browser/browser-webcodecs.service.ts +1499 -0
- package/frontend/lib/services/preview/browser/mcp-integration.svelte.ts +67 -0
- package/frontend/lib/services/preview/index.ts +23 -0
- package/frontend/lib/services/project/index.ts +8 -0
- package/frontend/lib/services/project/status.service.ts +159 -0
- package/frontend/lib/services/snapshot/snapshot.service.ts +47 -0
- package/frontend/lib/services/terminal/background/index.ts +130 -0
- package/frontend/lib/services/terminal/background/session-restore.ts +274 -0
- package/frontend/lib/services/terminal/background/stream-manager.ts +286 -0
- package/frontend/lib/services/terminal/index.ts +14 -0
- package/frontend/lib/services/terminal/persistence.service.ts +260 -0
- package/frontend/lib/services/terminal/project.service.ts +953 -0
- package/frontend/lib/services/terminal/session.service.ts +364 -0
- package/frontend/lib/services/terminal/terminal.service.ts +369 -0
- package/frontend/lib/stores/core/app.svelte.ts +117 -0
- package/frontend/lib/stores/core/files.svelte.ts +73 -0
- package/frontend/lib/stores/core/presence.svelte.ts +48 -0
- package/frontend/lib/stores/core/projects.svelte.ts +317 -0
- package/frontend/lib/stores/core/sessions.svelte.ts +383 -0
- package/frontend/lib/stores/features/claude-accounts.svelte.ts +58 -0
- package/frontend/lib/stores/features/models.svelte.ts +89 -0
- package/frontend/lib/stores/features/settings.svelte.ts +87 -0
- package/frontend/lib/stores/features/terminal.svelte.ts +701 -0
- package/frontend/lib/stores/features/tunnel.svelte.ts +161 -0
- package/frontend/lib/stores/features/user.svelte.ts +96 -0
- package/frontend/lib/stores/ui/chat-input.svelte.ts +57 -0
- package/frontend/lib/stores/ui/chat-model.svelte.ts +61 -0
- package/frontend/lib/stores/ui/dialog.svelte.ts +59 -0
- package/frontend/lib/stores/ui/edit-mode.svelte.ts +214 -0
- package/frontend/lib/stores/ui/notification.svelte.ts +166 -0
- package/frontend/lib/stores/ui/settings-modal.svelte.ts +88 -0
- package/frontend/lib/stores/ui/theme.svelte.ts +179 -0
- package/frontend/lib/stores/ui/workspace.svelte.ts +754 -0
- package/frontend/lib/types/native-ui.ts +73 -0
- package/frontend/lib/utils/chat/date-separator.ts +39 -0
- package/frontend/lib/utils/chat/message-grouper.ts +219 -0
- package/frontend/lib/utils/chat/message-processor.ts +135 -0
- package/frontend/lib/utils/chat/tool-handler.ts +161 -0
- package/frontend/lib/utils/chat/virtual-scroll.svelte.ts +142 -0
- package/frontend/lib/utils/click-outside.ts +20 -0
- package/frontend/lib/utils/context-manager.ts +257 -0
- package/frontend/lib/utils/file-icon-mappings.ts +769 -0
- package/frontend/lib/utils/folder-icon-mappings.ts +1030 -0
- package/frontend/lib/utils/git-status.ts +68 -0
- package/frontend/lib/utils/platform.ts +113 -0
- package/frontend/lib/utils/port-check.ts +65 -0
- package/frontend/lib/utils/terminalFormatter.ts +207 -0
- package/frontend/lib/utils/theme.ts +6 -0
- package/frontend/lib/utils/tree-visualizer.ts +320 -0
- package/frontend/lib/utils/ws.ts +44 -0
- package/frontend/main.ts +13 -0
- package/index.html +70 -0
- package/package.json +111 -0
- package/scripts/generate-icons.ts +87 -0
- package/scripts/pre-publish-check.sh +142 -0
- package/scripts/setup-hooks.sh +134 -0
- package/scripts/validate-branch-name.sh +47 -0
- package/scripts/validate-commit-msg.sh +42 -0
- package/shared/constants/engines.ts +134 -0
- package/shared/types/database/connection.ts +16 -0
- package/shared/types/database/index.ts +6 -0
- package/shared/types/database/schema.ts +141 -0
- package/shared/types/engine/index.ts +45 -0
- package/shared/types/filesystem/index.ts +22 -0
- package/shared/types/git.ts +171 -0
- package/shared/types/messaging/index.ts +239 -0
- package/shared/types/messaging/tool.ts +526 -0
- package/shared/types/network/api.ts +18 -0
- package/shared/types/network/index.ts +5 -0
- package/shared/types/stores/app.ts +23 -0
- package/shared/types/stores/dialog.ts +21 -0
- package/shared/types/stores/index.ts +3 -0
- package/shared/types/stores/settings.ts +15 -0
- package/shared/types/terminal/index.ts +44 -0
- package/shared/types/ui/components.ts +61 -0
- package/shared/types/ui/icons.ts +23 -0
- package/shared/types/ui/index.ts +22 -0
- package/shared/types/ui/notifications.ts +14 -0
- package/shared/types/ui/theme.ts +12 -0
- package/shared/types/websocket/index.ts +43 -0
- package/shared/types/window.d.ts +13 -0
- package/shared/utils/anonymous-user.ts +168 -0
- package/shared/utils/async.ts +10 -0
- package/shared/utils/diff-calculator.ts +184 -0
- package/shared/utils/file-type-detection.ts +166 -0
- package/shared/utils/logger.ts +158 -0
- package/shared/utils/message-formatter.ts +79 -0
- package/shared/utils/path.ts +47 -0
- package/shared/utils/ws-client.ts +768 -0
- package/shared/utils/ws-server.ts +660 -0
- package/static/audio/notification.ogg +0 -0
- package/static/favicon.svg +8 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-italic-latin.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin-ext.woff2 +0 -0
- package/static/fonts/dm-sans/dm-sans-normal-latin.woff2 +0 -0
- package/static/fonts/dm-sans.css +96 -0
- package/svelte.config.js +20 -0
- package/vite.config.ts +33 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { DatabaseConnection } from '$shared/types/database/connection';
|
|
2
|
+
|
|
3
|
+
import { debug } from '$shared/utils/logger';
|
|
4
|
+
interface Migration {
|
|
5
|
+
id: string;
|
|
6
|
+
description: string;
|
|
7
|
+
up: (db: DatabaseConnection) => void;
|
|
8
|
+
down: (db: DatabaseConnection) => void;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class MigrationRunner {
|
|
12
|
+
private db: DatabaseConnection;
|
|
13
|
+
private migrations: Migration[] = [];
|
|
14
|
+
|
|
15
|
+
constructor(db: DatabaseConnection) {
|
|
16
|
+
this.db = db;
|
|
17
|
+
this.ensureMigrationsTable();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private ensureMigrationsTable(): void {
|
|
21
|
+
this.db.exec(`
|
|
22
|
+
CREATE TABLE IF NOT EXISTS migrations (
|
|
23
|
+
id TEXT PRIMARY KEY,
|
|
24
|
+
description TEXT NOT NULL,
|
|
25
|
+
executed_at TEXT NOT NULL
|
|
26
|
+
)
|
|
27
|
+
`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
addMigration(migration: Migration): void {
|
|
31
|
+
this.migrations.push(migration);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async runMigrations(): Promise<void> {
|
|
35
|
+
debug.log('database', '🔄 Running database migrations...');
|
|
36
|
+
|
|
37
|
+
// Get already executed migrations
|
|
38
|
+
const executedMigrations = this.db.prepare(`
|
|
39
|
+
SELECT id FROM migrations ORDER BY id
|
|
40
|
+
`).all() as { id: string }[];
|
|
41
|
+
|
|
42
|
+
const executedIds = new Set(executedMigrations.map(m => m.id));
|
|
43
|
+
|
|
44
|
+
// Sort migrations by ID to ensure order
|
|
45
|
+
const sortedMigrations = this.migrations.sort((a, b) => a.id.localeCompare(b.id));
|
|
46
|
+
|
|
47
|
+
let executedCount = 0;
|
|
48
|
+
|
|
49
|
+
for (const migration of sortedMigrations) {
|
|
50
|
+
if (!executedIds.has(migration.id)) {
|
|
51
|
+
debug.log('database', `📋 Running migration: ${migration.id} - ${migration.description}`);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
// Execute migration
|
|
55
|
+
migration.up(this.db);
|
|
56
|
+
|
|
57
|
+
// Record migration as executed
|
|
58
|
+
this.db.prepare(`
|
|
59
|
+
INSERT INTO migrations (id, description, executed_at)
|
|
60
|
+
VALUES (?, ?, ?)
|
|
61
|
+
`).run(migration.id, migration.description, new Date().toISOString());
|
|
62
|
+
|
|
63
|
+
executedCount++;
|
|
64
|
+
debug.log('database', `✅ Migration ${migration.id} completed`);
|
|
65
|
+
} catch (error) {
|
|
66
|
+
debug.error('database', `❌ Migration ${migration.id} failed:`, error);
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (executedCount === 0) {
|
|
73
|
+
debug.log('database', 'ℹ️ No new migrations to run');
|
|
74
|
+
} else {
|
|
75
|
+
debug.log('database', `✅ Executed ${executedCount} migrations successfully`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async rollbackMigration(migrationId: string): Promise<void> {
|
|
80
|
+
debug.log('database', `🔄 Rolling back migration: ${migrationId}`);
|
|
81
|
+
|
|
82
|
+
const migration = this.migrations.find(m => m.id === migrationId);
|
|
83
|
+
if (!migration) {
|
|
84
|
+
throw new Error(`Migration ${migrationId} not found`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
// Execute rollback
|
|
89
|
+
migration.down(this.db);
|
|
90
|
+
|
|
91
|
+
// Remove migration record
|
|
92
|
+
this.db.prepare(`
|
|
93
|
+
DELETE FROM migrations WHERE id = ?
|
|
94
|
+
`).run(migrationId);
|
|
95
|
+
|
|
96
|
+
debug.log('database', `✅ Migration ${migrationId} rolled back successfully`);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
debug.error('database', `❌ Rollback of ${migrationId} failed:`, error);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getExecutedMigrations(): string[] {
|
|
104
|
+
const migrations = this.db.prepare(`
|
|
105
|
+
SELECT id FROM migrations ORDER BY id
|
|
106
|
+
`).all() as { id: string }[];
|
|
107
|
+
|
|
108
|
+
return migrations.map(m => m.id);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
getPendingMigrations(): string[] {
|
|
112
|
+
const executed = new Set(this.getExecutedMigrations());
|
|
113
|
+
return this.migrations
|
|
114
|
+
.filter(m => !executed.has(m.id))
|
|
115
|
+
.sort((a, b) => a.id.localeCompare(b.id))
|
|
116
|
+
.map(m => m.id);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { DatabaseConnection } from '$shared/types/database/connection';
|
|
2
|
+
|
|
3
|
+
import { debug } from '$shared/utils/logger';
|
|
4
|
+
interface Seeder {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
seed: (db: DatabaseConnection) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class SeederRunner {
|
|
11
|
+
private db: DatabaseConnection;
|
|
12
|
+
private seeders: Seeder[] = [];
|
|
13
|
+
|
|
14
|
+
constructor(db: DatabaseConnection) {
|
|
15
|
+
this.db = db;
|
|
16
|
+
this.ensureSeedersTable();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
private ensureSeedersTable(): void {
|
|
20
|
+
this.db.exec(`
|
|
21
|
+
CREATE TABLE IF NOT EXISTS seeders (
|
|
22
|
+
name TEXT PRIMARY KEY,
|
|
23
|
+
description TEXT NOT NULL,
|
|
24
|
+
executed_at TEXT NOT NULL
|
|
25
|
+
)
|
|
26
|
+
`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
addSeeder(seeder: Seeder): void {
|
|
30
|
+
this.seeders.push(seeder);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async runSeeders(force: boolean = false): Promise<void> {
|
|
34
|
+
debug.log('database', '🌱 Running database seeders...');
|
|
35
|
+
|
|
36
|
+
// Get already executed seeders
|
|
37
|
+
const executedSeeders = this.db.prepare(`
|
|
38
|
+
SELECT name FROM seeders
|
|
39
|
+
`).all() as { name: string }[];
|
|
40
|
+
|
|
41
|
+
const executedNames = new Set(executedSeeders.map(s => s.name));
|
|
42
|
+
|
|
43
|
+
let executedCount = 0;
|
|
44
|
+
|
|
45
|
+
for (const seeder of this.seeders) {
|
|
46
|
+
if (!executedNames.has(seeder.name) || force) {
|
|
47
|
+
debug.log('database', `🌱 Running seeder: ${seeder.name} - ${seeder.description}`);
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// Execute seeder
|
|
51
|
+
seeder.seed(this.db);
|
|
52
|
+
|
|
53
|
+
// Record seeder as executed (replace if force)
|
|
54
|
+
if (force && executedNames.has(seeder.name)) {
|
|
55
|
+
this.db.prepare(`
|
|
56
|
+
UPDATE seeders
|
|
57
|
+
SET description = ?, executed_at = ?
|
|
58
|
+
WHERE name = ?
|
|
59
|
+
`).run(seeder.description, new Date().toISOString(), seeder.name);
|
|
60
|
+
} else {
|
|
61
|
+
this.db.prepare(`
|
|
62
|
+
INSERT OR REPLACE INTO seeders (name, description, executed_at)
|
|
63
|
+
VALUES (?, ?, ?)
|
|
64
|
+
`).run(seeder.name, seeder.description, new Date().toISOString());
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
executedCount++;
|
|
68
|
+
debug.log('database', `✅ Seeder ${seeder.name} completed`);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
debug.error('database', `❌ Seeder ${seeder.name} failed:`, error);
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (executedCount === 0) {
|
|
77
|
+
debug.log('database', 'ℹ️ No new seeders to run');
|
|
78
|
+
} else {
|
|
79
|
+
debug.log('database', `✅ Executed ${executedCount} seeders successfully`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async runSpecificSeeder(seederName: string, force: boolean = false): Promise<void> {
|
|
84
|
+
const seeder = this.seeders.find(s => s.name === seederName);
|
|
85
|
+
if (!seeder) {
|
|
86
|
+
throw new Error(`Seeder ${seederName} not found`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
debug.log('database', `🌱 Running specific seeder: ${seederName}`);
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
seeder.seed(this.db);
|
|
93
|
+
|
|
94
|
+
// Record seeder as executed
|
|
95
|
+
this.db.prepare(`
|
|
96
|
+
INSERT OR REPLACE INTO seeders (name, description, executed_at)
|
|
97
|
+
VALUES (?, ?, ?)
|
|
98
|
+
`).run(seeder.name, seeder.description, new Date().toISOString());
|
|
99
|
+
|
|
100
|
+
debug.log('database', `✅ Seeder ${seederName} completed`);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
debug.error('database', `❌ Seeder ${seederName} failed:`, error);
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getExecutedSeeders(): string[] {
|
|
108
|
+
const seeders = this.db.prepare(`
|
|
109
|
+
SELECT name FROM seeders ORDER BY executed_at
|
|
110
|
+
`).all() as { name: string }[];
|
|
111
|
+
|
|
112
|
+
return seeders.map(s => s.name);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
getPendingSeeders(): string[] {
|
|
116
|
+
const executed = new Set(this.getExecutedSeeders());
|
|
117
|
+
return this.seeders
|
|
118
|
+
.filter(s => !executed.has(s.name))
|
|
119
|
+
.map(s => s.name);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment configuration for Claude Code engine.
|
|
3
|
+
*
|
|
4
|
+
* Builds an env dictionary to pass via SDK Options.env
|
|
5
|
+
* instead of mutating process.env directly.
|
|
6
|
+
* This avoids global state race conditions when multiple projects
|
|
7
|
+
* stream concurrently.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { isWindows, findGitBash } from '../../../terminal/shell-utils.js';
|
|
13
|
+
import { engineQueries } from '../../../database/queries';
|
|
14
|
+
import { debug } from '$shared/utils/logger';
|
|
15
|
+
|
|
16
|
+
let _ready = false;
|
|
17
|
+
let _initPromise: Promise<void> | null = null;
|
|
18
|
+
let _envOverrides: Record<string, string> = {};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Returns the isolated Claude config directory under ~/.clopen/claude/user/
|
|
22
|
+
*/
|
|
23
|
+
export function getClaudeUserConfigDir(): string {
|
|
24
|
+
return join(homedir(), '.clopen', 'claude', 'user');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resets environment state so the next setupEnvironmentOnce() re-reads
|
|
29
|
+
* the active account token from DB. Called after account switch/delete.
|
|
30
|
+
*/
|
|
31
|
+
export function resetEnvironment(): void {
|
|
32
|
+
_ready = false;
|
|
33
|
+
_initPromise = null;
|
|
34
|
+
_envOverrides = {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Idempotent, concurrency-safe environment setup.
|
|
39
|
+
* Multiple concurrent calls share a single initialization promise.
|
|
40
|
+
*/
|
|
41
|
+
export async function setupEnvironmentOnce(): Promise<void> {
|
|
42
|
+
if (_ready) return;
|
|
43
|
+
if (_initPromise) return _initPromise;
|
|
44
|
+
|
|
45
|
+
_initPromise = _doSetup();
|
|
46
|
+
try {
|
|
47
|
+
await _initPromise;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
_initPromise = null;
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Returns the env dictionary for SDK Options.env.
|
|
56
|
+
* Merges process.env with our overrides so the SDK subprocess
|
|
57
|
+
* inherits everything it needs.
|
|
58
|
+
*
|
|
59
|
+
* When accountId is provided, overrides the OAuth token with that
|
|
60
|
+
* specific account's token instead of the globally active account.
|
|
61
|
+
*/
|
|
62
|
+
export function getEngineEnv(accountId?: number): Record<string, string> {
|
|
63
|
+
const env: Record<string, string> = {};
|
|
64
|
+
// Copy process.env (filter out undefined values)
|
|
65
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
66
|
+
if (value !== undefined) {
|
|
67
|
+
env[key] = value;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Apply our overrides
|
|
71
|
+
Object.assign(env, _envOverrides);
|
|
72
|
+
|
|
73
|
+
// Override with specific account token if requested
|
|
74
|
+
if (accountId !== undefined) {
|
|
75
|
+
try {
|
|
76
|
+
const accounts = engineQueries.getClaudeAccounts();
|
|
77
|
+
const account = accounts.find(a => a.id === accountId);
|
|
78
|
+
if (account) {
|
|
79
|
+
env['CLAUDE_CODE_OAUTH_TOKEN'] = account.oauth_token;
|
|
80
|
+
debug.log('engine', `Claude Code: Per-session account override → "${account.name}"`);
|
|
81
|
+
}
|
|
82
|
+
} catch {
|
|
83
|
+
// Ignore — fall back to default token from overrides
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return env;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async function _doSetup(): Promise<void> {
|
|
91
|
+
const overrides: Record<string, string> = {};
|
|
92
|
+
|
|
93
|
+
// Bypass permissions for Claude Code
|
|
94
|
+
overrides['CLAUDE_CODE_PERMISSION_MODE'] = 'bypassPermissions';
|
|
95
|
+
|
|
96
|
+
// Isolate Claude config to ~/.clopen/claude/user/
|
|
97
|
+
const claudeUserDir = getClaudeUserConfigDir();
|
|
98
|
+
await ensureDirectory(claudeUserDir);
|
|
99
|
+
overrides['CLAUDE_CONFIG_DIR'] = claudeUserDir;
|
|
100
|
+
|
|
101
|
+
// Inject OAuth token from active account (if any)
|
|
102
|
+
try {
|
|
103
|
+
const activeAccount = engineQueries.getActiveClaudeAccount();
|
|
104
|
+
if (activeAccount) {
|
|
105
|
+
overrides['CLAUDE_CODE_OAUTH_TOKEN'] = activeAccount.oauth_token;
|
|
106
|
+
debug.log('engine', `✅ Claude Code: Using account "${activeAccount.name}"`);
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
// DB may not be initialized yet during first startup
|
|
110
|
+
debug.warn('engine', '⚠️ Claude Code: Could not read active account (DB may not be ready)');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Setup Git Bash on Windows
|
|
114
|
+
if (isWindows) {
|
|
115
|
+
const existingPath = process.env.CLAUDE_CODE_GIT_BASH_PATH;
|
|
116
|
+
if (existingPath) {
|
|
117
|
+
overrides['CLAUDE_CODE_GIT_BASH_PATH'] = existingPath;
|
|
118
|
+
} else {
|
|
119
|
+
const gitBashPath = await findGitBash();
|
|
120
|
+
if (gitBashPath) {
|
|
121
|
+
overrides['CLAUDE_CODE_GIT_BASH_PATH'] = gitBashPath;
|
|
122
|
+
debug.log('engine', '✅ Claude Code: Git Bash configured at:', gitBashPath);
|
|
123
|
+
} else {
|
|
124
|
+
debug.warn('engine', '⚠️ Claude Code: Git Bash not found - may have limited functionality on Windows');
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Terminal environment variables
|
|
130
|
+
overrides['FORCE_COLOR'] = '1';
|
|
131
|
+
overrides['COLORTERM'] = 'truecolor';
|
|
132
|
+
if (!process.env.TERM) {
|
|
133
|
+
overrides['TERM'] = 'xterm-256color';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_envOverrides = overrides;
|
|
137
|
+
_ready = true;
|
|
138
|
+
debug.log('engine', '✅ Environment configured (one-time setup)');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function ensureDirectory(dirPath: string): Promise<void> {
|
|
142
|
+
try {
|
|
143
|
+
await Bun.file(dirPath).stat();
|
|
144
|
+
} catch {
|
|
145
|
+
// Directory doesn't exist, create via temp file workaround
|
|
146
|
+
const tempFile = join(dirPath, '.init');
|
|
147
|
+
await Bun.write(tempFile, '');
|
|
148
|
+
try {
|
|
149
|
+
if (await Bun.file(tempFile).exists()) {
|
|
150
|
+
if (process.platform === 'win32') {
|
|
151
|
+
await Bun.spawn(['cmd', '/c', 'del', '/f', '/q', tempFile.replace(/\//g, '\\')], {
|
|
152
|
+
stdout: 'ignore', stderr: 'ignore'
|
|
153
|
+
}).exited;
|
|
154
|
+
} else {
|
|
155
|
+
await Bun.spawn(['rm', '-f', tempFile], {
|
|
156
|
+
stdout: 'ignore', stderr: 'ignore'
|
|
157
|
+
}).exited;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
// Ignore cleanup errors
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export function handleStreamError(error: unknown): void {
|
|
2
|
+
if (!(error instanceof Error)) {
|
|
3
|
+
throw error;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
// Abort errors are expected during cancellation - don't re-throw
|
|
7
|
+
if (error.name === 'AbortError' || error.message.includes('aborted') || error.message.includes('abort')) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (error.message.includes('error_max_turns') || error.message.includes('max_turns')) {
|
|
12
|
+
throw new Error('Unexpected conversation limit reached. This should not happen with unlimited turns enabled.');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (error.message.includes('git-bash') || error.message.includes('requires git-bash')) {
|
|
16
|
+
throw new Error('Claude Code requires git-bash on Windows. Please ensure Git is installed and accessible.');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (error.message.includes('ENOENT') || error.message.includes('spawn claude-code')) {
|
|
20
|
+
throw new Error('Claude Agent SDK not found. Please install it with: npm install @anthropic-ai/claude-agent-sdk');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (error.message.includes('API key')) {
|
|
24
|
+
throw new Error('Claude Agent SDK requires an Anthropic API key. Please set ANTHROPIC_API_KEY environment variable.');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Extract detailed error info from API errors (Anthropic SDK APIError has .status, .error)
|
|
28
|
+
const enriched = extractDetailedError(error);
|
|
29
|
+
throw new Error(enriched);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Extract detailed error info from an Error object.
|
|
34
|
+
* Handles Anthropic SDK's APIError which has .status and .error.message fields.
|
|
35
|
+
*
|
|
36
|
+
* Does NOT prepend error class names (e.g. "APIError", "UnknownError") —
|
|
37
|
+
* those are SDK implementation details, not useful for the end user.
|
|
38
|
+
* The stream-manager's normalizeErrorText() also strips them as a safety net.
|
|
39
|
+
*/
|
|
40
|
+
function extractDetailedError(error: Error): string {
|
|
41
|
+
const err = error as Record<string, any>;
|
|
42
|
+
|
|
43
|
+
// Use the message directly, stripping any redundant leading "Error: " prefix
|
|
44
|
+
let message = (err.message || '').replace(/^Error:\s*/, '');
|
|
45
|
+
if (!message) {
|
|
46
|
+
message = err.name && err.name !== 'Error' ? String(err.name) : 'Unknown error';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Append status code if available (Anthropic APIError has .status)
|
|
50
|
+
if (err.status && !message.includes(String(err.status))) {
|
|
51
|
+
message += ` (status ${err.status})`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Append nested error body (Anthropic APIError has .error.message)
|
|
55
|
+
if (err.error?.message && !message.includes(err.error.message)) {
|
|
56
|
+
message += ` - ${err.error.message}`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return message || error.message || 'Unknown error';
|
|
60
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ClaudeCodeEngine } from './stream';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { resolve } from 'path';
|
|
2
|
+
|
|
3
|
+
export function normalizePath(projectPath: string): string {
|
|
4
|
+
let normalizedProjectPath = projectPath;
|
|
5
|
+
|
|
6
|
+
if (process.platform === 'win32') {
|
|
7
|
+
if (!projectPath.match(/^[A-Za-z]:\\/)) {
|
|
8
|
+
if (projectPath.startsWith('\\')) {
|
|
9
|
+
const currentDrive = process.cwd().substring(0, 2);
|
|
10
|
+
normalizedProjectPath = currentDrive + projectPath;
|
|
11
|
+
} else {
|
|
12
|
+
normalizedProjectPath = resolve(projectPath);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
normalizedProjectPath = normalizedProjectPath.replace(/\//g, '\\');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return normalizedProjectPath;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function changeWorkingDirectory(targetPath: string): string {
|
|
23
|
+
const originalCwd = process.cwd();
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
process.chdir(targetPath);
|
|
27
|
+
return originalCwd;
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw new Error(`Cannot change working directory to ${targetPath}: ${error}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function restoreWorkingDirectory(originalCwd: string): void {
|
|
34
|
+
try {
|
|
35
|
+
process.chdir(originalCwd);
|
|
36
|
+
} catch (error) {
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code Engine Adapter
|
|
3
|
+
*
|
|
4
|
+
* Wraps the @anthropic-ai/claude-agent-sdk into the AIEngine interface.
|
|
5
|
+
* Messages are already in SDKMessage format — no conversion needed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
import { query, type SDKMessage, type EngineSDKMessage, type Options, type Query, type SDKUserMessage } from '$shared/types/messaging';
|
|
10
|
+
import type { PermissionMode } from "@anthropic-ai/claude-agent-sdk";
|
|
11
|
+
import { normalizePath } from './path-utils';
|
|
12
|
+
import { setupEnvironmentOnce, getEngineEnv } from './environment';
|
|
13
|
+
import { handleStreamError } from './error-handler';
|
|
14
|
+
import { getEnabledMcpServers, getAllowedMcpTools } from '../../../mcp';
|
|
15
|
+
import type { AIEngine, EngineQueryOptions } from '../../types';
|
|
16
|
+
import type { EngineModel } from '$shared/types/engine';
|
|
17
|
+
import { CLAUDE_CODE_MODELS } from '$shared/constants/engines';
|
|
18
|
+
|
|
19
|
+
import { debug } from '$shared/utils/logger';
|
|
20
|
+
|
|
21
|
+
/** Type guard for AsyncIterable */
|
|
22
|
+
function isAsyncIterable<T>(value: unknown): value is AsyncIterable<T> {
|
|
23
|
+
return value != null && typeof value === 'object' && Symbol.asyncIterator in value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class ClaudeCodeEngine implements AIEngine {
|
|
27
|
+
readonly name = 'claude-code' as const;
|
|
28
|
+
private _isInitialized = false;
|
|
29
|
+
private activeController: AbortController | null = null;
|
|
30
|
+
private activeQuery: Query | null = null;
|
|
31
|
+
|
|
32
|
+
get isInitialized(): boolean {
|
|
33
|
+
return this._isInitialized;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
get isActive(): boolean {
|
|
37
|
+
return this.activeController !== null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async initialize(): Promise<void> {
|
|
41
|
+
if (this._isInitialized) return;
|
|
42
|
+
|
|
43
|
+
// One-time environment setup (idempotent, concurrency-safe)
|
|
44
|
+
await setupEnvironmentOnce();
|
|
45
|
+
|
|
46
|
+
this._isInitialized = true;
|
|
47
|
+
debug.log('engine', '✅ Claude Code engine initialized');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async dispose(): Promise<void> {
|
|
51
|
+
await this.cancel();
|
|
52
|
+
this._isInitialized = false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async getAvailableModels(): Promise<EngineModel[]> {
|
|
56
|
+
return CLAUDE_CODE_MODELS;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Stream query with real-time callbacks
|
|
61
|
+
*/
|
|
62
|
+
async *streamQuery(options: EngineQueryOptions): AsyncGenerator<EngineSDKMessage, void, unknown> {
|
|
63
|
+
const {
|
|
64
|
+
projectPath,
|
|
65
|
+
prompt,
|
|
66
|
+
resume,
|
|
67
|
+
maxTurns = undefined,
|
|
68
|
+
model = 'sonnet',
|
|
69
|
+
includePartialMessages = false,
|
|
70
|
+
abortController,
|
|
71
|
+
claudeAccountId
|
|
72
|
+
} = options;
|
|
73
|
+
|
|
74
|
+
debug.log('chat', "Claude Code - Stream Query");
|
|
75
|
+
debug.log('chat', { prompt });
|
|
76
|
+
|
|
77
|
+
this.activeController = abortController || new AbortController();
|
|
78
|
+
|
|
79
|
+
const normalizedProjectPath = normalizePath(projectPath);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Get custom MCP servers and allowed tools
|
|
83
|
+
const mcpServers = getEnabledMcpServers();
|
|
84
|
+
const allowedMcpTools = getAllowedMcpTools();
|
|
85
|
+
|
|
86
|
+
debug.log('mcp', '📦 Loading custom MCP servers...');
|
|
87
|
+
debug.log('mcp', `Enabled servers: ${Object.keys(mcpServers).length}`);
|
|
88
|
+
debug.log('mcp', `Allowed tools: ${allowedMcpTools.length}`);
|
|
89
|
+
|
|
90
|
+
// SDK uses cwd from options — no process.chdir() needed.
|
|
91
|
+
// Environment is passed via env option — no process.env mutation.
|
|
92
|
+
// When claudeAccountId is specified, the env uses that account's token
|
|
93
|
+
// instead of the globally active account.
|
|
94
|
+
const sdkOptions: Options = {
|
|
95
|
+
permissionMode: 'bypassPermissions' as PermissionMode,
|
|
96
|
+
allowDangerouslySkipPermissions: true,
|
|
97
|
+
cwd: normalizedProjectPath,
|
|
98
|
+
env: getEngineEnv(claudeAccountId),
|
|
99
|
+
systemPrompt: { type: "preset", preset: "claude_code" },
|
|
100
|
+
settingSources: ["user", "project", "local"],
|
|
101
|
+
forkSession: true,
|
|
102
|
+
...(model && { model }),
|
|
103
|
+
...(resume && { resume }),
|
|
104
|
+
...(maxTurns && { maxTurns }),
|
|
105
|
+
...(includePartialMessages && { includePartialMessages }),
|
|
106
|
+
abortController: this.activeController,
|
|
107
|
+
...(Object.keys(mcpServers).length > 0 && { mcpServers }),
|
|
108
|
+
...(allowedMcpTools.length > 0 && { allowedTools: allowedMcpTools })
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Create async iterable from single message if needed
|
|
112
|
+
let promptIterable: AsyncIterable<SDKUserMessage>;
|
|
113
|
+
|
|
114
|
+
if (isAsyncIterable<SDKUserMessage>(prompt)) {
|
|
115
|
+
promptIterable = prompt;
|
|
116
|
+
} else {
|
|
117
|
+
promptIterable = (async function* () {
|
|
118
|
+
yield prompt as SDKUserMessage;
|
|
119
|
+
})();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const queryInstance = query({
|
|
123
|
+
prompt: promptIterable,
|
|
124
|
+
options: sdkOptions,
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
this.activeQuery = queryInstance;
|
|
128
|
+
|
|
129
|
+
for await (const message of queryInstance) {
|
|
130
|
+
yield message;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
} catch (error) {
|
|
134
|
+
handleStreamError(error);
|
|
135
|
+
} finally {
|
|
136
|
+
this.activeController = null;
|
|
137
|
+
this.activeQuery = null;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Cancel active query
|
|
143
|
+
*/
|
|
144
|
+
async cancel(): Promise<void> {
|
|
145
|
+
if (this.activeQuery && typeof this.activeQuery.interrupt === 'function') {
|
|
146
|
+
try {
|
|
147
|
+
await this.activeQuery.interrupt();
|
|
148
|
+
} catch {
|
|
149
|
+
// Ignore interrupt errors
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (this.activeController) {
|
|
154
|
+
this.activeController.abort();
|
|
155
|
+
this.activeController = null;
|
|
156
|
+
}
|
|
157
|
+
this.activeQuery = null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Interrupt the active query
|
|
162
|
+
*/
|
|
163
|
+
async interrupt(): Promise<void> {
|
|
164
|
+
if (this.activeQuery && typeof this.activeQuery.interrupt === 'function') {
|
|
165
|
+
await this.activeQuery.interrupt();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Change permission mode for active query
|
|
171
|
+
*/
|
|
172
|
+
async setPermissionMode(mode: PermissionMode): Promise<void> {
|
|
173
|
+
if (this.activeQuery && typeof this.activeQuery.setPermissionMode === 'function') {
|
|
174
|
+
await this.activeQuery.setPermissionMode(mode);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|