@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,796 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Modular XTerm Component - xterm.js Integration
|
|
3
|
+
Cleaner, more maintainable implementation using XTermService
|
|
4
|
+
-->
|
|
5
|
+
<script lang="ts">
|
|
6
|
+
import { onMount, onDestroy } from 'svelte';
|
|
7
|
+
import { browser } from '$frontend/lib/app-environment';
|
|
8
|
+
import type { TerminalSession } from '$shared/types/terminal';
|
|
9
|
+
import { XTermService } from './xterm-service';
|
|
10
|
+
import type { XTermProps, XTermMethods } from './types';
|
|
11
|
+
import { terminalStore } from '$frontend/lib/stores/features/terminal.svelte';
|
|
12
|
+
import { backgroundTerminalService } from '$frontend/lib/services/terminal/background';
|
|
13
|
+
import { terminalService } from '$frontend/lib/services/terminal';
|
|
14
|
+
|
|
15
|
+
// Import CSS directly - Vite will handle it properly
|
|
16
|
+
import 'xterm/css/xterm.css';
|
|
17
|
+
|
|
18
|
+
// Props
|
|
19
|
+
const {
|
|
20
|
+
session,
|
|
21
|
+
class: className = '',
|
|
22
|
+
hasActiveProject = false,
|
|
23
|
+
projectPath = '',
|
|
24
|
+
isExecuting = false,
|
|
25
|
+
onExecuteCommand,
|
|
26
|
+
onClearSession
|
|
27
|
+
}: XTermProps = $props();
|
|
28
|
+
|
|
29
|
+
// Track local execution state for this session
|
|
30
|
+
let sessionExecuting = $state(false);
|
|
31
|
+
let lastExecutingState = $state(false);
|
|
32
|
+
|
|
33
|
+
// Local state
|
|
34
|
+
let terminalContainer = $state<HTMLDivElement>();
|
|
35
|
+
const xtermService = new XTermService();
|
|
36
|
+
let lastProcessedIndex = $state(-1);
|
|
37
|
+
let isInitialized = $state(false); // Reactive state for initialization
|
|
38
|
+
const initialPromptShown = $state(false); // Track if initial prompt has been shown
|
|
39
|
+
let isTerminalReady = $state(false); // Track if terminal is ready for input
|
|
40
|
+
let isAfterClearOperation = $state(false); // Track if session was manually cleared
|
|
41
|
+
let lastSessionId = $state(''); // Track last session to detect changes
|
|
42
|
+
let promptShownForSession = $state(false); // Track if prompt shown for current session
|
|
43
|
+
let justRestoredFromBuffer = $state(false); // Track if we just restored from buffer
|
|
44
|
+
let isInitialMount = $state(true); // Track if this is the initial mount after restore
|
|
45
|
+
const keyboardEventHandler: ((e: KeyboardEvent) => void) | null = null; // Store event handler reference
|
|
46
|
+
|
|
47
|
+
// Track which sessions have connected to PTY in this browser session (after refresh)
|
|
48
|
+
// This prevents reconnection issues after browser refresh
|
|
49
|
+
const connectedSessions = new Set<string>();
|
|
50
|
+
|
|
51
|
+
// Initialize terminal
|
|
52
|
+
async function initializeTerminal() {
|
|
53
|
+
if (!terminalContainer) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await xtermService.initialize(terminalContainer);
|
|
58
|
+
|
|
59
|
+
if (xtermService.isInitialized && session) {
|
|
60
|
+
// CRITICAL: Mark session as connected BEFORE setting isInitialized
|
|
61
|
+
// This prevents the session change $effect from triggering a duplicate connection
|
|
62
|
+
// because $effect checks connectedSessions.has(session.id)
|
|
63
|
+
if (hasActiveProject && onExecuteCommand) {
|
|
64
|
+
connectedSessions.add(session.id);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Update reactive state AFTER marking connected
|
|
68
|
+
isInitialized = xtermService.isInitialized;
|
|
69
|
+
|
|
70
|
+
// Setup input handling - forwards all keystrokes to PTY
|
|
71
|
+
xtermService.setupInput(
|
|
72
|
+
session.id,
|
|
73
|
+
onExecuteCommand,
|
|
74
|
+
onClearSession,
|
|
75
|
+
hasActiveProject,
|
|
76
|
+
projectPath,
|
|
77
|
+
session?.directory
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// Focus terminal for immediate input
|
|
81
|
+
xtermService.terminal?.focus();
|
|
82
|
+
|
|
83
|
+
// Process existing session data first - but ONLY if not initial mount
|
|
84
|
+
// On initial mount after restore, lines will be rendered by restoreSessionBuffer
|
|
85
|
+
if (session && !isInitialMount) {
|
|
86
|
+
processSessionLines();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Mark terminal as ready immediately - shell handles prompts
|
|
90
|
+
isTerminalReady = true;
|
|
91
|
+
|
|
92
|
+
// CRITICAL: Auto-connect to PTY session right after initialization
|
|
93
|
+
// This opens the SSE stream to persistent PTY
|
|
94
|
+
if (hasActiveProject && onExecuteCommand && session) {
|
|
95
|
+
// IMPORTANT: Get actual terminal dimensions and pass to PTY
|
|
96
|
+
// This ensures PTY is created with correct size to prevent cursor positioning issues
|
|
97
|
+
let terminalSize: { cols: number; rows: number } | undefined;
|
|
98
|
+
if (xtermService.fitAddon && xtermService.terminal) {
|
|
99
|
+
// First fit to get proper dimensions
|
|
100
|
+
xtermService.fitAddon.fit();
|
|
101
|
+
const dims = xtermService.fitAddon.proposeDimensions();
|
|
102
|
+
if (dims) {
|
|
103
|
+
terminalSize = { cols: dims.cols, rows: dims.rows };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
onExecuteCommand('', terminalSize).catch(() => {
|
|
108
|
+
// Silently handle connection errors
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// Update reactive state even if not fully initialized
|
|
113
|
+
isInitialized = xtermService.isInitialized;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Process session lines and render to terminal
|
|
118
|
+
function processSessionLines() {
|
|
119
|
+
if (!isInitialized || !session) return;
|
|
120
|
+
|
|
121
|
+
const lines = session.lines;
|
|
122
|
+
const startIndex = Math.max(0, lastProcessedIndex + 1);
|
|
123
|
+
const totalLines = sessionLinesLength;
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
// Process new lines since last update
|
|
127
|
+
for (let i = startIndex; i < totalLines; i++) {
|
|
128
|
+
const line = lines[i];
|
|
129
|
+
|
|
130
|
+
// Track when command starts (input line detected)
|
|
131
|
+
if (line.type === 'input') {
|
|
132
|
+
sessionExecuting = true;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Track when command finishes (prompt-trigger detected)
|
|
136
|
+
if (line.type === 'prompt-trigger') {
|
|
137
|
+
// Only set to false if we were executing
|
|
138
|
+
if (sessionExecuting) {
|
|
139
|
+
sessionExecuting = false;
|
|
140
|
+
// IMPORTANT: Also update xterm service execution state
|
|
141
|
+
// This ensures prompt can be displayed properly
|
|
142
|
+
xtermService.setExecuting(false);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
xtermService.renderLine(line);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
lastProcessedIndex = totalLines - 1;
|
|
150
|
+
|
|
151
|
+
// Auto scroll to bottom only if user is near the bottom
|
|
152
|
+
// This prevents disrupting user's reading when they scroll up
|
|
153
|
+
requestAnimationFrame(() => {
|
|
154
|
+
xtermService.scrollToBottomIfNearEnd();
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Handle resize events
|
|
159
|
+
function setupResizeHandling() {
|
|
160
|
+
const handleResize = () => {
|
|
161
|
+
setTimeout(() => {
|
|
162
|
+
xtermService.fit(session?.id);
|
|
163
|
+
}, 100);
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
window.addEventListener('resize', handleResize);
|
|
167
|
+
|
|
168
|
+
// Also fit when container size might change
|
|
169
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
170
|
+
handleResize();
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (terminalContainer) {
|
|
174
|
+
resizeObserver.observe(terminalContainer);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return () => {
|
|
178
|
+
window.removeEventListener('resize', handleResize);
|
|
179
|
+
resizeObserver.disconnect();
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Handle right-click copy/paste functionality
|
|
184
|
+
function setupRightClickCopy() {
|
|
185
|
+
if (!terminalContainer || !xtermService.terminal) return;
|
|
186
|
+
|
|
187
|
+
const handleRightClick = async (event: MouseEvent) => {
|
|
188
|
+
event.preventDefault(); // Prevent default context menu
|
|
189
|
+
|
|
190
|
+
// Get selected text from xterm.js
|
|
191
|
+
const selectedText = xtermService.getSelectedText();
|
|
192
|
+
|
|
193
|
+
if (selectedText && selectedText.trim()) {
|
|
194
|
+
// Copy selected text to clipboard
|
|
195
|
+
try {
|
|
196
|
+
await navigator.clipboard.writeText(selectedText);
|
|
197
|
+
|
|
198
|
+
// Clear selection after copy (like most terminals do)
|
|
199
|
+
xtermService.clearSelection();
|
|
200
|
+
|
|
201
|
+
// Show brief visual feedback
|
|
202
|
+
showCopyFeedback();
|
|
203
|
+
} catch (err) {
|
|
204
|
+
}
|
|
205
|
+
} else {
|
|
206
|
+
// No text selected - paste from clipboard instead
|
|
207
|
+
try {
|
|
208
|
+
const clipboardText = await navigator.clipboard.readText();
|
|
209
|
+
if (clipboardText && clipboardText.trim() && xtermService.isReady) {
|
|
210
|
+
|
|
211
|
+
// Use terminal's built-in paste functionality
|
|
212
|
+
// This simulates typing each character through the input handler
|
|
213
|
+
if ((xtermService as any).inputHandler) {
|
|
214
|
+
// Process each character through the input handler
|
|
215
|
+
for (const char of clipboardText) {
|
|
216
|
+
// Skip newlines - let user decide when to execute
|
|
217
|
+
if (char !== '\n' && char !== '\r') {
|
|
218
|
+
(xtermService as any).inputHandler(char);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Show brief visual feedback
|
|
224
|
+
showPasteFeedback();
|
|
225
|
+
}
|
|
226
|
+
} catch {
|
|
227
|
+
// paste not supported
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Add right-click event listener to terminal container
|
|
233
|
+
terminalContainer.addEventListener('contextmenu', handleRightClick);
|
|
234
|
+
|
|
235
|
+
return () => {
|
|
236
|
+
terminalContainer?.removeEventListener('contextmenu', handleRightClick);
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Show brief visual feedback for copy action
|
|
241
|
+
function showCopyFeedback() {
|
|
242
|
+
if (!terminalContainer) return;
|
|
243
|
+
|
|
244
|
+
// Create temporary feedback element
|
|
245
|
+
const feedback = document.createElement('div');
|
|
246
|
+
feedback.textContent = 'Copied!';
|
|
247
|
+
feedback.style.cssText = `
|
|
248
|
+
position: absolute;
|
|
249
|
+
top: 10px;
|
|
250
|
+
right: 10px;
|
|
251
|
+
background: rgb(34 197 94 / 0.9);
|
|
252
|
+
color: white;
|
|
253
|
+
padding: 4px 8px;
|
|
254
|
+
border-radius: 4px;
|
|
255
|
+
font-size: 12px;
|
|
256
|
+
font-family: system-ui, sans-serif;
|
|
257
|
+
z-index: 1000;
|
|
258
|
+
pointer-events: none;
|
|
259
|
+
`;
|
|
260
|
+
|
|
261
|
+
terminalContainer.style.position = 'relative';
|
|
262
|
+
terminalContainer.appendChild(feedback);
|
|
263
|
+
|
|
264
|
+
// Remove feedback after 1 second
|
|
265
|
+
setTimeout(() => {
|
|
266
|
+
if (feedback.parentNode) {
|
|
267
|
+
feedback.parentNode.removeChild(feedback);
|
|
268
|
+
}
|
|
269
|
+
}, 1000);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Show brief visual feedback for paste action
|
|
273
|
+
function showPasteFeedback() {
|
|
274
|
+
if (!terminalContainer) return;
|
|
275
|
+
|
|
276
|
+
// Create temporary feedback element
|
|
277
|
+
const feedback = document.createElement('div');
|
|
278
|
+
feedback.textContent = 'Pasted!';
|
|
279
|
+
feedback.style.cssText = `
|
|
280
|
+
position: absolute;
|
|
281
|
+
top: 10px;
|
|
282
|
+
right: 10px;
|
|
283
|
+
background: rgba(59, 130, 246, 0.9);
|
|
284
|
+
color: white;
|
|
285
|
+
padding: 4px 8px;
|
|
286
|
+
border-radius: 4px;
|
|
287
|
+
font-size: 12px;
|
|
288
|
+
font-family: system-ui, sans-serif;
|
|
289
|
+
z-index: 1000;
|
|
290
|
+
pointer-events: none;
|
|
291
|
+
`;
|
|
292
|
+
|
|
293
|
+
terminalContainer.style.position = 'relative';
|
|
294
|
+
terminalContainer.appendChild(feedback);
|
|
295
|
+
|
|
296
|
+
// Remove feedback after 1 second
|
|
297
|
+
setTimeout(() => {
|
|
298
|
+
if (feedback.parentNode) {
|
|
299
|
+
feedback.parentNode.removeChild(feedback);
|
|
300
|
+
}
|
|
301
|
+
}, 1000);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Handle theme changes
|
|
305
|
+
function setupThemeHandling() {
|
|
306
|
+
xtermService.updateTheme();
|
|
307
|
+
|
|
308
|
+
// Watch for theme changes
|
|
309
|
+
const observer = new MutationObserver(() => {
|
|
310
|
+
xtermService.updateTheme();
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
observer.observe(document.documentElement, {
|
|
314
|
+
attributes: true,
|
|
315
|
+
attributeFilter: ['class', 'data-theme']
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
return () => {
|
|
319
|
+
observer.disconnect();
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Reactive effects
|
|
324
|
+
$effect(() => {
|
|
325
|
+
// Initialize terminal when container is available
|
|
326
|
+
if (terminalContainer && !xtermService.isInitialized) {
|
|
327
|
+
// Small delay to ensure DOM is ready
|
|
328
|
+
setTimeout(() => {
|
|
329
|
+
initializeTerminal();
|
|
330
|
+
}, 10);
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Use $derived.by to properly track session.lines changes in Svelte 5
|
|
335
|
+
const sessionLinesLength = $derived.by(() => {
|
|
336
|
+
const length = session ? session.lines.length : 0;
|
|
337
|
+
return length;
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
$effect(() => {
|
|
341
|
+
// Process new session data when lines length changes
|
|
342
|
+
// Skip if we just restored from buffer to avoid duplicates
|
|
343
|
+
// Also skip if this is a session switch (lastSessionId !== session.id)
|
|
344
|
+
if (isInitialized && session && !justRestoredFromBuffer && lastSessionId === session.id) {
|
|
345
|
+
// Check if this is a clear operation (lines reduced and contains clear-screen marker)
|
|
346
|
+
const hasClearMarker = session.lines.length > 0 &&
|
|
347
|
+
session.lines[session.lines.length - 1].type === 'clear-screen';
|
|
348
|
+
|
|
349
|
+
if (hasClearMarker && sessionLinesLength < lastProcessedIndex) {
|
|
350
|
+
// This is a clear operation - handle it immediately
|
|
351
|
+
xtermService.clear();
|
|
352
|
+
lastProcessedIndex = -1;
|
|
353
|
+
|
|
354
|
+
// Process the clear marker
|
|
355
|
+
processSessionLines();
|
|
356
|
+
} else if (sessionLinesLength > lastProcessedIndex) {
|
|
357
|
+
// Normal case: process NEW lines
|
|
358
|
+
processSessionLines();
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
// Track session execution state changes
|
|
364
|
+
$effect(() => {
|
|
365
|
+
// Check if execution state changed from true to false (command finished)
|
|
366
|
+
// Also verify that terminal store agrees that execution is done
|
|
367
|
+
if (session && lastExecutingState && !sessionExecuting && !isExecuting) {
|
|
368
|
+
// Command just finished for this session
|
|
369
|
+
|
|
370
|
+
// Trigger prompt display for this session regardless of whether it's active
|
|
371
|
+
if (isInitialized && hasActiveProject) {
|
|
372
|
+
setTimeout(() => {
|
|
373
|
+
// Check if there's still an active stream before showing prompt
|
|
374
|
+
// This prevents race condition after refresh where execution state changes
|
|
375
|
+
// but stream is still being reconnected
|
|
376
|
+
if (!backgroundTerminalService.hasActiveStream(session.id)) {
|
|
377
|
+
xtermService.showPrompt();
|
|
378
|
+
isTerminalReady = true;
|
|
379
|
+
}
|
|
380
|
+
}, 200);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Update last state for next comparison
|
|
385
|
+
lastExecutingState = sessionExecuting;
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
// Save terminal buffer before switching away
|
|
389
|
+
function saveCurrentBuffer(sessionId: string) {
|
|
390
|
+
// No longer saving buffer since we re-render from session lines
|
|
391
|
+
// This preserves ANSI colors when switching tabs
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Restore terminal buffer when switching to a session
|
|
395
|
+
function restoreSessionBuffer(sessionId: string) {
|
|
396
|
+
if (!isInitialized || !sessionId) return;
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
// ALWAYS re-render from session lines to preserve ANSI colors
|
|
400
|
+
// This ensures colors are preserved when switching tabs
|
|
401
|
+
|
|
402
|
+
// Clear terminal first
|
|
403
|
+
xtermService.clear();
|
|
404
|
+
|
|
405
|
+
// Don't set the flag here - it's already set in the session change handler
|
|
406
|
+
// justRestoredFromBuffer = true;
|
|
407
|
+
|
|
408
|
+
// Reset processed index to re-render all lines
|
|
409
|
+
lastProcessedIndex = -1;
|
|
410
|
+
|
|
411
|
+
// Re-render all lines from the session (with ANSI colors intact)
|
|
412
|
+
if (session && session.lines.length > 0) {
|
|
413
|
+
// Check if the session was cleared (has clear-screen marker)
|
|
414
|
+
const hasClearMarker = session.lines.some(line => line.type === 'clear-screen');
|
|
415
|
+
|
|
416
|
+
if (hasClearMarker) {
|
|
417
|
+
// Find the last clear-screen marker
|
|
418
|
+
let lastClearIndex = -1;
|
|
419
|
+
for (let i = session.lines.length - 1; i >= 0; i--) {
|
|
420
|
+
if (session.lines[i].type === 'clear-screen') {
|
|
421
|
+
lastClearIndex = i;
|
|
422
|
+
break;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Only render lines after the last clear
|
|
427
|
+
// This ensures the terminal stays clear when switching tabs
|
|
428
|
+
for (let i = lastClearIndex; i < session.lines.length; i++) {
|
|
429
|
+
const line = session.lines[i];
|
|
430
|
+
|
|
431
|
+
// Track execution state while re-rendering
|
|
432
|
+
if (line.type === 'input') {
|
|
433
|
+
sessionExecuting = true;
|
|
434
|
+
}
|
|
435
|
+
if (line.type === 'prompt-trigger') {
|
|
436
|
+
sessionExecuting = false;
|
|
437
|
+
// Update xterm service execution state for proper prompt display
|
|
438
|
+
xtermService.setExecuting(false);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Render line with original ANSI codes preserved
|
|
442
|
+
// Pass isRestoring=true to properly handle input lines
|
|
443
|
+
xtermService.renderLine(line, true);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// After restoring, reset execution state for proper prompt display
|
|
447
|
+
sessionExecuting = false;
|
|
448
|
+
xtermService.setExecuting(false);
|
|
449
|
+
} else {
|
|
450
|
+
// No clear marker - render all lines as before
|
|
451
|
+
for (let i = 0; i < session.lines.length; i++) {
|
|
452
|
+
const line = session.lines[i];
|
|
453
|
+
|
|
454
|
+
// Track execution state while re-rendering
|
|
455
|
+
if (line.type === 'input') {
|
|
456
|
+
sessionExecuting = true;
|
|
457
|
+
}
|
|
458
|
+
if (line.type === 'prompt-trigger') {
|
|
459
|
+
sessionExecuting = false;
|
|
460
|
+
// Update xterm service execution state for proper prompt display
|
|
461
|
+
xtermService.setExecuting(false);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Render line with original ANSI codes preserved
|
|
465
|
+
// Pass isRestoring=true to properly handle input lines
|
|
466
|
+
xtermService.renderLine(line, true);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// After restoring all lines, ensure execution state is correct
|
|
471
|
+
// If the last line wasn't a prompt-trigger, reset execution state
|
|
472
|
+
// This ensures directory path is always shown for input lines during restore
|
|
473
|
+
sessionExecuting = false;
|
|
474
|
+
xtermService.setExecuting(false);
|
|
475
|
+
|
|
476
|
+
// Update last processed index to current length
|
|
477
|
+
lastProcessedIndex = session.lines.length - 1;
|
|
478
|
+
|
|
479
|
+
// Only scroll to bottom if there's content after clear
|
|
480
|
+
// If it was just cleared, keep at top
|
|
481
|
+
if (!hasClearMarker || session.lines.length > 1) {
|
|
482
|
+
requestAnimationFrame(() => {
|
|
483
|
+
xtermService.scrollToBottom();
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
// Reset flag after rendering is complete
|
|
489
|
+
// Use longer timeout to ensure effect doesn't trigger during restoration
|
|
490
|
+
setTimeout(() => {
|
|
491
|
+
justRestoredFromBuffer = false;
|
|
492
|
+
}, 200);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
$effect(() => {
|
|
496
|
+
// Handle session changes
|
|
497
|
+
if (isInitialized && session) {
|
|
498
|
+
// Check if session ID changed
|
|
499
|
+
if (lastSessionId !== session.id) {
|
|
500
|
+
// Save buffer for previous session before switching
|
|
501
|
+
if (lastSessionId) {
|
|
502
|
+
saveCurrentBuffer(lastSessionId);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Set flag BEFORE updating lastSessionId to prevent double processing
|
|
506
|
+
justRestoredFromBuffer = true;
|
|
507
|
+
|
|
508
|
+
lastSessionId = session.id;
|
|
509
|
+
promptShownForSession = false; // Reset prompt flag for new session
|
|
510
|
+
isAfterClearOperation = false;
|
|
511
|
+
sessionExecuting = false; // Reset execution state for new session
|
|
512
|
+
lastExecutingState = false; // Reset last state tracking
|
|
513
|
+
|
|
514
|
+
// Update command history for the new session
|
|
515
|
+
xtermService.updateCommandHistory(session.commandHistory);
|
|
516
|
+
|
|
517
|
+
// IMPORTANT: Update context with new session directory BEFORE restore
|
|
518
|
+
// This ensures sessionDirectory is correct when restoring input lines
|
|
519
|
+
xtermService.updateContext(hasActiveProject, projectPath, session.directory);
|
|
520
|
+
// Set session ID for active stream checking
|
|
521
|
+
xtermService.setSessionId(session.id);
|
|
522
|
+
|
|
523
|
+
// Restore terminal buffer for the new session
|
|
524
|
+
// This will also update lastProcessedIndex
|
|
525
|
+
restoreSessionBuffer(session.id);
|
|
526
|
+
|
|
527
|
+
// Mark initial mount as complete after first restore
|
|
528
|
+
if (isInitialMount) {
|
|
529
|
+
setTimeout(() => {
|
|
530
|
+
isInitialMount = false;
|
|
531
|
+
}, 500); // Give enough time for restoration to complete
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// CRITICAL: Auto-connect to PTY for session that hasn't connected yet
|
|
535
|
+
// This ensures PTY session is created on server for:
|
|
536
|
+
// 1. New tabs (session.lines.length === 0)
|
|
537
|
+
// 2. Restored sessions after browser refresh that haven't connected yet
|
|
538
|
+
const hasNotConnected = !connectedSessions.has(session.id);
|
|
539
|
+
if (hasNotConnected && hasActiveProject && onExecuteCommand) {
|
|
540
|
+
// Session hasn't connected to PTY yet - create connection
|
|
541
|
+
connectedSessions.add(session.id);
|
|
542
|
+
setTimeout(() => {
|
|
543
|
+
// Get terminal dimensions for PTY size sync
|
|
544
|
+
let terminalSize: { cols: number; rows: number } | undefined;
|
|
545
|
+
if (xtermService.fitAddon && xtermService.terminal) {
|
|
546
|
+
xtermService.fitAddon.fit();
|
|
547
|
+
const dims = xtermService.fitAddon.proposeDimensions();
|
|
548
|
+
if (dims) {
|
|
549
|
+
terminalSize = { cols: dims.cols, rows: dims.rows };
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
onExecuteCommand('', terminalSize).catch(() => {
|
|
553
|
+
// Silently handle connection errors
|
|
554
|
+
});
|
|
555
|
+
}, 200); // Small delay to ensure session is ready
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Check if we need to show prompt after restore
|
|
559
|
+
// Also check isExecuting prop from terminal store to avoid showing prompt during active execution
|
|
560
|
+
if (!sessionExecuting && !isExecuting) {
|
|
561
|
+
// IMPORTANT: Add delay to allow stream reconnection to complete
|
|
562
|
+
// This prevents prompt from appearing before reconnected output
|
|
563
|
+
setTimeout(() => {
|
|
564
|
+
// Check again if there's an active stream after the delay
|
|
565
|
+
const hasActiveStream = session ? backgroundTerminalService.hasActiveStream(session.id) : false;
|
|
566
|
+
if (!hasActiveStream) {
|
|
567
|
+
if (session.lines.length > 0) {
|
|
568
|
+
// Session has history - check if we need prompt
|
|
569
|
+
const lastLine = session.lines[session.lines.length - 1];
|
|
570
|
+
if (lastLine.type === 'prompt-trigger' || lastLine.type === 'output') {
|
|
571
|
+
xtermService.showPrompt();
|
|
572
|
+
isTerminalReady = true;
|
|
573
|
+
}
|
|
574
|
+
} else {
|
|
575
|
+
// Empty session - show prompt immediately
|
|
576
|
+
// (prompt will be displayed before PTY connection completes)
|
|
577
|
+
xtermService.showPrompt();
|
|
578
|
+
isTerminalReady = true;
|
|
579
|
+
}
|
|
580
|
+
} else {
|
|
581
|
+
// Active stream detected, mark as ready without prompt
|
|
582
|
+
isTerminalReady = true;
|
|
583
|
+
}
|
|
584
|
+
}, 500); // Delay to ensure stream reconnection completes
|
|
585
|
+
}
|
|
586
|
+
} else if (lastProcessedIndex >= sessionLinesLength && sessionLinesLength === 0) {
|
|
587
|
+
// Only clear if lines were actually removed (shouldn't happen anymore)
|
|
588
|
+
// With new approach, clear command adds a clear-screen marker instead
|
|
589
|
+
isAfterClearOperation = true;
|
|
590
|
+
xtermService.clear();
|
|
591
|
+
lastProcessedIndex = -1;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
$effect(() => {
|
|
597
|
+
// Handle execution state changes and context updates
|
|
598
|
+
if (isInitialized) {
|
|
599
|
+
// Update context whenever props change
|
|
600
|
+
xtermService.updateContext(hasActiveProject, projectPath, session?.directory);
|
|
601
|
+
// Update session ID for active stream checking
|
|
602
|
+
if (session) {
|
|
603
|
+
xtermService.setSessionId(session.id);
|
|
604
|
+
}
|
|
605
|
+
// Use session-specific execution state instead of global
|
|
606
|
+
xtermService.setExecuting(sessionExecuting);
|
|
607
|
+
|
|
608
|
+
// Update ready state only if becoming ready (don't go back to false during execution)
|
|
609
|
+
// This prevents DOM blocking from re-activating during normal operation
|
|
610
|
+
const serviceReady = xtermService.isReady;
|
|
611
|
+
const serviceInit = xtermService.isInitialized;
|
|
612
|
+
|
|
613
|
+
if (serviceReady) {
|
|
614
|
+
isTerminalReady = true;
|
|
615
|
+
}
|
|
616
|
+
// Only set to false if terminal is genuinely not initialized
|
|
617
|
+
else if (!serviceInit) {
|
|
618
|
+
isTerminalReady = false;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
// Update command history whenever it changes
|
|
624
|
+
$effect(() => {
|
|
625
|
+
if (isInitialized && session?.commandHistory) {
|
|
626
|
+
xtermService.updateCommandHistory(session.commandHistory);
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// Setup event handlers
|
|
631
|
+
$effect(() => {
|
|
632
|
+
if (!isInitialized) return;
|
|
633
|
+
|
|
634
|
+
const cleanupResize = setupResizeHandling();
|
|
635
|
+
const cleanupTheme = setupThemeHandling();
|
|
636
|
+
const cleanupRightClick = setupRightClickCopy();
|
|
637
|
+
|
|
638
|
+
return () => {
|
|
639
|
+
cleanupResize();
|
|
640
|
+
cleanupTheme();
|
|
641
|
+
cleanupRightClick?.();
|
|
642
|
+
};
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
// Cleanup on destroy
|
|
646
|
+
onDestroy(() => {
|
|
647
|
+
// Remove keyboard event listener (must match capture phase)
|
|
648
|
+
if (keyboardEventHandler && xtermService.terminal?.element) {
|
|
649
|
+
xtermService.terminal.element.removeEventListener('keydown', keyboardEventHandler, true);
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// CRITICAL: Clean up terminal service WebSocket listeners for the current session
|
|
653
|
+
// This prevents stale listeners from accumulating when XTerm is destroyed
|
|
654
|
+
// during project switches, which would cause duplicate output processing
|
|
655
|
+
if (lastSessionId) {
|
|
656
|
+
terminalService.cleanupListeners(lastSessionId);
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
xtermService.dispose();
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
// Expose methods for parent component (implementing XTermMethods)
|
|
663
|
+
export function clear() {
|
|
664
|
+
xtermService.clear();
|
|
665
|
+
lastProcessedIndex = -1;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export function fit() {
|
|
669
|
+
xtermService.fit(session?.id);
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
export function scrollToBottom() {
|
|
673
|
+
xtermService.scrollToBottom();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export function scrollToBottomIfNearEnd() {
|
|
677
|
+
xtermService.scrollToBottomIfNearEnd();
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
export function writeData(data: string) {
|
|
681
|
+
xtermService.writeData(data);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export function getSelectedText(): string {
|
|
685
|
+
return xtermService.getSelectedText();
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
export function clearSelection() {
|
|
689
|
+
xtermService.clearSelection();
|
|
690
|
+
}
|
|
691
|
+
</script>
|
|
692
|
+
|
|
693
|
+
<!-- Pure xterm.js terminal container -->
|
|
694
|
+
<div
|
|
695
|
+
bind:this={terminalContainer}
|
|
696
|
+
class="w-full h-full overflow-hidden bg-slate-50 dark:bg-slate-900/70 {className} select-none"
|
|
697
|
+
style="transition: opacity 0.2s ease-in-out; user-select: text;"
|
|
698
|
+
role="textbox"
|
|
699
|
+
tabindex="0"
|
|
700
|
+
aria-label="Interactive terminal"
|
|
701
|
+
title="Right-click selected text to copy"
|
|
702
|
+
>
|
|
703
|
+
{#if !browser}
|
|
704
|
+
<!-- SSR fallback -->
|
|
705
|
+
<div class="flex items-center justify-center h-full">
|
|
706
|
+
<div class="text-slate-600 dark:text-slate-400 text-sm">
|
|
707
|
+
Loading terminal...
|
|
708
|
+
</div>
|
|
709
|
+
</div>
|
|
710
|
+
{:else if !isInitialized}
|
|
711
|
+
<!-- Browser loading state -->
|
|
712
|
+
<div class="flex items-center justify-center h-full">
|
|
713
|
+
<div class="text-slate-600 dark:text-slate-400 text-sm">
|
|
714
|
+
Initializing terminal...
|
|
715
|
+
</div>
|
|
716
|
+
</div>
|
|
717
|
+
{/if}
|
|
718
|
+
</div>
|
|
719
|
+
|
|
720
|
+
<style>
|
|
721
|
+
/* Custom xterm.js styling */
|
|
722
|
+
:global(.xterm) {
|
|
723
|
+
padding: 0.5rem !important;
|
|
724
|
+
background: transparent !important;
|
|
725
|
+
height: 100% !important;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
:global(.xterm .xterm-viewport) {
|
|
729
|
+
background: transparent !important;
|
|
730
|
+
height: 100% !important;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
:global(.xterm .xterm-screen) {
|
|
734
|
+
background: transparent !important;
|
|
735
|
+
height: 100% !important;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
:global(.xterm .xterm-helper-textarea) {
|
|
739
|
+
height: 100% !important;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
/* Text selection styling */
|
|
743
|
+
:global(.xterm .xterm-selection div) {
|
|
744
|
+
background-color: #22c55e !important; /* green-500 */
|
|
745
|
+
opacity: 0.3 !important;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/* Better text selection on dark theme */
|
|
749
|
+
:global(.dark .xterm .xterm-selection div) {
|
|
750
|
+
background-color: #4ade80 !important; /* green-400 */
|
|
751
|
+
opacity: 0.4 !important;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/* Scrollbar styling to match application theme */
|
|
755
|
+
:global(.xterm .xterm-viewport::-webkit-scrollbar) {
|
|
756
|
+
width: 6px;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
:global(.xterm .xterm-viewport::-webkit-scrollbar-track) {
|
|
760
|
+
background: transparent;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
:global(.xterm .xterm-viewport::-webkit-scrollbar-thumb) {
|
|
764
|
+
background-color: rgb(148 163 184 / 0.3); /* slate-400 with opacity */
|
|
765
|
+
border-radius: 3px;
|
|
766
|
+
transition: background-color 0.2s ease;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
:global(.dark .xterm .xterm-viewport::-webkit-scrollbar-thumb) {
|
|
770
|
+
background-color: rgb(71 85 105 / 0.3); /* slate-600 with opacity */
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
:global(.xterm .xterm-viewport::-webkit-scrollbar-thumb:hover) {
|
|
774
|
+
background-color: rgb(148 163 184 / 0.6); /* slate-400 with higher opacity */
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
:global(.dark .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover) {
|
|
778
|
+
background-color: rgb(71 85 105 / 0.6); /* slate-600 with higher opacity */
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/* Ensure proper focus styling */
|
|
782
|
+
:global(.xterm.focus .xterm-cursor) {
|
|
783
|
+
background-color: #22c55e !important; /* green-500 */
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
:global(.xterm:not(.focus) .xterm-cursor) {
|
|
787
|
+
background-color: #52525b !important; /* zinc-600 */
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/* Responsive font size adjustments */
|
|
791
|
+
@media (max-width: 640px) {
|
|
792
|
+
:global(.xterm) {
|
|
793
|
+
font-size: 12px !important;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
</style>
|