Showing preview only (2,625K chars total). Download the full file or copy to clipboard to get everything.
Repository: siteboon/claudecodeui
Branch: main
Commit: 08a6653b3813
Files: 448
Total size: 2.4 MB
Directory structure:
gitextract_zr639i5w/
├── .github/
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ └── feature_request.md
│ └── workflows/
│ └── discord-release.yml
├── .gitignore
├── .gitmodules
├── .husky/
│ ├── commit-msg
│ └── pre-commit
├── .npmignore
├── .nvmrc
├── .release-it.json
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.de.md
├── README.ja.md
├── README.ko.md
├── README.md
├── README.ru.md
├── README.zh-CN.md
├── commitlint.config.js
├── eslint.config.js
├── index.html
├── package.json
├── postcss.config.js
├── public/
│ ├── api-docs.html
│ ├── clear-cache.html
│ ├── convert-icons.md
│ ├── generate-icons.js
│ ├── manifest.json
│ └── sw.js
├── release.sh
├── scripts/
│ └── fix-node-pty.js
├── server/
│ ├── claude-sdk.js
│ ├── cli.js
│ ├── constants/
│ │ └── config.js
│ ├── cursor-cli.js
│ ├── database/
│ │ ├── db.js
│ │ └── init.sql
│ ├── gemini-cli.js
│ ├── gemini-response-handler.js
│ ├── index.js
│ ├── load-env.js
│ ├── middleware/
│ │ └── auth.js
│ ├── openai-codex.js
│ ├── projects.js
│ ├── providers/
│ │ ├── claude/
│ │ │ └── adapter.js
│ │ ├── codex/
│ │ │ └── adapter.js
│ │ ├── cursor/
│ │ │ └── adapter.js
│ │ ├── gemini/
│ │ │ └── adapter.js
│ │ ├── registry.js
│ │ ├── types.js
│ │ └── utils.js
│ ├── routes/
│ │ ├── agent.js
│ │ ├── auth.js
│ │ ├── cli-auth.js
│ │ ├── codex.js
│ │ ├── commands.js
│ │ ├── cursor.js
│ │ ├── gemini.js
│ │ ├── git.js
│ │ ├── mcp-utils.js
│ │ ├── mcp.js
│ │ ├── messages.js
│ │ ├── plugins.js
│ │ ├── projects.js
│ │ ├── settings.js
│ │ ├── taskmaster.js
│ │ └── user.js
│ ├── services/
│ │ ├── notification-orchestrator.js
│ │ └── vapid-keys.js
│ ├── sessionManager.js
│ └── utils/
│ ├── commandParser.js
│ ├── frontmatter.js
│ ├── gitConfig.js
│ ├── mcp-detector.js
│ ├── plugin-loader.js
│ ├── plugin-process-manager.js
│ └── taskmaster-websocket.js
├── shared/
│ ├── modelConstants.js
│ └── networkHosts.js
├── src/
│ ├── App.tsx
│ ├── components/
│ │ ├── app/
│ │ │ ├── AppContent.tsx
│ │ │ └── MobileNav.tsx
│ │ ├── auth/
│ │ │ ├── constants.ts
│ │ │ ├── context/
│ │ │ │ └── AuthContext.tsx
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ ├── utils.ts
│ │ │ └── view/
│ │ │ ├── AuthErrorAlert.tsx
│ │ │ ├── AuthInputField.tsx
│ │ │ ├── AuthLoadingScreen.tsx
│ │ │ ├── AuthScreenLayout.tsx
│ │ │ ├── LoginForm.tsx
│ │ │ ├── ProtectedRoute.tsx
│ │ │ └── SetupForm.tsx
│ │ ├── chat/
│ │ │ ├── constants/
│ │ │ │ └── thinkingModes.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useChatComposerState.ts
│ │ │ │ ├── useChatMessages.ts
│ │ │ │ ├── useChatProviderState.ts
│ │ │ │ ├── useChatRealtimeHandlers.ts
│ │ │ │ ├── useChatSessionState.ts
│ │ │ │ ├── useFileMentions.tsx
│ │ │ │ └── useSlashCommands.ts
│ │ │ ├── tools/
│ │ │ │ ├── README.md
│ │ │ │ ├── ToolRenderer.tsx
│ │ │ │ ├── components/
│ │ │ │ │ ├── CollapsibleDisplay.tsx
│ │ │ │ │ ├── CollapsibleSection.tsx
│ │ │ │ │ ├── ContentRenderers/
│ │ │ │ │ │ ├── FileListContent.tsx
│ │ │ │ │ │ ├── MarkdownContent.tsx
│ │ │ │ │ │ ├── QuestionAnswerContent.tsx
│ │ │ │ │ │ ├── TaskListContent.tsx
│ │ │ │ │ │ ├── TextContent.tsx
│ │ │ │ │ │ ├── TodoList.tsx
│ │ │ │ │ │ ├── TodoListContent.tsx
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── InteractiveRenderers/
│ │ │ │ │ │ ├── AskUserQuestionPanel.tsx
│ │ │ │ │ │ └── index.ts
│ │ │ │ │ ├── OneLineDisplay.tsx
│ │ │ │ │ ├── SubagentContainer.tsx
│ │ │ │ │ ├── ToolDiffViewer.tsx
│ │ │ │ │ └── index.ts
│ │ │ │ ├── configs/
│ │ │ │ │ ├── permissionPanelRegistry.ts
│ │ │ │ │ └── toolConfigs.ts
│ │ │ │ └── index.ts
│ │ │ ├── types/
│ │ │ │ └── types.ts
│ │ │ ├── utils/
│ │ │ │ ├── chatFormatting.ts
│ │ │ │ ├── chatPermissions.ts
│ │ │ │ ├── chatStorage.ts
│ │ │ │ ├── messageKeys.ts
│ │ │ │ └── messageTransforms.ts
│ │ │ └── view/
│ │ │ ├── ChatInterface.tsx
│ │ │ └── subcomponents/
│ │ │ ├── AssistantThinkingIndicator.tsx
│ │ │ ├── ChatComposer.tsx
│ │ │ ├── ChatInputControls.tsx
│ │ │ ├── ChatMessagesPane.tsx
│ │ │ ├── ClaudeStatus.tsx
│ │ │ ├── CommandMenu.tsx
│ │ │ ├── ImageAttachment.tsx
│ │ │ ├── Markdown.tsx
│ │ │ ├── MessageComponent.tsx
│ │ │ ├── MessageCopyControl.tsx
│ │ │ ├── PermissionRequestsBanner.tsx
│ │ │ ├── ProviderSelectionEmptyState.tsx
│ │ │ ├── ThinkingModeSelector.tsx
│ │ │ └── TokenUsagePie.tsx
│ │ ├── code-editor/
│ │ │ ├── constants/
│ │ │ │ └── settings.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useCodeEditorDocument.ts
│ │ │ │ ├── useCodeEditorSettings.ts
│ │ │ │ ├── useEditorKeyboardShortcuts.ts
│ │ │ │ └── useEditorSidebar.ts
│ │ │ ├── types/
│ │ │ │ └── types.ts
│ │ │ ├── utils/
│ │ │ │ ├── binaryFile.ts
│ │ │ │ ├── editorExtensions.ts
│ │ │ │ ├── editorStyles.ts
│ │ │ │ └── editorToolbarPanel.ts
│ │ │ └── view/
│ │ │ ├── CodeEditor.tsx
│ │ │ ├── EditorSidebar.tsx
│ │ │ └── subcomponents/
│ │ │ ├── CodeEditorBinaryFile.tsx
│ │ │ ├── CodeEditorFooter.tsx
│ │ │ ├── CodeEditorHeader.tsx
│ │ │ ├── CodeEditorLoadingState.tsx
│ │ │ ├── CodeEditorSurface.tsx
│ │ │ └── markdown/
│ │ │ ├── MarkdownCodeBlock.tsx
│ │ │ └── MarkdownPreview.tsx
│ │ ├── file-tree/
│ │ │ ├── constants/
│ │ │ │ ├── constants.ts
│ │ │ │ └── fileIcons.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useExpandedDirectories.ts
│ │ │ │ ├── useFileTreeData.ts
│ │ │ │ ├── useFileTreeOperations.ts
│ │ │ │ ├── useFileTreeSearch.ts
│ │ │ │ ├── useFileTreeUpload.ts
│ │ │ │ └── useFileTreeViewMode.ts
│ │ │ ├── types/
│ │ │ │ └── types.ts
│ │ │ ├── utils/
│ │ │ │ └── fileTreeUtils.ts
│ │ │ └── view/
│ │ │ ├── FileContextMenu.tsx
│ │ │ ├── FileTree.tsx
│ │ │ ├── FileTreeBody.tsx
│ │ │ ├── FileTreeDetailedColumns.tsx
│ │ │ ├── FileTreeEmptyState.tsx
│ │ │ ├── FileTreeHeader.tsx
│ │ │ ├── FileTreeList.tsx
│ │ │ ├── FileTreeLoadingState.tsx
│ │ │ ├── FileTreeNode.tsx
│ │ │ └── ImageViewer.tsx
│ │ ├── git-panel/
│ │ │ ├── constants/
│ │ │ │ └── constants.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useGitPanelController.ts
│ │ │ │ ├── useRevertLocalCommit.ts
│ │ │ │ └── useSelectedProvider.ts
│ │ │ ├── types/
│ │ │ │ └── types.ts
│ │ │ ├── utils/
│ │ │ │ └── gitPanelUtils.ts
│ │ │ └── view/
│ │ │ ├── GitPanel.tsx
│ │ │ ├── GitPanelHeader.tsx
│ │ │ ├── GitRepositoryErrorState.tsx
│ │ │ ├── GitViewTabs.tsx
│ │ │ ├── branches/
│ │ │ │ └── BranchesView.tsx
│ │ │ ├── changes/
│ │ │ │ ├── ChangesView.tsx
│ │ │ │ ├── CommitComposer.tsx
│ │ │ │ ├── FileChangeItem.tsx
│ │ │ │ ├── FileChangeList.tsx
│ │ │ │ ├── FileSelectionControls.tsx
│ │ │ │ └── FileStatusLegend.tsx
│ │ │ ├── history/
│ │ │ │ ├── CommitHistoryItem.tsx
│ │ │ │ └── HistoryView.tsx
│ │ │ ├── modals/
│ │ │ │ ├── ConfirmActionModal.tsx
│ │ │ │ └── NewBranchModal.tsx
│ │ │ └── shared/
│ │ │ └── GitDiffViewer.tsx
│ │ ├── llm-logo-provider/
│ │ │ ├── ClaudeLogo.tsx
│ │ │ ├── CodexLogo.tsx
│ │ │ ├── CursorLogo.tsx
│ │ │ ├── GeminiLogo.tsx
│ │ │ └── SessionProviderLogo.tsx
│ │ ├── main-content/
│ │ │ ├── hooks/
│ │ │ │ └── useMobileMenuHandlers.ts
│ │ │ ├── types/
│ │ │ │ └── types.ts
│ │ │ └── view/
│ │ │ ├── ErrorBoundary.tsx
│ │ │ ├── MainContent.tsx
│ │ │ └── subcomponents/
│ │ │ ├── MainContentHeader.tsx
│ │ │ ├── MainContentStateView.tsx
│ │ │ ├── MainContentTabSwitcher.tsx
│ │ │ ├── MainContentTitle.tsx
│ │ │ └── MobileMenuButton.tsx
│ │ ├── mic-button/
│ │ │ ├── constants/
│ │ │ │ └── constants.ts
│ │ │ ├── data/
│ │ │ │ └── whisper.ts
│ │ │ ├── hooks/
│ │ │ │ └── useMicButtonController.ts
│ │ │ ├── types/
│ │ │ │ └── types.ts
│ │ │ └── view/
│ │ │ ├── MicButton.tsx
│ │ │ └── MicButtonView.tsx
│ │ ├── onboarding/
│ │ │ └── view/
│ │ │ ├── Onboarding.tsx
│ │ │ ├── subcomponents/
│ │ │ │ ├── AgentConnectionCard.tsx
│ │ │ │ ├── AgentConnectionsStep.tsx
│ │ │ │ ├── GitConfigurationStep.tsx
│ │ │ │ └── OnboardingStepProgress.tsx
│ │ │ ├── types.ts
│ │ │ └── utils.ts
│ │ ├── plugins/
│ │ │ └── view/
│ │ │ ├── PluginIcon.tsx
│ │ │ ├── PluginSettingsTab.tsx
│ │ │ └── PluginTabContent.tsx
│ │ ├── prd-editor/
│ │ │ ├── PRDEditor.tsx
│ │ │ ├── constants.ts
│ │ │ ├── hooks/
│ │ │ │ ├── usePrdDocument.ts
│ │ │ │ ├── usePrdKeyboardShortcuts.ts
│ │ │ │ ├── usePrdRegistry.ts
│ │ │ │ └── usePrdSave.ts
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ ├── utils/
│ │ │ │ └── fileName.ts
│ │ │ └── view/
│ │ │ ├── GenerateTasksModal.tsx
│ │ │ ├── OverwriteConfirmModal.tsx
│ │ │ ├── PrdEditorBody.tsx
│ │ │ ├── PrdEditorFooter.tsx
│ │ │ ├── PrdEditorHeader.tsx
│ │ │ ├── PrdEditorLoadingState.tsx
│ │ │ └── PrdEditorWorkspace.tsx
│ │ ├── project-creation-wizard/
│ │ │ ├── ProjectCreationWizard.tsx
│ │ │ ├── components/
│ │ │ │ ├── ErrorBanner.tsx
│ │ │ │ ├── FolderBrowserModal.tsx
│ │ │ │ ├── GithubAuthenticationCard.tsx
│ │ │ │ ├── StepConfiguration.tsx
│ │ │ │ ├── StepReview.tsx
│ │ │ │ ├── StepTypeSelection.tsx
│ │ │ │ ├── WizardFooter.tsx
│ │ │ │ ├── WizardProgress.tsx
│ │ │ │ └── WorkspacePathField.tsx
│ │ │ ├── data/
│ │ │ │ └── workspaceApi.ts
│ │ │ ├── hooks/
│ │ │ │ └── useGithubTokens.ts
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── utils/
│ │ │ └── pathUtils.ts
│ │ ├── provider-auth/
│ │ │ ├── types.ts
│ │ │ └── view/
│ │ │ └── ProviderLoginModal.tsx
│ │ ├── quick-settings-panel/
│ │ │ ├── constants.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useQuickSettingsDrag.ts
│ │ │ │ └── useWhisperMode.ts
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ └── view/
│ │ │ ├── QuickSettingsContent.tsx
│ │ │ ├── QuickSettingsHandle.tsx
│ │ │ ├── QuickSettingsPanelHeader.tsx
│ │ │ ├── QuickSettingsPanelView.tsx
│ │ │ ├── QuickSettingsSection.tsx
│ │ │ ├── QuickSettingsToggleRow.tsx
│ │ │ └── QuickSettingsWhisperSection.tsx
│ │ ├── settings/
│ │ │ ├── constants/
│ │ │ │ └── constants.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useCredentialsSettings.ts
│ │ │ │ ├── useGitSettings.ts
│ │ │ │ └── useSettingsController.ts
│ │ │ ├── types/
│ │ │ │ └── types.ts
│ │ │ └── view/
│ │ │ ├── Settings.tsx
│ │ │ ├── SettingsCard.tsx
│ │ │ ├── SettingsMainTabs.tsx
│ │ │ ├── SettingsRow.tsx
│ │ │ ├── SettingsSection.tsx
│ │ │ ├── SettingsSidebar.tsx
│ │ │ ├── SettingsToggle.tsx
│ │ │ ├── modals/
│ │ │ │ ├── ClaudeMcpFormModal.tsx
│ │ │ │ └── CodexMcpFormModal.tsx
│ │ │ └── tabs/
│ │ │ ├── AppearanceSettingsTab.tsx
│ │ │ ├── NotificationsSettingsTab.tsx
│ │ │ ├── agents-settings/
│ │ │ │ ├── AgentListItem.tsx
│ │ │ │ ├── AgentsSettingsTab.tsx
│ │ │ │ ├── sections/
│ │ │ │ │ ├── AgentCategoryContentSection.tsx
│ │ │ │ │ ├── AgentCategoryTabsSection.tsx
│ │ │ │ │ ├── AgentSelectorSection.tsx
│ │ │ │ │ └── content/
│ │ │ │ │ ├── AccountContent.tsx
│ │ │ │ │ ├── McpServersContent.tsx
│ │ │ │ │ └── PermissionsContent.tsx
│ │ │ │ └── types.ts
│ │ │ ├── api-settings/
│ │ │ │ ├── CredentialsSettingsTab.tsx
│ │ │ │ ├── sections/
│ │ │ │ │ ├── ApiKeysSection.tsx
│ │ │ │ │ ├── GithubCredentialsSection.tsx
│ │ │ │ │ ├── NewApiKeyAlert.tsx
│ │ │ │ │ └── VersionInfoSection.tsx
│ │ │ │ └── types.ts
│ │ │ ├── git-settings/
│ │ │ │ └── GitSettingsTab.tsx
│ │ │ └── tasks-settings/
│ │ │ └── TasksSettingsTab.tsx
│ │ ├── shell/
│ │ │ ├── constants/
│ │ │ │ └── constants.ts
│ │ │ ├── hooks/
│ │ │ │ ├── useShellConnection.ts
│ │ │ │ ├── useShellRuntime.ts
│ │ │ │ └── useShellTerminal.ts
│ │ │ ├── types/
│ │ │ │ └── types.ts
│ │ │ ├── utils/
│ │ │ │ ├── auth.ts
│ │ │ │ ├── socket.ts
│ │ │ │ └── terminalStyles.ts
│ │ │ └── view/
│ │ │ ├── Shell.tsx
│ │ │ └── subcomponents/
│ │ │ ├── ShellConnectionOverlay.tsx
│ │ │ ├── ShellEmptyState.tsx
│ │ │ ├── ShellHeader.tsx
│ │ │ ├── ShellMinimalView.tsx
│ │ │ └── TerminalShortcutsPanel.tsx
│ │ ├── sidebar/
│ │ │ ├── hooks/
│ │ │ │ └── useSidebarController.ts
│ │ │ ├── types/
│ │ │ │ └── types.ts
│ │ │ ├── utils/
│ │ │ │ └── utils.ts
│ │ │ └── view/
│ │ │ ├── Sidebar.tsx
│ │ │ └── subcomponents/
│ │ │ ├── SidebarCollapsed.tsx
│ │ │ ├── SidebarContent.tsx
│ │ │ ├── SidebarFooter.tsx
│ │ │ ├── SidebarHeader.tsx
│ │ │ ├── SidebarModals.tsx
│ │ │ ├── SidebarProjectItem.tsx
│ │ │ ├── SidebarProjectList.tsx
│ │ │ ├── SidebarProjectSessions.tsx
│ │ │ ├── SidebarProjectsState.tsx
│ │ │ ├── SidebarSessionItem.tsx
│ │ │ └── TaskIndicator.tsx
│ │ ├── standalone-shell/
│ │ │ └── view/
│ │ │ ├── StandaloneShell.tsx
│ │ │ └── subcomponents/
│ │ │ ├── StandaloneShellEmptyState.tsx
│ │ │ └── StandaloneShellHeader.tsx
│ │ ├── task-master/
│ │ │ ├── context/
│ │ │ │ └── TaskMasterContext.tsx
│ │ │ ├── hooks/
│ │ │ │ ├── useProjectPrdFiles.ts
│ │ │ │ └── useTaskBoardState.ts
│ │ │ ├── index.ts
│ │ │ ├── types.ts
│ │ │ ├── utils/
│ │ │ │ ├── taskKanban.ts
│ │ │ │ └── taskSorting.ts
│ │ │ └── view/
│ │ │ ├── NextTaskBanner.tsx
│ │ │ ├── TaskBoard.tsx
│ │ │ ├── TaskBoardContent.tsx
│ │ │ ├── TaskBoardToolbar.tsx
│ │ │ ├── TaskCard.tsx
│ │ │ ├── TaskDetailModal.tsx
│ │ │ ├── TaskEmptyState.tsx
│ │ │ ├── TaskMasterPanel.tsx
│ │ │ ├── modals/
│ │ │ │ ├── CreateTaskModal.tsx
│ │ │ │ ├── TaskHelpModal.tsx
│ │ │ │ └── TaskMasterSetupModal.tsx
│ │ │ └── shared/
│ │ │ ├── TaskFiltersPanel.tsx
│ │ │ └── TaskQuickSortBar.tsx
│ │ └── version-upgrade/
│ │ └── view/
│ │ ├── VersionUpgradeModal.tsx
│ │ └── index.ts
│ ├── constants/
│ │ └── config.ts
│ ├── contexts/
│ │ ├── AuthContext.jsx
│ │ ├── PluginsContext.tsx
│ │ ├── TaskMasterContext.ts
│ │ ├── TasksSettingsContext.jsx
│ │ ├── ThemeContext.jsx
│ │ └── WebSocketContext.tsx
│ ├── hooks/
│ │ ├── useDeviceSettings.ts
│ │ ├── useLocalStorage.jsx
│ │ ├── useProjectsState.ts
│ │ ├── useSessionProtection.ts
│ │ ├── useUiPreferences.ts
│ │ ├── useVersionCheck.ts
│ │ └── useWebPush.ts
│ ├── i18n/
│ │ ├── config.js
│ │ ├── languages.js
│ │ └── locales/
│ │ ├── de/
│ │ │ ├── auth.json
│ │ │ ├── chat.json
│ │ │ ├── codeEditor.json
│ │ │ ├── common.json
│ │ │ ├── settings.json
│ │ │ ├── sidebar.json
│ │ │ └── tasks.json
│ │ ├── en/
│ │ │ ├── auth.json
│ │ │ ├── chat.json
│ │ │ ├── codeEditor.json
│ │ │ ├── common.json
│ │ │ ├── settings.json
│ │ │ ├── sidebar.json
│ │ │ └── tasks.json
│ │ ├── ja/
│ │ │ ├── auth.json
│ │ │ ├── chat.json
│ │ │ ├── codeEditor.json
│ │ │ ├── common.json
│ │ │ ├── settings.json
│ │ │ ├── sidebar.json
│ │ │ └── tasks.json
│ │ ├── ko/
│ │ │ ├── auth.json
│ │ │ ├── chat.json
│ │ │ ├── codeEditor.json
│ │ │ ├── common.json
│ │ │ ├── settings.json
│ │ │ └── sidebar.json
│ │ ├── ru/
│ │ │ ├── auth.json
│ │ │ ├── chat.json
│ │ │ ├── codeEditor.json
│ │ │ ├── common.json
│ │ │ ├── settings.json
│ │ │ ├── sidebar.json
│ │ │ └── tasks.json
│ │ └── zh-CN/
│ │ ├── auth.json
│ │ ├── chat.json
│ │ ├── codeEditor.json
│ │ ├── common.json
│ │ ├── settings.json
│ │ └── sidebar.json
│ ├── index.css
│ ├── lib/
│ │ └── utils.js
│ ├── main.jsx
│ ├── shared/
│ │ └── view/
│ │ └── ui/
│ │ ├── Badge.tsx
│ │ ├── Button.tsx
│ │ ├── DarkModeToggle.tsx
│ │ ├── Input.tsx
│ │ ├── LanguageSelector.tsx
│ │ ├── PillBar.tsx
│ │ ├── ScrollArea.tsx
│ │ ├── Tooltip.tsx
│ │ └── index.ts
│ ├── stores/
│ │ └── useSessionStore.ts
│ ├── types/
│ │ ├── app.ts
│ │ ├── global.d.ts
│ │ ├── react-syntax-highlighter.d.ts
│ │ └── sharedTypes.ts
│ ├── utils/
│ │ ├── api.js
│ │ ├── clipboard.ts
│ │ └── dateUtils.ts
│ └── vite-env.d.ts
├── tailwind.config.js
├── tsconfig.json
└── vite.config.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
type: Bug
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Error message**
If applicable, add the error message you see to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
**Additional context**
Add any other context about the problem here.
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: "[Feature]"
labels: ''
assignees: ''
type: Feature
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.
================================================
FILE: .github/workflows/discord-release.yml
================================================
name: Discord Release Notification
on:
release:
types: [published]
jobs:
github-releases-to-discord:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Github Releases To Discord
uses: SethCohen/github-releases-to-discord@v1.19.0
with:
webhook_url: ${{ secrets.DISCORD_WEBHOOK_URL }}
color: "2105893"
username: "Release Changelog"
avatar_url: "https://cdn.discordapp.com/avatars/487431320314576937/bd64361e4ba6313d561d54e78c9e7171.png"
content: "||@everyone||"
footer_title: "Changelog"
reduce_headings: true
================================================
FILE: .gitignore
================================================
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Build outputs
dist/
dist-ssr/
build/
out/
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# IDE and editor files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Logs
*.log
logs/
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Coverage directory used by tools like istanbul
coverage/
*.lcov
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
.tmp/
# Vite
.vite/
# Local Netlify folder
.netlify
# AI specific
.claude/
.cursor/
.roo/
.taskmaster/
.cline/
.windsurf/
.serena/
CLAUDE.md
.mcp.json
.gemini/
# Database files
*.db
*.sqlite
*.sqlite3
logs
dev-debug.log
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
# OS specific
# Task files
tasks.json
tasks/
# Translations
!src/i18n/locales/en/tasks.json
!src/i18n/locales/ja/tasks.json
!src/i18n/locales/ru/tasks.json
!src/i18n/locales/de/tasks.json
# Git worktrees
.worktrees/
================================================
FILE: .gitmodules
================================================
[submodule "plugins/starter"]
path = plugins/starter
url = https://github.com/cloudcli-ai/cloudcli-plugin-starter.git
================================================
FILE: .husky/commit-msg
================================================
npx commitlint --edit $1
================================================
FILE: .husky/pre-commit
================================================
npx lint-staged
================================================
FILE: .npmignore
================================================
*.md
!README.md
.env*
.gitignore
.nvmrc
.release-it.json
release.sh
postcss.config.js
vite.config.js
tailwind.config.js
# Database files
authdb/
*.db
*.sqlite
*.sqlite3
# IDE and editor files
.vscode/
.idea/
*.swp
*.swo
*~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# AI specific
.claude/
.cursor/
.roo/
.taskmaster/
.cline/
.windsurf/
.serena/
CLAUDE.md
.mcp.json
# Task files
tasks.json
tasks/
# Environment variables
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
================================================
FILE: .nvmrc
================================================
v22
================================================
FILE: .release-it.json
================================================
{
"git": {
"commitMessage": "chore(release): v${version}",
"tagName": "v${version}",
"requireBranch": "main",
"requireCleanWorkingDir": true
},
"npm": {
"publish": true
},
"github": {
"release": true,
"releaseName": "CloudCLI UI v${version}"
},
"hooks": {
"before:init": ["npm run build"]
},
"plugins": {
"@release-it/conventional-changelog": {
"infile": "CHANGELOG.md",
"header": "# Changelog\n\nAll notable changes to CloudCLI UI will be documented in this file.\n",
"preset": {
"name": "conventionalcommits",
"types": [
{ "type": "feat", "section": "New Features" },
{ "type": "feature", "section": "New Features" },
{ "type": "fix", "section": "Bug Fixes" },
{ "type": "perf", "section": "Performance" },
{ "type": "refactor", "section": "Refactoring" },
{ "type": "docs", "section": "Documentation" },
{ "type": "style", "section": "Styling" },
{ "type": "chore", "section": "Maintenance" },
{ "type": "ci", "section": "CI/CD" },
{ "type": "test", "section": "Tests" },
{ "type": "build", "section": "Build" }
]
}
}
}
}
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to CloudCLI UI will be documented in this file.
## [1.26.0](https://github.com/siteboon/claudecodeui/compare/v1.25.2...v1.26.0) (2026-03-20)
### New Features
* add German (Deutsch) language support ([#525](https://github.com/siteboon/claudecodeui/issues/525)) ([a7299c6](https://github.com/siteboon/claudecodeui/commit/a7299c68237908c752d504c2e8eea91570a30203))
* add WebSocket proxy for plugin backends ([#553](https://github.com/siteboon/claudecodeui/issues/553)) ([88c60b7](https://github.com/siteboon/claudecodeui/commit/88c60b70b031798d51ce26c8f080a0f64d824b05))
* Browser autofill support for login form ([#521](https://github.com/siteboon/claudecodeui/issues/521)) ([72ff134](https://github.com/siteboon/claudecodeui/commit/72ff134b315b7a1d602f3cc7dd60d47c1c1c34af))
* git panel redesign ([#535](https://github.com/siteboon/claudecodeui/issues/535)) ([adb3a06](https://github.com/siteboon/claudecodeui/commit/adb3a06d7e66a6d2dbcdfb501615e617178314af))
* introduce notification system and claude notifications ([#450](https://github.com/siteboon/claudecodeui/issues/450)) ([45e71a0](https://github.com/siteboon/claudecodeui/commit/45e71a0e73b368309544165e4dcf8b7fd014e8dd))
* **refactor:** move plugins to typescript ([#557](https://github.com/siteboon/claudecodeui/issues/557)) ([612390d](https://github.com/siteboon/claudecodeui/commit/612390db536417e2f68c501329bfccf5c6795e45))
* unified message architecture with provider adapters and session store ([#558](https://github.com/siteboon/claudecodeui/issues/558)) ([a4632dc](https://github.com/siteboon/claudecodeui/commit/a4632dc4cec228a8febb7c5bae4807c358963678))
### Bug Fixes
* detect Claude auth from settings env ([#527](https://github.com/siteboon/claudecodeui/issues/527)) ([95bcee0](https://github.com/siteboon/claudecodeui/commit/95bcee0ec459f186d52aeffe100ac1a024e92909))
* remove /exit command from claude login flow during onboarding ([#552](https://github.com/siteboon/claudecodeui/issues/552)) ([4de8b78](https://github.com/siteboon/claudecodeui/commit/4de8b78c6db5d8c2c402afce0f0b4cc16d5b6496))
### Documentation
* add German language link to all README files ([#534](https://github.com/siteboon/claudecodeui/issues/534)) ([1d31c3e](https://github.com/siteboon/claudecodeui/commit/1d31c3ec8309b433a041f3099955addc8c136c35))
* **readme:** hotfix and improve for README.jp.md ([#550](https://github.com/siteboon/claudecodeui/issues/550)) ([7413c2c](https://github.com/siteboon/claudecodeui/commit/7413c2c78422c308ac949e6a83c3e9216b24b649))
* **README:** update translations with CloudCLI branding and feature restructuring ([#544](https://github.com/siteboon/claudecodeui/issues/544)) ([14aef73](https://github.com/siteboon/claudecodeui/commit/14aef73cc6085fbb519fe64aea7cac80b7d51285))
## [1.25.2](https://github.com/siteboon/claudecodeui/compare/v1.25.0...v1.25.2) (2026-03-11)
### New Features
* **i18n:** localize plugin settings for all languages ([#515](https://github.com/siteboon/claudecodeui/issues/515)) ([621853c](https://github.com/siteboon/claudecodeui/commit/621853cbfb4233b34cb8cc2e1ed10917ba424352))
### Bug Fixes
* codeql user value provided path validation ([aaa14b9](https://github.com/siteboon/claudecodeui/commit/aaa14b9fc0b9b51c4fb9d1dba40fada7cbbe0356))
* numerous bugs ([#528](https://github.com/siteboon/claudecodeui/issues/528)) ([a77f213](https://github.com/siteboon/claudecodeui/commit/a77f213dd5d0b2538dea091ab8da6e55d2002f2f))
* **security:** disable executable gray-matter frontmatter in commands ([b9c902b](https://github.com/siteboon/claudecodeui/commit/b9c902b016f411a942c8707dd07d32b60bad087c))
* session reconnect catch-up, always-on input, frozen session recovery ([#524](https://github.com/siteboon/claudecodeui/issues/524)) ([4d8fb6e](https://github.com/siteboon/claudecodeui/commit/4d8fb6e30aa03d7cdb92bd62b7709422f9d08e32))
### Refactoring
* new settings page design and new pill component ([8ddeeb0](https://github.com/siteboon/claudecodeui/commit/8ddeeb0ce8d0642560bd3fa149236011dc6e3707))
## [1.25.0](https://github.com/siteboon/claudecodeui/compare/v1.24.0...v1.25.0) (2026-03-10)
### New Features
* add copy as text or markdown feature for assistant messages ([#519](https://github.com/siteboon/claudecodeui/issues/519)) ([1dc2a20](https://github.com/siteboon/claudecodeui/commit/1dc2a205dc2a3cbf960625d7669c7c63a2b6905f))
* add full Russian language support; update Readme.md files, and .gitignore update ([#514](https://github.com/siteboon/claudecodeui/issues/514)) ([c7dcba8](https://github.com/siteboon/claudecodeui/commit/c7dcba8d9117e84db8aac7d8a7bf6a3aa683e115))
* new plugin system ([#489](https://github.com/siteboon/claudecodeui/issues/489)) ([8afb46a](https://github.com/siteboon/claudecodeui/commit/8afb46af2e5514c9284030367281793fbb014e4f))
### Bug Fixes
* resolve duplicate key issue when rendering model options ([#520](https://github.com/siteboon/claudecodeui/issues/520)) ([9bceab9](https://github.com/siteboon/claudecodeui/commit/9bceab9e1a6e063b0b4f934ed2d9f854fcc9c6a4))
### Maintenance
* add plugins section in readme ([e581a0e](https://github.com/siteboon/claudecodeui/commit/e581a0e1ccd59fd7ec7306ca76a13e73d7c674c1))
## [1.24.0](https://github.com/siteboon/claudecodeui/compare/v1.23.2...v1.24.0) (2026-03-09)
### New Features
* add full-text search across conversations ([#482](https://github.com/siteboon/claudecodeui/issues/482)) ([3950c0e](https://github.com/siteboon/claudecodeui/commit/3950c0e47f41e93227af31494690818d45c8bc7a))
### Bug Fixes
* **git:** prevent shell injection in git routes ([86c33c1](https://github.com/siteboon/claudecodeui/commit/86c33c1c0cb34176725a38f46960213714fc3e04))
* replace getDatabase with better-sqlite3 db in getGithubTokenById ([#501](https://github.com/siteboon/claudecodeui/issues/501)) ([cb4fd79](https://github.com/siteboon/claudecodeui/commit/cb4fd795c938b1cc86d47f401973bfccdd68fdee))
## [1.23.2](https://github.com/siteboon/claudecodeui/compare/v1.22.1...v1.23.2) (2026-03-06)
### New Features
* add clickable overlay buttons for CLI prompts in Shell terminal ([#480](https://github.com/siteboon/claudecodeui/issues/480)) ([2444209](https://github.com/siteboon/claudecodeui/commit/2444209723701dda2b881cea2501b239e64e51c1)), closes [#427](https://github.com/siteboon/claudecodeui/issues/427)
* add terminal shortcuts panel for mobile ([#411](https://github.com/siteboon/claudecodeui/issues/411)) ([b0a3fdf](https://github.com/siteboon/claudecodeui/commit/b0a3fdf95ffdb961261194d10400267251e42f17))
* implement session rename with SQLite storage ([#413](https://github.com/siteboon/claudecodeui/issues/413)) ([198e3da](https://github.com/siteboon/claudecodeui/commit/198e3da89b353780f53a91888384da9118995e81)), closes [#72](https://github.com/siteboon/claudecodeui/issues/72) [#358](https://github.com/siteboon/claudecodeui/issues/358)
### Bug Fixes
* **chat:** finalize terminal lifecycle to prevent stuck processing/thinking UI ([#483](https://github.com/siteboon/claudecodeui/issues/483)) ([0590c5c](https://github.com/siteboon/claudecodeui/commit/0590c5c178f4791e2b039d525ecca4d220c3dcae))
* **codex-history:** prevent AGENTS.md/internal prompt leakage when reloading Codex sessions ([#488](https://github.com/siteboon/claudecodeui/issues/488)) ([64a96b2](https://github.com/siteboon/claudecodeui/commit/64a96b24f853acb802f700810b302f0f5cf00898))
* preserve pending permission requests across WebSocket reconnections ([#462](https://github.com/siteboon/claudecodeui/issues/462)) ([4ee88f0](https://github.com/siteboon/claudecodeui/commit/4ee88f0eb0c648b54b05f006c6796fb7b09b0fae))
* prevent React 18 batching from losing messages during session sync ([#461](https://github.com/siteboon/claudecodeui/issues/461)) ([688d734](https://github.com/siteboon/claudecodeui/commit/688d73477a50773e43c85addc96212aa6290aea5))
* release it script ([dcea8a3](https://github.com/siteboon/claudecodeui/commit/dcea8a329c7d68437e1e72c8c766cf33c74637e9))
### Styling
* improve UI for processing banner ([#477](https://github.com/siteboon/claudecodeui/issues/477)) ([2320e1d](https://github.com/siteboon/claudecodeui/commit/2320e1d74b59c65b5b7fc4fa8b05fd9208f4898c))
### Maintenance
* remove logging of received WebSocket messages in production ([#487](https://github.com/siteboon/claudecodeui/issues/487)) ([9193feb](https://github.com/siteboon/claudecodeui/commit/9193feb6dc83041f3c365204648a88468bdc001b))
## [1.22.0](https://github.com/siteboon/claudecodeui/compare/v1.21.0...v1.22.0) (2026-03-03)
### New Features
* add community button in the app ([84d4634](https://github.com/siteboon/claudecodeui/commit/84d4634735f9ee13ac1c20faa0e7e31f1b77cae8))
* Advanced file editor and file tree improvements ([#444](https://github.com/siteboon/claudecodeui/issues/444)) ([9768958](https://github.com/siteboon/claudecodeui/commit/97689588aa2e8240ba4373da5f42ab444c772e72))
* update document title based on selected project ([#448](https://github.com/siteboon/claudecodeui/issues/448)) ([9e22f42](https://github.com/siteboon/claudecodeui/commit/9e22f42a3d3a781f448ddac9d133292fe103bb8c))
### Bug Fixes
* **claude:** correct project encoded path ([#451](https://github.com/siteboon/claudecodeui/issues/451)) ([9c0e864](https://github.com/siteboon/claudecodeui/commit/9c0e864532dcc5ce7ee890d3b4db722872db2b54)), closes [#447](https://github.com/siteboon/claudecodeui/issues/447)
* **claude:** move model usage log to result message only ([#454](https://github.com/siteboon/claudecodeui/issues/454)) ([506d431](https://github.com/siteboon/claudecodeui/commit/506d43144b3ec3155c3e589e7e803862c4a8f83a))
* missing translation label ([855e22f](https://github.com/siteboon/claudecodeui/commit/855e22f9176a71daa51de716370af7f19d55bfb4))
### Maintenance
* add Gemini-CLI support to README ([#453](https://github.com/siteboon/claudecodeui/issues/453)) ([503c384](https://github.com/siteboon/claudecodeui/commit/503c3846850fb843781979b0c0e10a24b07e1a4b))
## [1.21.0](https://github.com/siteboon/claudecodeui/compare/v1.20.1...v1.21.0) (2026-02-27)
### New Features
* add copy icon for user messages ([#449](https://github.com/siteboon/claudecodeui/issues/449)) ([b359c51](https://github.com/siteboon/claudecodeui/commit/b359c515277b4266fde2fb9a29b5356949c07c4f))
* Google's gemini-cli integration ([#422](https://github.com/siteboon/claudecodeui/issues/422)) ([a367edd](https://github.com/siteboon/claudecodeui/commit/a367edd51578608b3281373cb4a95169dbf17f89))
* persist active tab across reloads via localStorage ([#414](https://github.com/siteboon/claudecodeui/issues/414)) ([e3b6892](https://github.com/siteboon/claudecodeui/commit/e3b689214f11d549ffe1b3a347476d58f25c5aca)), closes [#387](https://github.com/siteboon/claudecodeui/issues/387)
### Bug Fixes
* add support for Codex in the shell ([#424](https://github.com/siteboon/claudecodeui/issues/424)) ([23801e9](https://github.com/siteboon/claudecodeui/commit/23801e9cc15d2b8d1bfc6e39aee2fae93226d1ad))
### Maintenance
* upgrade @anthropic-ai/claude-agent-sdk to version 0.2.59 and add model usage logging ([#446](https://github.com/siteboon/claudecodeui/issues/446)) ([917c353](https://github.com/siteboon/claudecodeui/commit/917c353115653ee288bf97be01f62fad24123cbc))
* upgrade better-sqlite to latest version to support node 25 ([#445](https://github.com/siteboon/claudecodeui/issues/445)) ([4ab94fc](https://github.com/siteboon/claudecodeui/commit/4ab94fce4257e1e20370fa83fa4c0f6fadbb8a2b))
## [1.20.1](https://github.com/siteboon/claudecodeui/compare/v1.19.1...v1.20.1) (2026-02-23)
### New Features
* implement install mode detection and update commands in version upgrade process ([f986004](https://github.com/siteboon/claudecodeui/commit/f986004319207b068431f9f6adf338a8ce8decfc))
* migrate legacy database to new location and improve last login update handling ([50e097d](https://github.com/siteboon/claudecodeui/commit/50e097d4ac498aa9f1803ef3564843721833dc19))
## [1.19.1](https://github.com/siteboon/claudecodeui/compare/v1.19.0...v1.19.1) (2026-02-23)
### Bug Fixes
* add prepublishOnly script to build before publishing ([82efac4](https://github.com/siteboon/claudecodeui/commit/82efac4704cab11ed8d1a05fe84f41312140b223))
## [1.19.0](https://github.com/siteboon/claudecodeui/compare/v1.18.2...v1.19.0) (2026-02-23)
### New Features
* add HOST environment variable for configurable bind address ([#360](https://github.com/siteboon/claudecodeui/issues/360)) ([cccd915](https://github.com/siteboon/claudecodeui/commit/cccd915c336192216b6e6f68e2b5f3ece0ccf966))
* subagent tool grouping ([#398](https://github.com/siteboon/claudecodeui/issues/398)) ([0207a1f](https://github.com/siteboon/claudecodeui/commit/0207a1f3a3c87f1c6c1aee8213be999b23289386))
### Bug Fixes
* **macos:** fix node-pty posix_spawnp error with postinstall script ([#347](https://github.com/siteboon/claudecodeui/issues/347)) ([38a593c](https://github.com/siteboon/claudecodeui/commit/38a593c97fdb2bb7f051e09e8e99c16035448655)), closes [#284](https://github.com/siteboon/claudecodeui/issues/284)
* slash commands with arguments bypass command execution ([#392](https://github.com/siteboon/claudecodeui/issues/392)) ([597e9c5](https://github.com/siteboon/claudecodeui/commit/597e9c54b76e7c6cd1947299c668c78d24019cab))
### Refactoring
* **releases:** Create a contributing guide and proper release notes using a release-it plugin ([fc369d0](https://github.com/siteboon/claudecodeui/commit/fc369d047e13cba9443fe36c0b6bb2ce3beaf61c))
### Maintenance
* update @anthropic-ai/claude-agent-sdk to version 0.1.77 in package-lock.json ([#410](https://github.com/siteboon/claudecodeui/issues/410)) ([7ccbc8d](https://github.com/siteboon/claudecodeui/commit/7ccbc8d92d440e18c157b656c9ea2635044a64f6))
================================================
FILE: CONTRIBUTING.md
================================================
# Contributing to CloudCLI UI
Thanks for your interest in contributing to CloudCLI UI! Before you start, please take a moment to read through this guide.
## Before You Start
- **Search first.** Check [existing issues](https://github.com/siteboon/claudecodeui/issues) and [pull requests](https://github.com/siteboon/claudecodeui/pulls) to avoid duplicating work.
- **Discuss first** for new features. Open an [issue](https://github.com/siteboon/claudecodeui/issues/new) to discuss your idea before investing time in implementation. We may already have plans or opinions on how it should work.
- **Bug fixes are always welcome.** If you spot a bug, feel free to open a PR directly.
## Prerequisites
- [Node.js](https://nodejs.org/) 22 or later
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and configured
## Getting Started
1. Fork the repository
2. Clone your fork:
```bash
git clone https://github.com/<your-username>/claudecodeui.git
cd claudecodeui
```
3. Install dependencies:
```bash
npm install
```
4. Start the development server:
```bash
npm run dev
```
5. Create a branch for your changes:
```bash
git checkout -b feat/your-feature-name
```
## Project Structure
```
claudecodeui/
├── src/ # React frontend (Vite + Tailwind)
│ ├── components/ # UI components
│ ├── contexts/ # React context providers
│ ├── hooks/ # Custom React hooks
│ ├── i18n/ # Internationalization and translations
│ ├── lib/ # Shared frontend libraries
│ ├── types/ # TypeScript type definitions
│ └── utils/ # Frontend utilities
├── server/ # Express backend
│ ├── routes/ # API route handlers
│ ├── middleware/ # Express middleware
│ ├── database/ # SQLite database layer
│ └── tools/ # CLI tool integrations
├── shared/ # Code shared between client and server
└── public/ # Static assets, icons, PWA manifest
```
## Development Workflow
- `npm run dev` — Start both the frontend and backend in development mode
- `npm run build` — Create a production build
- `npm run server` — Start only the backend server
- `npm run client` — Start only the Vite dev server
## Making Changes
### Bug Fixes
- Reference the issue number in your PR if one exists
- Describe how to reproduce the bug in your PR description
- Add a screenshot or recording for visual bugs
### New Features
- Keep the scope focused — one feature per PR
- Include screenshots or recordings for UI changes
### Documentation
- Documentation improvements are always welcome
- Keep language clear and concise
## Commit Convention
We follow [Conventional Commits](https://conventionalcommits.org/) to generate release notes automatically. Every commit message should follow this format:
```
<type>(optional scope): <description>
```
Use imperative, present tense: "add feature" not "added feature" or "adds feature".
### Types
| Type | Description |
|------|-------------|
| `feat` | A new feature |
| `fix` | A bug fix |
| `perf` | A performance improvement |
| `refactor` | Code change that neither fixes a bug nor adds a feature |
| `docs` | Documentation only |
| `style` | CSS, formatting, visual changes |
| `chore` | Maintenance, dependencies, config |
| `ci` | CI/CD pipeline changes |
| `test` | Adding or updating tests |
| `build` | Build system changes |
### Examples
```bash
feat: add conversation search
feat(i18n): add Japanese language support
fix: redirect unauthenticated users to login
fix(editor): syntax highlighting for .env files
perf: lazy load code editor component
refactor(chat): extract message list component
docs: update API configuration guide
```
### Breaking Changes
Add `!` after the type or include `BREAKING CHANGE:` in the commit footer:
```bash
feat!: redesign settings page layout
```
## Pull Requests
- Give your PR a clear, descriptive title following the commit convention above
- Fill in the PR description with what changed and why
- Link any related issues
- Include screenshots for UI changes
- Make sure the build passes (`npm run build`)
- Keep PRs focused — avoid unrelated changes
## Releases
Releases are managed by maintainers using [release-it](https://github.com/release-it/release-it) with the [conventional changelog plugin](https://github.com/release-it/conventional-changelog).
```bash
npm run release # interactive (prompts for version bump)
npm run release -- patch # patch release
npm run release -- minor # minor release
```
This automatically:
- Bumps the version based on commit types (`feat` = minor, `fix` = patch)
- Generates categorized release notes
- Updates `CHANGELOG.md`
- Creates a git tag and GitHub Release
- Publishes to npm
## License
By contributing, you agree that your contributions will be licensed under the [GPL-3.0 License](LICENSE).
================================================
FILE: LICENSE
================================================
# GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
## Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom
to share and change all versions of a program--to make sure it remains
free software for all its users. We, the Free Software Foundation, use
the GNU General Public License for most of our software; it applies
also to any other work released this way by its authors. You can apply
it to your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you
have certain responsibilities if you distribute copies of the
software, or if you modify it: responsibilities to respect the freedom
of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the
manufacturer can do so. This is fundamentally incompatible with the
aim of protecting users' freedom to change the software. The
systematic pattern of such abuse occurs in the area of products for
individuals to use, which is precisely where it is most unacceptable.
Therefore, we have designed this version of the GPL to prohibit the
practice for those products. If such problems arise substantially in
other domains, we stand ready to extend this provision to those
domains in future versions of the GPL, as needed to protect the
freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish
to avoid the special danger that patents applied to a free program
could make it effectively proprietary. To prevent this, the GPL
assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
## TERMS AND CONDITIONS
### 0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds
of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of
an exact copy. The resulting work is called a "modified version" of
the earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user
through a computer network, with no transfer of a copy, is not
conveying.
An interactive user interface displays "Appropriate Legal Notices" to
the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
### 1. Source Code.
The "source code" for a work means the preferred form of the work for
making modifications to it. "Object code" means any non-source form of
a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users can
regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same
work.
### 2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey,
without conditions so long as your license otherwise remains in force.
You may convey covered works to others for the sole purpose of having
them make modifications exclusively for you, or provide you with
facilities for running those works, provided that you comply with the
terms of this License in conveying all material for which you do not
control copyright. Those thus making or running the covered works for
you must do so exclusively on your behalf, under your direction and
control, on terms that prohibit them from making any copies of your
copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the
conditions stated below. Sublicensing is not allowed; section 10 makes
it unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such
circumvention is effected by exercising rights under this License with
respect to the covered work, and you disclaim any intention to limit
operation or modification of the work as a means of enforcing, against
the work's users, your or third parties' legal rights to forbid
circumvention of technological measures.
### 4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
### 5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these
conditions:
- a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
- b) The work must carry prominent notices stating that it is
released under this License and any conditions added under
section 7. This requirement modifies the requirement in section 4
to "keep intact all notices".
- c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
- d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
### 6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of
sections 4 and 5, provided that you also convey the machine-readable
Corresponding Source under the terms of this License, in one of these
ways:
- a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
- b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the Corresponding
Source from a network server at no charge.
- c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
- d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
- e) Convey the object code using peer-to-peer transmission,
provided you inform other peers where the object code and
Corresponding Source of the work are being offered to the general
public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal,
family, or household purposes, or (2) anything designed or sold for
incorporation into a dwelling. In determining whether a product is a
consumer product, doubtful cases shall be resolved in favor of
coverage. For a particular product received by a particular user,
"normally used" refers to a typical or common use of that class of
product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected
to use, the product. A product is a consumer product regardless of
whether the product has substantial commercial, industrial or
non-consumer uses, unless such uses represent the only significant
mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to
install and execute modified versions of a covered work in that User
Product from a modified version of its Corresponding Source. The
information must suffice to ensure that the continued functioning of
the modified object code is in no case prevented or interfered with
solely because modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or
updates for a work that has been modified or installed by the
recipient, or for the User Product in which it has been modified or
installed. Access to a network may be denied when the modification
itself materially and adversely affects the operation of the network
or violates the rules and protocols for communication across the
network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
### 7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders
of that material) supplement the terms of this License with terms:
- a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
- b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
- c) Prohibiting misrepresentation of the origin of that material,
or requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
- d) Limiting the use for publicity purposes of names of licensors
or authors of the material; or
- e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
- f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions
of it) with contractual assumptions of liability to the recipient,
for any liability that these contractual assumptions directly
impose on those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions; the
above requirements apply either way.
### 8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your license
from a particular copyright holder is reinstated (a) provisionally,
unless and until the copyright holder explicitly and finally
terminates your license, and (b) permanently, if the copyright holder
fails to notify you of the violation by some reasonable means prior to
60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
### 9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run
a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
### 10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
### 11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned
or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within the
scope of its coverage, prohibits the exercise of, or is conditioned on
the non-exercise of one or more of the rights that are specifically
granted under this License. You may not convey a covered work if you
are a party to an arrangement with a third party that is in the
business of distributing software, under which you make payment to the
third party based on the extent of your activity of conveying the
work, and under which the third party grants, to any of the parties
who would receive the covered work from you, a discriminatory patent
license (a) in connection with copies of the covered work conveyed by
you (or copies made from those copies), or (b) primarily for and in
connection with specific products or compilations that contain the
covered work, unless you entered into that arrangement, or that patent
license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
### 12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under
this License and any other pertinent obligations, then as a
consequence you may not convey it at all. For example, if you agree to
terms that obligate you to collect a royalty for further conveying
from those to whom you convey the Program, the only way you could
satisfy both those terms and this License would be to refrain entirely
from conveying the Program.
### 13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
### 14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions
of the GNU General Public License from time to time. Such new versions
will be similar in spirit to the present version, but may differ in
detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies that a certain numbered version of the GNU General Public
License "or any later version" applies to it, you have the option of
following the terms and conditions either of that numbered version or
of any later version published by the Free Software Foundation. If the
Program does not specify a version number of the GNU General Public
License, you may choose any version ever published by the Free
Software Foundation.
If the Program specifies that a proxy can decide which future versions
of the GNU General Public License can be used, that proxy's public
statement of acceptance of a version permanently authorizes you to
choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
### 15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT
WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR
CORRECTION.
### 16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR
CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES
ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT
NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR
LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM
TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER
PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
## How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these
terms.
To do so, attach the following notices to the program. It is safest to
attach them to the start of each source file to most effectively state
the exclusion of warranty; and each file should have at least the
"copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper
mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands \`show w' and \`show c' should show the
appropriate parts of the General Public License. Of course, your
program's commands might be different; for a GUI interface, you would
use an "about box".
You should also get your employer (if you work as a programmer) or
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. For more information on this, and how to apply and follow
the GNU GPL, see <https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your
program into proprietary programs. If your program is a subroutine
library, you may consider it more useful to permit linking proprietary
applications with the library. If this is what you want to do, use the
GNU Lesser General Public License instead of this License. But first,
please read <https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: README.de.md
================================================
<div align="center">
<img src="public/logo.svg" alt="CloudCLI UI" width="64" height="64">
<h1>Cloud CLI (auch bekannt als Claude Code UI)</h1>
<p>Eine Desktop- und Mobile-Oberfläche für <a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>, <a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>, <a href="https://developers.openai.com/codex">Codex</a> und <a href="https://geminicli.com/">Gemini-CLI</a>.<br>Lokal oder remote nutzbar – verwalte deine aktiven Projekte und Sitzungen von überall.</p>
</div>
<p align="center">
<a href="https://cloudcli.ai">CloudCLI Cloud</a> · <a href="https://cloudcli.ai/docs">Dokumentation</a> · <a href="https://discord.gg/buxwujPNRE">Discord</a> · <a href="https://github.com/siteboon/claudecodeui/issues">Fehler melden</a> · <a href="CONTRIBUTING.md">Mitwirken</a>
</p>
<p align="center">
<a href="https://cloudcli.ai"><img src="https://img.shields.io/badge/☁️_CloudCLI_Cloud-Try_Now-0066FF?style=for-the-badge" alt="CloudCLI Cloud"></a>
<a href="https://discord.gg/buxwujPNRE"><img src="https://img.shields.io/badge/Discord-Join_Community-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Join Community"></a>
<br><br>
<a href="https://trendshift.io/repositories/15586" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15586" alt="siteboon%2Fclaudecodeui | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<div align="right"><i><a href="./README.md">English</a> · <a href="./README.ru.md">Русский</a> · <b>Deutsch</b> · <a href="./README.ko.md">한국어</a> · <a href="./README.zh-CN.md">中文</a> · <a href="./README.ja.md">日本語</a></i></div>
---
## Screenshots
<div align="center">
<table>
<tr>
<td align="center">
<h3>Desktop-Ansicht</h3>
<img src="public/screenshots/desktop-main.png" alt="Desktop-Oberfläche" width="400">
<br>
<em>Hauptoberfläche mit Projektübersicht und Chat</em>
</td>
<td align="center">
<h3>Mobile-Erfahrung</h3>
<img src="public/screenshots/mobile-chat.png" alt="Mobile-Oberfläche" width="250">
<br>
<em>Responsives mobiles Design mit Touch-Navigation</em>
</td>
</tr>
<tr>
<td align="center" colspan="2">
<h3>CLI-Auswahl</h3>
<img src="public/screenshots/cli-selection.png" alt="CLI-Auswahl" width="400">
<br>
<em>Wähle zwischen Claude Code, Gemini, Cursor CLI und Codex</em>
</td>
</tr>
</table>
</div>
## Funktionen
- **Responsives Design** – Funktioniert nahtlos auf Desktop, Tablet und Mobilgerät, sodass du Agents auch vom Smartphone aus nutzen kannst
- **Interaktives Chat-Interface** – Eingebaute Chat-Oberfläche für die reibungslose Kommunikation mit den Agents
- **Integriertes Shell-Terminal** – Direkter Zugriff auf die Agents CLI über die eingebaute Shell-Funktionalität
- **Datei-Explorer** – Interaktiver Dateibaum mit Syntaxhervorhebung und Live-Bearbeitung
- **Git-Explorer** – Änderungen anzeigen, stagen und committen. Branches wechseln ebenfalls möglich
- **Sitzungsverwaltung** – Gespräche fortsetzen, mehrere Sitzungen verwalten und Verlauf nachverfolgen
- **Plugin-System** – CloudCLI mit eigenen Plugins erweitern – neue Tabs, Backend-Dienste und Integrationen hinzufügen. [Eigenes Plugin erstellen →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
- **TaskMaster AI Integration** *(Optional)* – Erweitertes Projektmanagement mit KI-gestützter Aufgabenplanung, PRD-Parsing und Workflow-Automatisierung
- **Modell-Kompatibilität** – Funktioniert mit Claude, GPT und Gemini (vollständige Liste unterstützter Modelle in [`shared/modelConstants.js`](shared/modelConstants.js))
## Schnellstart
### CloudCLI Cloud (Empfohlen)
Der schnellste Einstieg – keine lokale Einrichtung erforderlich. Erhalte eine vollständig verwaltete, containerisierte Entwicklungsumgebung, die über Web, Mobile App, API oder deine bevorzugte IDE erreichbar ist.
**[Mit CloudCLI Cloud starten](https://cloudcli.ai)**
### Self-Hosted (Open Source)
CloudCLI UI sofort mit **npx** ausprobieren (erfordert **Node.js** v22+):
```bash
npx @siteboon/claude-code-ui
```
Oder **global** installieren für regelmäßige Nutzung:
```bash
npm install -g @siteboon/claude-code-ui
cloudcli
```
Öffne `http://localhost:3001` – alle vorhandenen Sitzungen werden automatisch erkannt.
Die **[Dokumentation →](https://cloudcli.ai/docs)** enthält weitere Konfigurationsoptionen, PM2, Remote-Server-Einrichtung und mehr.
---
## Welche Option passt zu dir?
CloudCLI UI ist die Open-Source-UI-Schicht, die CloudCLI Cloud antreibt. Du kannst es auf deinem eigenen Rechner selbst hosten oder CloudCLI Cloud nutzen, das darauf aufbaut und eine vollständig verwaltete Cloud-Umgebung, Team-Funktionen und tiefere Integrationen bietet.
| | CloudCLI UI (Self-hosted) | CloudCLI Cloud |
|---|---|---|
| **Am besten für** | Entwickler:innen, die eine vollständige UI für lokale Agent-Sitzungen auf ihrem eigenen Rechner möchten | Teams und Entwickler:innen, die Agents in der Cloud betreiben möchten, überall erreichbar |
| **Zugriff** | Browser via `[deineIP]:port` | Browser, jede IDE, REST API, n8n |
| **Einrichtung** | `npx @siteboon/claude-code-ui` | Keine Einrichtung erforderlich |
| **Rechner muss laufen** | Ja | Nein |
| **Mobiler Zugriff** | Jeder Browser im Netzwerk | Jedes Gerät, native App in Entwicklung |
| **Verfügbare Sitzungen** | Alle Sitzungen automatisch aus `~/.claude` erkannt | Alle Sitzungen in deiner Cloud-Umgebung |
| **Unterstützte Agents** | Claude Code, Cursor CLI, Codex, Gemini CLI | Claude Code, Cursor CLI, Codex, Gemini CLI |
| **Datei-Explorer und Git** | Ja, direkt in der UI | Ja, direkt in der UI |
| **MCP-Konfiguration** | Über UI verwaltet, synchronisiert mit lokalem `~/.claude` | Über UI verwaltet |
| **IDE-Zugriff** | Deine lokale IDE | Jede IDE, die mit deiner Cloud-Umgebung verbunden ist |
| **REST API** | Ja | Ja |
| **n8n-Node** | Nein | Ja |
| **Team-Sharing** | Nein | Ja |
| **Plattformkosten** | Kostenlos, Open Source | Ab $7/Monat |
> Beide Optionen verwenden deine eigenen KI-Abonnements (Claude, Cursor usw.) – CloudCLI stellt die Umgebung bereit, nicht die KI.
---
## Sicherheit & Tool-Konfiguration
**🔒 Wichtiger Hinweis**: Alle Claude Code Tools sind **standardmäßig deaktiviert**. Dies verhindert, dass potenziell schädliche Operationen automatisch ausgeführt werden.
### Tools aktivieren
Um den vollen Funktionsumfang von Claude Code zu nutzen, müssen Tools manuell aktiviert werden:
1. **Tool-Einstellungen öffnen** – Klicke auf das Zahnrad-Symbol in der Seitenleiste
2. **Selektiv aktivieren** – Nur die benötigten Tools einschalten
3. **Einstellungen übernehmen** – Deine Einstellungen werden lokal gespeichert
<div align="center">

*Tool-Einstellungen – nur aktivieren, was benötigt wird*
</div>
**Empfohlene Vorgehensweise**: Mit grundlegenden Tools starten und bei Bedarf weitere hinzufügen. Die Einstellungen können jederzeit angepasst werden.
---
## Plugins
CloudCLI verfügt über ein Plugin-System, mit dem benutzerdefinierte Tabs mit eigener Frontend-UI und optionalem Node.js-Backend hinzugefügt werden können. Plugins können direkt in **Einstellungen > Plugins** aus Git-Repos installiert oder selbst entwickelt werden.
### Verfügbare Plugins
| Plugin | Beschreibung |
|---|---|
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | Zeigt Dateianzahl, Codezeilen, Dateityp-Aufschlüsselung, größte Dateien und zuletzt geänderte Dateien des aktuellen Projekts |
### Eigenes Plugin erstellen
**[Plugin-Starter-Vorlage →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** – Forke dieses Repository, um ein eigenes Plugin zu erstellen. Es enthält ein funktionierendes Beispiel mit Frontend-Rendering, Live-Kontext-Updates und RPC-Kommunikation zu einem Backend-Server.
**[Plugin-Dokumentation →](https://cloudcli.ai/docs/plugin-overview)** – Vollständige Anleitung zur Plugin-API, zum Manifest-Format, zum Sicherheitsmodell und mehr.
---
## FAQ
<details>
<summary>Wie unterscheidet sich das von Claude Code Remote Control?</summary>
Claude Code Remote Control ermöglicht es, Nachrichten an eine bereits im lokalen Terminal laufende Sitzung zu senden. Der Rechner muss eingeschaltet bleiben, das Terminal muss offen bleiben, und Sitzungen laufen nach etwa 10 Minuten ohne Netzwerkverbindung ab.
CloudCLI UI und CloudCLI Cloud erweitern Claude Code, anstatt neben ihm zu laufen – MCP-Server, Berechtigungen, Einstellungen und Sitzungen sind exakt dieselben, die Claude Code nativ verwendet. Nichts wird dupliziert oder separat verwaltet.
Das bedeutet in der Praxis:
- **Alle Sitzungen, nicht nur eine** – CloudCLI UI erkennt automatisch jede Sitzung aus dem `~/.claude`-Ordner. Remote Control stellt nur die einzelne aktive Sitzung bereit, um sie in der Claude Mobile App verfügbar zu machen.
- **Deine Einstellungen sind deine Einstellungen** – MCP-Server, Tool-Berechtigungen und Projektkonfiguration, die in CloudCLI UI geändert werden, werden direkt in die Claude Code-Konfiguration geschrieben und treten sofort in Kraft – und umgekehrt.
- **Funktioniert mit mehr Agents** – Claude Code, Cursor CLI, Codex und Gemini CLI, nicht nur Claude Code.
- **Vollständige UI, nicht nur ein Chat-Fenster** – Datei-Explorer, Git-Integration, MCP-Verwaltung und ein Shell-Terminal sind alle eingebaut.
- **CloudCLI Cloud läuft in der Cloud** – Laptop zuklappen, der Agent läuft weiter. Kein Terminal zu überwachen, kein Rechner, der laufen muss.
</details>
<details>
<summary>Muss ich ein KI-Abonnement separat bezahlen?</summary>
Ja. CloudCLI stellt die Umgebung bereit, nicht die KI. Du bringst dein eigenes Claude-, Cursor-, Codex- oder Gemini-Abonnement mit. CloudCLI Cloud beginnt bei $7/Monat für die gehostete Umgebung zusätzlich dazu.
</details>
<details>
<summary>Kann ich CloudCLI UI auf meinem Smartphone nutzen?</summary>
Ja. Bei Self-Hosted: Server auf dem eigenen Rechner starten und `[deineIP]:port` in einem beliebigen Browser im Netzwerk öffnen. Bei CloudCLI Cloud: Von jedem Gerät aus öffnen – kein VPN, keine Portweiterleitung, keine Einrichtung. Eine native App ist ebenfalls in Entwicklung.
</details>
<details>
<summary>Wirken sich Änderungen in der UI auf mein lokales Claude Code-Setup aus?</summary>
Ja, bei Self-Hosted. CloudCLI UI liest aus und schreibt in dieselbe `~/.claude`-Konfiguration, die Claude Code nativ verwendet. MCP-Server, die über die UI hinzugefügt werden, erscheinen sofort in Claude Code und umgekehrt.
</details>
---
## Community & Support
- **[Dokumentation](https://cloudcli.ai/docs)** — Installation, Konfiguration, Funktionen und Fehlerbehebung
- **[Discord](https://discord.gg/buxwujPNRE)** — Hilfe erhalten und mit anderen Nutzer:innen in Kontakt treten
- **[GitHub Issues](https://github.com/siteboon/claudecodeui/issues)** — Fehlerberichte und Feature-Anfragen
- **[Beitragsrichtlinien](CONTRIBUTING.md)** — So kannst du zum Projekt beitragen
## Lizenz
GNU General Public License v3.0 – siehe [LICENSE](LICENSE)-Datei für Details.
Dieses Projekt ist Open Source und kann unter der GPL v3-Lizenz kostenlos genutzt, modifiziert und verteilt werden.
## Danksagungen
### Erstellt mit
- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** - Anthropics offizielle CLI
- **[Cursor CLI](https://docs.cursor.com/en/cli/overview)** - Cursors offizielle CLI
- **[Codex](https://developers.openai.com/codex)** - OpenAI Codex
- **[Gemini-CLI](https://geminicli.com/)** - Google Gemini CLI
- **[React](https://react.dev/)** - UI-Bibliothek
- **[Vite](https://vitejs.dev/)** - Schnelles Build-Tool und Dev-Server
- **[Tailwind CSS](https://tailwindcss.com/)** - Utility-first CSS-Framework
- **[CodeMirror](https://codemirror.net/)** - Erweiterter Code-Editor
- **[TaskMaster AI](https://github.com/eyaltoledano/claude-task-master)** *(Optional)* - KI-gestütztes Projektmanagement und Aufgabenplanung
### Sponsoren
- [Siteboon - KI-gestützter Website-Builder](https://siteboon.ai)
---
<div align="center">
<strong>Mit Sorgfalt für die Claude Code-, Cursor- und Codex-Community erstellt.</strong>
</div>
================================================
FILE: README.ja.md
================================================
<div align="center">
<img src="public/logo.svg" alt="CloudCLI UI" width="64" height="64">
<h1>Cloud CLI(別名 Claude Code UI)</h1>
<p><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>、<a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>、<a href="https://developers.openai.com/codex">Codex</a>、<a href="https://geminicli.com/">Gemini-CLI</a> のためのデスクトップ/モバイル UI。<br>ローカルでもリモートでも使え、アクティブなプロジェクトとセッションをどこからでも閲覧できます。</p>
</div>
<p align="center">
<a href="https://cloudcli.ai">CloudCLI Cloud</a> · <a href="https://cloudcli.ai/docs">ドキュメント</a> · <a href="https://discord.gg/buxwujPNRE">Discord</a> · <a href="https://github.com/siteboon/claudecodeui/issues">バグ報告</a> · <a href="CONTRIBUTING.md">コントリビュート</a>
</p>
<p align="center">
<a href="https://cloudcli.ai"><img src="https://img.shields.io/badge/☁️_CloudCLI_Cloud-Try_Now-0066FF?style=for-the-badge" alt="CloudCLI Cloud"></a>
<a href="https://discord.gg/buxwujPNRE"><img src="https://img.shields.io/badge/Discord-Join%20Community-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Discord コミュニティに参加"></a>
<br><br>
<a href="https://trendshift.io/repositories/15586" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15586" alt="siteboon%2Fclaudecodeui | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<div align="right"><i><a href="./README.md">English</a> · <a href="./README.ru.md">Русский</a> · <a href="./README.de.md">Deutsch</a> · <a href="./README.ko.md">한국어</a> · <a href="./README.zh-CN.md">中文</a> · <b>日本語</b></i></div>
---
## スクリーンショット
<div align="center">
<table>
<tr>
<td align="center">
<h3>デスクトップビュー</h3>
<img src="public/screenshots/desktop-main.png" alt="デスクトップインターフェース" width="400">
<br>
<em>プロジェクト概要とチャットを表示するメイン画面</em>
</td>
<td align="center">
<h3>モバイル体験</h3>
<img src="public/screenshots/mobile-chat.png" alt="モバイルインターフェース" width="250">
<br>
<em>タッチ操作に対応したレスポンシブなモバイルデザイン</em>
</td>
</tr>
<tr>
<td align="center" colspan="2">
<h3>CLI 選択</h3>
<img src="public/screenshots/cli-selection.png" alt="CLI 選択" width="400">
<br>
<em>Claude Code、Gemini、Cursor CLI、Codex から選択</em>
</td>
</tr>
</table>
</div>
## 機能
- **レスポンシブデザイン** - デスクトップ/タブレット/モバイルでシームレスに動作し、モバイルからも Agents を利用可能
- **インタラクティブチャット UI** - Agents とスムーズにやり取りできる内蔵チャット UI
- **統合シェルターミナル** - 内蔵シェル機能で Agents の CLI に直接アクセス
- **ファイルエクスプローラー** - シンタックスハイライトとライブ編集に対応したインタラクティブなファイルツリー
- **Git エクスプローラー** - 変更の表示、ステージ、コミット。ブランチ切り替えも可能
- **セッション管理** - 会話の再開、複数セッションの管理、履歴の追跡
- **プラグインシステム** - カスタムプラグインで CloudCLI を拡張 — 新しいタブ、バックエンドサービス、連携を追加できます。[自分で構築する →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
## クイックスタート
### CloudCLI Cloud(推奨)
最速で始める方法 — ローカルのセットアップは不要です。Web、モバイルアプリ、API、またはお気に入りの IDE からアクセスできる、フルマネージドでコンテナ化された開発環境を利用できます。
**[CloudCLI Cloud を始める](https://cloudcli.ai)**
### セルフホスト(オープンソース)
**npx** で今すぐ CloudCLI UI を試せます(**Node.js** v22+ が必要):
```bash
npx @siteboon/claude-code-ui
```
または、普段使いするなら **グローバル** にインストール:
```bash
npm install -g @siteboon/claude-code-ui
cloudcli
```
`http://localhost:3001` を開いてください — 既存のセッションは自動的に検出されます。
より詳細な設定オプション、PM2、リモートサーバー設定などについては **[ドキュメントはこちら →](https://cloudcli.ai/docs)** を参照してください。
---
## どちらの選択肢が適していますか?
CloudCLI UI は、CloudCLI Cloud を支えるオープンソースの UI レイヤーです。自分のマシンにセルフホストすることも、フルマネージドのクラウド環境、チーム機能、より深い統合を備えた CloudCLI Cloud を使うこともできます。
| | CloudCLI UI(セルフホスト) | CloudCLI Cloud |
|---|---|---|
| **対象ユーザー** | 自分のマシン上でローカルの agent セッションに対してフル UI を使いたい開発者 | クラウド上で動く agents をどこからでも利用したいチーム/開発者 |
| **アクセス方法** | ブラウザ(`[yourip]:port`) | ブラウザ、任意の IDE、REST API、n8n |
| **セットアップ** | `npx @siteboon/claude-code-ui` | セットアップ不要 |
| **マシンの稼働継続** | はい | いいえ |
| **モバイルアクセス** | 同一ネットワーク内の任意のブラウザ | 任意のデバイス(ネイティブアプリも準備中) |
| **利用可能なセッション** | `~/.claude` から全セッションを自動検出 | クラウド環境内の全セッション |
| **対応エージェント** | Claude Code、Cursor CLI、Codex、Gemini CLI | Claude Code、Cursor CLI、Codex、Gemini CLI |
| **ファイルエクスプローラとGit** | はい(UI に内蔵) | はい(UI に内蔵) |
| **MCP設定** | UI で管理し、ローカルの `~/.claude` 設定と同期 | UI で管理 |
| **IDEアクセス** | ローカル IDE | クラウド環境に接続された任意の IDE |
| **REST API** | はい | はい |
| **n8n ノード** | いいえ | はい |
| **チーム共有** | いいえ | はい |
| **料金プラン** | 無料(オープンソース) | 月 $7〜 |
> どちらの選択肢でも、AI のサブスクリプション(Claude、Cursor など)はご自身のものを使用します — CloudCLI が提供するのは環境であり、AI そのものではありません。
---
## セキュリティとツール設定
**🔒 重要なお知らせ** すべての Claude Code ツールは **デフォルトで無効** です。これにより、潜在的に有害な操作が自動的に実行されることを防ぎます。
### ツールの有効化
1. **ツール設定を開く** - サイドバーの歯車アイコンをクリック
2. **必要なツールだけを選んで有効化** - 本当に使うものだけをオンにする
3. **設定を適用** - 設定内容はローカルに保存されます
<div align="center">

*Tools 設定画面 - 必要なものだけを有効にしてください*
</div>
**推奨アプローチ**: まずは基本ツールだけを有効にし、必要に応じて追加してください。これらの設定は後からいつでも調整できます。
---
## プラグイン
CloudCLI にはプラグインシステムがあり、独自のフロントエンド UI と(必要に応じて)Node.js バックエンドを持つカスタムタブを追加できます。プラグインは **Settings > Plugins** から git リポジトリを直接指定してインストールするか、自作できます。
### 利用可能なプラグイン
| プラグイン | 説明 |
|---|---|
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | 現在のプロジェクトについて、ファイル数、コード行数、ファイル種別の内訳、最大ファイル、最近変更されたファイルを表示 |
### 自作する
**[Plugin Starter Template →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** — このリポジトリを fork して独自プラグインを作れます。フロントエンド描画、ライブコンテキスト更新、バックエンドサーバーへの RPC 通信を含む動作例が入っています。
**[プラグインのドキュメント →](https://cloudcli.ai/docs/plugin-overview)** — プラグイン API、manifest 形式、セキュリティモデルなどの完全ガイド。
---
## FAQ
<details>
<summary>Claude Code Remote Control とはどう違いますか?</summary>
Claude Code Remote Control は、ローカル端末で既に動作しているセッションへメッセージを送れる仕組みです。マシンを起動したままにし、端末も開いたままにする必要があり、ネットワーク接続がない状態が約 10 分続くとセッションがタイムアウトします。
CloudCLI UI と CloudCLI Cloud は、Claude Code の横に別物として存在するのではなく、Claude Code を拡張します — MCP サーバー、権限、設定、セッションは Claude Code がネイティブに使うものと完全に同一です。複製したり、別系統で管理したりしません。
- **すべてのセッションにアクセス** — CloudCLI UI は `~/.claude` フォルダのすべてのセッションを自動検出します。Remote Control は、Claude モバイルアプリで利用可能にするため、1つのアクティブセッションだけを公開します。
- **設定はあなたの設定** — CloudCLI UI で変更した MCP サーバー、ツール権限、プロジェクト構成は、Claude Code の設定に直接書き込まれて即座に反映され、その逆(Claude Code での変更が UI に反映)も同様です。
- **対応エージェントがさらに充実** — Claude Code に加えて Cursor CLI、Codex、Gemini CLI にも対応しています。
- **チャット窓だけではない完全な UI** — ファイルエクスプローラー、Git 統合、MCP 管理、シェル端末などがすべて組み込まれています。
- **CloudCLI Cloud はクラウド上で稼働** — ノートパソコンを閉じてもエージェントは動き続けます。監視が要る端末も、スリープ防止も不要です。
</details>
<details>
<summary>AI のサブスクリプションは別途支払いが必要ですか?</summary>
はい。CloudCLI は環境を提供するものであり、AI は含まれません。Claude、Cursor、Codex、または Gemini のサブスクリプションはご自身でご用意ください。CloudCLI Cloud のホスティング環境はそれに加えて月額 $7 から提供されます。
</details>
<details>
<summary>CloudCLI UI をスマホで使えますか?</summary>
はい。セルフホストの場合は、自身のマシンでサーバーを起動し、ネットワーク内のブラウザで `[yourip]:port` を開いてください。CloudCLI Cloud を使う場合は、任意のデバイスからアクセスできます。VPN もポートフォワーディングも不要で、セットアップも不要です。ネイティブアプリも開発中です。
</details>
<details>
<summary>UI で加えた変更はローカルの Claude Code 設定に影響しますか?</summary>
はい、セルフホストの場合です。CloudCLI UI は Claude Code がネイティブに使う `~/.claude` 設定を読み書きします。UI から追加した MCP サーバーは即座に Claude Code に反映され、その逆も同様です。
</details>
---
## コミュニティとサポート
- **[ドキュメント](https://cloudcli.ai/docs)** — インストール、設定、機能、トラブルシューティング
- **[Discord](https://discord.gg/buxwujPNRE)** — ヘルプを得たり、ユーザー同士で交流したりできます
- **[GitHub Issues](https://github.com/siteboon/claudecodeui/issues)** — バグ報告と機能要望
- **[コントリビューションガイド](CONTRIBUTING.md)** — プロジェクトへの貢献方法
## ライセンス
GNU General Public License v3.0 - 詳細は [LICENSE](LICENSE) ファイルを参照してください。
このプロジェクトはオープンソースであり、GPL v3 ライセンスの下で無料で使用、修正、再配布できます。
## 謝辞
### 使用技術
- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** - Anthropic の公式 CLI
- **[Cursor CLI](https://docs.cursor.com/en/cli/overview)** - Cursor の公式 CLI
- **[Codex](https://developers.openai.com/codex)** - OpenAI Codex
- **[Gemini-CLI](https://geminicli.com/)** - Google Gemini CLI
- **[React](https://react.dev/)** - ユーザーインターフェースライブラリ
- **[Vite](https://vitejs.dev/)** - 高速ビルドツールと開発サーバー
- **[Tailwind CSS](https://tailwindcss.com/)** - ユーティリティファーストの CSS フレームワーク
- **[CodeMirror](https://codemirror.net/)** - 高度なコードエディタ
- **[TaskMaster AI](https://github.com/eyaltoledano/claude-task-master)** *(オプション)* - AI を活用したプロジェクト管理とタスク計画
## スポンサー
- [Siteboon - AI を活用したウェブサイトビルダー](https://siteboon.ai)
---
<div align="center">
<strong>Claude Code、Cursor、Codex コミュニティのために心を込めて作りました。</strong>
</div>
================================================
FILE: README.ko.md
================================================
<div align="center">
<img src="public/logo.svg" alt="CloudCLI UI" width="64" height="64">
<h1>Cloud CLI (일명 Claude Code UI)</h1>
<p><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>, <a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>, <a href="https://developers.openai.com/codex">Codex</a>, <a href="https://geminicli.com/">Gemini-CLI</a> 용 데스크톱 및 모바일 UI입니다.<br>로컬 또는 원격에서 실행하여 어디서나 활성 프로젝트와 세션을 확인하세요.</p>
</div>
<p align="center">
<a href="https://cloudcli.ai">CloudCLI Cloud</a> · <a href="https://cloudcli.ai/docs">문서</a> · <a href="https://discord.gg/buxwujPNRE">Discord</a> · <a href="https://github.com/siteboon/claudecodeui/issues">버그 신고</a> · <a href="CONTRIBUTING.md">기여 안내</a>
</p>
<p align="center">
<a href="https://cloudcli.ai"><img src="https://img.shields.io/badge/☁️_CloudCLI_Cloud-Try_Now-0066FF?style=for-the-badge" alt="CloudCLI Cloud"></a>
<a href="https://discord.gg/buxwujPNRE"><img src="https://img.shields.io/badge/Discord-Join%20Community-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Discord 커뮤니티"></a>
<br><br>
<a href="https://trendshift.io/repositories/15586" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15586" alt="siteboon%2Fclaudecodeui | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<div align="right"><i><a href="./README.md">English</a> · <a href="./README.ru.md">Русский</a> · <a href="./README.de.md">Deutsch</a> · <b>한국어</b> · <a href="./README.zh-CN.md">中文</a> · <a href="./README.ja.md">日本語</a></i></div>
---
## 스크린샷
<div align="center">
<table>
<tr>
<td align="center">
<h3>데스크톱 보기</h3>
<img src="public/screenshots/desktop-main.png" alt="데스크톱 인터페이스" width="400">
<br>
<em>프로젝트 개요와 채팅을 보여주는 메인 인터페이스</em>
</td>
<td align="center">
<h3>모바일 경험</h3>
<img src="public/screenshots/mobile-chat.png" alt="모바일 인터페이스" width="250">
<br>
<em>터치 내비게이션이 포함된 반응형 모바일 디자인</em>
</td>
</tr>
<tr>
<td align="center" colspan="2">
<h3>CLI 선택</h3>
<img src="public/screenshots/cli-selection.png" alt="CLI 선택" width="400">
<br>
<em>Claude Code, Gemini, Cursor CLI 및 Codex 중 선택</em>
</td>
</tr>
</table>
</div>
## 기능
- **반응형 디자인** - 데스크톱, 태블릿, 모바일을 아우르는 매끄러운 경험으로 어디서든 Agents를 사용할 수 있습니다
- **대화형 채팅 인터페이스** - 내장된 채팅 UI를 통해 에이전트와 자연스럽게 소통
- **통합 셸 터미널** - 셸 기능을 통해 Agents CLI에 직접 접근
- **파일 탐색기** - 구문 강조 및 실시간 편집을 갖춘 인터랙티브 파일 트리
- **Git 탐색기** - 변경 사항 보기, 스테이징 및 커밋. 브랜치 전환 기능 포함
- **세션 관리** - 대화를 재개하고, 여러 세션을 관리하며 기록을 추적
- **플러그인 시스템** - 커스텀 탭, 백엔드 서비스, 통합을 추가하여 CloudCLI 확장. [직접 빌드 →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
- **TaskMaster AI 통합** *(선택사항)* - AI 중심의 작업 계획, PRD 파싱, 워크플로 자동화를 통한 고급 프로젝트 관리
- **모델 호환성** - Claude, GPT, Gemini 모델 계열에서 작동 (`shared/modelConstants.js`에서 전체 지원 모델 확인)
## 빠른 시작
### CloudCLI Cloud (추천)
가장 빠르게 시작하는 방법 — 로컬 설정 없이도 가능합니다. 웹, 모바일 앱, API 또는 선호하는 IDE에서 이용할 수 있는 완전 관리형 컨테이너화된 개발 환경을 제공합니다.
**[CloudCLI Cloud 시작하기](https://cloudcli.ai)**
### 셀프 호스트 (오픈 소스)
**npx**로 즉시 CloudCLI UI를 실행하세요 (Node.js v22+ 필요):
```bash
npx @siteboon/claude-code-ui
```
**정기적으로 사용한다면 전역 설치:**
```bash
npm install -g @siteboon/claude-code-ui
cloudcli
```
`http://localhost:3001`을 열면 기존 세션이 자동으로 발견됩니다.
자세한 구성 옵션, PM2, 원격 서버 설정 등은 **[문서 →](https://cloudcli.ai/docs)**를 참고하세요
---
## 어느 옵션이 적합한가요?
CloudCLI UI는 CloudCLI Cloud를 구동하는 오픈 소스 UI 계층입니다. 로컬 머신에서 직접 셀프 호스트하거나, CloudCLI Cloud(완전 관리형 클라우드 환경, 팀 기능, 심화 통합 제공)를 사용할 수 있습니다.
| | CloudCLI UI (셀프 호스트) | CloudCLI Cloud |
|---|---|---|
| **적합한 대상** | 로컬 에이전트 세션을 위한 전체 UI가 필요한 개발자 | 어디서든 접근 가능한 클라우드에서 에이전트를 운영하고자 하는 팀 및 개발자 |
| **접근 방법** | `[yourip]:port`를 통해 브라우저 접속 | 브라우저, IDE, REST API, n8n |
| **설정** | `npx @siteboon/claude-code-ui` | 설정 불필요 |
| **기기 유지 필요 여부** | 예 (머신 켜둬야 함) | 아니오 |
| **모바일 접근** | 네트워크 내 브라우저 | 모든 기기 (네이티브 앱 예정) |
| **세션 접근** | `~/.claude`에서 자동 발견 | 클라우드 환경 내 세션 |
| **지원 에이전트** | Claude Code, Cursor CLI, Codex, Gemini CLI | Claude Code, Cursor CLI, Codex, Gemini CLI |
| **파일 탐색기 및 Git** | UI에 통합됨 | UI에 통합됨 |
| **MCP 구성** | UI에서 관리, 로컬 `~/.claude` 설정과 동기화됨 | UI에서 관리 |
| **IDE 접근** | 로컬 IDE | 클라우드 환경에 연결된 모든 IDE |
| **REST API** | 예 | 예 |
| **n8n 노드** | 아니오 | 예 |
| **팀 공유** | 아니오 | 예 |
| **플랫폼 비용** | 무료, 오픈 소스 | 월 $7부터 |
> 둘 다 자체 AI 구독(Claude, Cursor 등)을 그대로 사용합니다 — CloudCLI는 환경만 제공합니다.
---
## 보안 및 도구 구성
**🔒 중요 공지**: 모든 Claude Code 도구는 **기본적으로 비활성화**되어 있습니다. 이는 잠재적인 유해 작업이 자동 실행되는 것을 방지하기 위한 조치입니다.
### 도구 활성화
1. **도구 설정 열기** - 사이드바의 톱니바퀴 아이콘 클릭
2. **선택적으로 활성화** - 필요한 도구만 켜기
3. **설정 적용** - 선호도는 로컬에 저장됨
<div align="center">

*도구 설정 인터페이스 - 필요한 것만 켜세요*
</div>
**권장 방법**: 기본 도구를 먼저 켜고 필요할 때 추가하세요. 언제든지 조정 가능합니다.
---
## 플러그인
CloudCLI는 커스텀 탭과 선택적 Node.js 백엔드가 포함된 플러그인 시스템을 제공합니다. Settings > Plugins에서 Git 저장소에서 플러그인을 설치하거나 직접 빌드할 수 있습니다.
### 이용 가능한 플러그인
| 플러그인 | 설명 |
|---|---|
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | 현재 프로젝트의 파일 수, 코드 줄 수, 파일 유형 분포, 가장 큰 파일, 최근 수정 파일을 표시 |
### 직접 만들기
**[Plugin Starter Template →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** — 이 저장소를 포크하여 플러그인 구축. 프런트엔드 렌더링, 실시간 컨텍스트 업데이트, RPC 통신 예제 포함.
**[플러그인 문서 →](https://cloudcli.ai/docs/plugin-overview)** — 플러그인 API, 매니페스트 포맷, 보안 모델 등을 설명.
---
## FAQ
<details>
<summary>Claude Code Remote Control과 어떻게 다른가요?</summary>
Claude Code Remote Control은 이미 로컬 터미널에서 실행 중인 세션으로 메시지를 전송합니다. 이 경우 기계가 켜져 있어야 하고 터미널을 열어 둬야 하며, 네트워크 연결 없이 약 10분 후 타임아웃됩니다.
CloudCLI UI와 CloudCLI Cloud는 Claude Code를 확장하며 별도로 존재하지 않습니다 — MCP 서버, 권한, 설정, 세션은 Claude Code에서 그대로 사용됩니다.
- **모든 세션을 다룬다** — CloudCLI UI는 `~/.claude` 폴더에서 모든 세션을 자동 발견합니다. Remote Control은 단일 활성 세션만 노출합니다.
- **설정은 그대로** — CloudCLI UI에서 변경한 MCP, 도구 권한, 프로젝트 설정은 Claude Code에 즉시 반영됩니다.
- **지원 에이전트가 더 많음** — Claude Code, Cursor CLI, Codex, Gemini CLI 지원.
- **전체 UI 제공** — 단일 채팅 창이 아닌 파일 탐색기, Git 통합, MCP 관리 및 셸 터미널 포함.
- **CloudCLI Cloud는 클라우드에서 실행** — 노트북을 닫아도 에이전트가 실행됩니다. 터미널을 계속 확인할 필요 없음.
</details>
<details>
<summary>AI 구독을 별도로 결제해야 하나요?</summary>
네. CloudCLI는 환경만 제공합니다. Claude, Cursor, Codex, Gemini 구독 비용은 별도로 부과됩니다. CloudCLI Cloud는 관리형 환경을 월 $7부터 제공합니다.
</details>
<details>
<summary>CloudCLI UI를 휴대폰에서 사용할 수 있나요?</summary>
네. 셀프 호스트인 경우 기계에서 서버를 실행하고 네트워크의 아무 브라우저에서 `[yourip]:port`를 열면 됩니다. CloudCLI Cloud는 어떤 기기에서도 열 수 있으며, 네이티브 앱도 준비 중입니다.
</details>
<details>
<summary>UI에서 변경하면 로컬 Claude Code 설정에 영향을 주나요?</summary>
네, 셀프 호스트에서는 그렇습니다. CloudCLI UI는 Claude Code가 사용하는 동일한 `~/.claude` 설정을 읽고 씁니다. UI에서 추가한 MCP 서버가 Claude Code에 즉시 나타납니다.
</details>
---
## 커뮤니티 및 지원
- **[문서](https://cloudcli.ai/docs)** — 설치, 구성, 기능, 문제 해결 안내
- **[Discord](https://discord.gg/buxwujPNRE)** — 도움 및 커뮤니티 참여
- **[GitHub Issues](https://github.com/siteboon/claudecodeui/issues)** — 버그 보고 및 기능 요청
- **[기여 안내](CONTRIBUTING.md)** — 프로젝트 참여 방법
## 라이선스
GNU General Public License v3.0 - 자세한 내용은 [LICENSE](LICENSE) 파일 참조.
이 프로젝트는 GPL v3 라이선스 하에 오픈 소스로 공개되어 있으며 자유롭게 사용, 수정, 배포할 수 있습니다.
## 감사의 말
### 사용 기술
- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** - Anthropic 공식 CLI
- **[Cursor CLI](https://docs.cursor.com/en/cli/overview)** - Cursor 공식 CLI
- **[Codex](https://developers.openai.com/codex)** - OpenAI Codex
- **[Gemini-CLI](https://geminicli.com/)** - Google Gemini CLI
- **[React](https://react.dev/)** - 사용자 인터페이스 라이브러리
- **[Vite](https://vitejs.dev/)** - 빠른 빌드 도구 및 개발 서버
- **[Tailwind CSS](https://tailwindcss.com/)** - 유틸리티 우선 CSS 프레임워크
- **[CodeMirror](https://codemirror.net/)** - 고급 코드 에디터
- **[TaskMaster AI](https://github.com/eyaltoledano/claude-task-master)** *(선택사항)* - AI 기반 프로젝트 관리 및 작업 계획
### 스폰서
- [Siteboon - AI powered website builder](https://siteboon.ai)
---
<div align="center">
<strong>Claude Code, Cursor, Codex 커뮤니티를 위해 정성껏 제작되었습니다.</strong>
</div>
================================================
FILE: README.md
================================================
<div align="center">
<img src="public/logo.svg" alt="CloudCLI UI" width="64" height="64">
<h1>Cloud CLI (aka Claude Code UI)</h1>
<p>A desktop and mobile UI for <a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>, <a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>, <a href="https://developers.openai.com/codex">Codex</a>, and <a href="https://geminicli.com/">Gemini-CLI</a>.<br>Use it locally or remotely to view your active projects and sessions from everywhere.</p>
</div>
<p align="center">
<a href="https://cloudcli.ai">CloudCLI Cloud</a> · <a href="https://cloudcli.ai/docs">Documentation</a> · <a href="https://discord.gg/buxwujPNRE">Discord</a> · <a href="https://github.com/siteboon/claudecodeui/issues">Bug Reports</a> · <a href="CONTRIBUTING.md">Contributing</a>
</p>
<p align="center">
<a href="https://cloudcli.ai"><img src="https://img.shields.io/badge/☁️_CloudCLI_Cloud-Try_Now-0066FF?style=for-the-badge" alt="CloudCLI Cloud"></a>
<a href="https://discord.gg/buxwujPNRE"><img src="https://img.shields.io/badge/Discord-Join%20Community-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Join our Discord"></a>
<br><br>
<a href="https://trendshift.io/repositories/15586" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15586" alt="siteboon%2Fclaudecodeui | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<div align="right"><i><b>English</b> · <a href="./README.ru.md">Русский</a> · <a href="./README.de.md">Deutsch</a> · <a href="./README.ko.md">한국어</a> · <a href="./README.zh-CN.md">中文</a> · <a href="./README.ja.md">日本語</a></i></div>
---
## Screenshots
<div align="center">
<table>
<tr>
<td align="center">
<h3>Desktop View</h3>
<img src="public/screenshots/desktop-main.png" alt="Desktop Interface" width="400">
<br>
<em>Main interface showing project overview and chat</em>
</td>
<td align="center">
<h3>Mobile Experience</h3>
<img src="public/screenshots/mobile-chat.png" alt="Mobile Interface" width="250">
<br>
<em>Responsive mobile design with touch navigation</em>
</td>
</tr>
<tr>
<td align="center" colspan="2">
<h3>CLI Selection</h3>
<img src="public/screenshots/cli-selection.png" alt="CLI Selection" width="400">
<br>
<em>Select between Claude Code, Gemini, Cursor CLI and Codex</em>
</td>
</tr>
</table>
</div>
## Features
- **Responsive Design** - Works seamlessly across desktop, tablet, and mobile so you can also use Agents from mobile
- **Interactive Chat Interface** - Built-in chat interface for seamless communication with the Agents
- **Integrated Shell Terminal** - Direct access to the Agents CLI through built-in shell functionality
- **File Explorer** - Interactive file tree with syntax highlighting and live editing
- **Git Explorer** - View, stage and commit your changes. You can also switch branches
- **Session Management** - Resume conversations, manage multiple sessions, and track history
- **Plugin System** - Extend CloudCLI with custom plugins — add new tabs, backend services, and integrations. [Build your own →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
- **TaskMaster AI Integration** *(Optional)* - Advanced project management with AI-powered task planning, PRD parsing, and workflow automation
- **Model Compatibility** - Works with Claude, GPT, and Gemini model families (see [`shared/modelConstants.js`](shared/modelConstants.js) for the full list of supported models)
## Quick Start
### CloudCLI Cloud (Recommended)
The fastest way to get started — no local setup required. Get a fully managed, containerized development environment accessible from the web, mobile app, API, or your favorite IDE.
**[Get started with CloudCLI Cloud](https://cloudcli.ai)**
### Self-Hosted (Open source)
Try CloudCLI UI instantly with **npx** (requires **Node.js** v22+):
```
npx @siteboon/claude-code-ui
```
Or install **globally** for regular use:
```
npm install -g @siteboon/claude-code-ui
cloudcli
```
Open `http://localhost:3001` — all your existing sessions are discovered automatically.
Visit the **[documentation →](https://cloudcli.ai/docs)** for more full configuration options, PM2, remote server setup and more
---
## Which option is right for you?
CloudCLI UI is the open source UI layer that powers CloudCLI Cloud. You can self-host it on your own machine, or use CloudCLI Cloud which builds on top of it with a full managed cloud environment, team features, and deeper integrations.
| | CloudCLI UI (Self-hosted) | CloudCLI Cloud |
|---|---|---|
| **Best for** | Developers who want a full UI for local agent sessions on their own machine | Teams and developers who want agents running in the cloud, accessible from anywhere |
| **How you access it** | Browser via `[yourip]:port` | Browser, any IDE, REST API, n8n |
| **Setup** | `npx @siteboon/claude-code-ui` | No setup required |
| **Machine needs to stay on** | Yes | No |
| **Mobile access** | Any browser on your network | Any device, native app coming |
| **Sessions available** | All sessions auto-discovered from `~/.claude` | All sessions within your cloud environment |
| **Agents supported** | Claude Code, Cursor CLI, Codex, Gemini CLI | Claude Code, Cursor CLI, Codex, Gemini CLI |
| **File explorer and Git** | Yes, built into the UI | Yes, built into the UI |
| **MCP configuration** | Managed via UI, synced with your local `~/.claude` config | Managed via UI |
| **IDE access** | Your local IDE | Any IDE connected to your cloud environment |
| **REST API** | Yes | Yes |
| **n8n node** | No | Yes |
| **Team sharing** | No | Yes |
| **Platform cost** | Free, open source | Starts at $7/month |
> Both options use your own AI subscriptions (Claude, Cursor, etc.) — CloudCLI provides the environment, not the AI.
---
## Security & Tools Configuration
**🔒 Important Notice**: All Claude Code tools are **disabled by default**. This prevents potentially harmful operations from running automatically.
### Enabling Tools
To use Claude Code's full functionality, you'll need to manually enable tools:
1. **Open Tools Settings** - Click the gear icon in the sidebar
2. **Enable Selectively** - Turn on only the tools you need
3. **Apply Settings** - Your preferences are saved locally
<div align="center">

*Tools Settings interface - enable only what you need*
</div>
**Recommended approach**: Start with basic tools enabled and add more as needed. You can always adjust these settings later.
---
## Plugins
CloudCLI has a plugin system that lets you add custom tabs with their own frontend UI and optional Node.js backend. Install plugins from git repos directly in **Settings > Plugins**, or build your own.
### Available Plugins
| Plugin | Description |
|---|---|
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | Shows file counts, lines of code, file-type breakdown, largest files, and recently modified files for your current project |
| **[Web Terminal](https://github.com/cloudcli-ai/cloudcli-plugin-terminal)** | Full xterm.js terminal with multi-tab support|
### Build Your Own
**[Plugin Starter Template →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** — fork this repo to create your own plugin. It includes a working example with frontend rendering, live context updates, and RPC communication to a backend server.
**[Plugin Documentation →](https://cloudcli.ai/docs/plugin-overview)** — full guide to the plugin API, manifest format, security model, and more.
---
## FAQ
<details>
<summary>How is this different from Claude Code Remote Control?</summary>
Claude Code Remote Control lets you send messages to a session already running in your local terminal. Your machine has to stay on, your terminal has to stay open, and sessions time out after roughly 10 minutes without a network connection.
CloudCLI UI and CloudCLI Cloud extend Claude Code rather than sit alongside it — your MCP servers, permissions, settings, and sessions are the exact same ones Claude Code uses natively. Nothing is duplicated or managed separately.
Here's what that means in practice:
- **All your sessions, not just one** — CloudCLI UI auto-discovers every session from your `~/.claude` folder. Remote Control only exposes the single active session to make it available in the Claude mobile app.
- **Your settings are your settings** — MCP servers, tool permissions, and project config you change in CloudCLI UI are written directly to your Claude Code config and take effect immediately, and vice versa.
- **Works with more agents** — Claude Code, Cursor CLI, Codex, and Gemini CLI, not just Claude Code.
- **Full UI, not just a chat window** — file explorer, Git integration, MCP management, and a shell terminal are all built in.
- **CloudCLI Cloud runs in the cloud** — close your laptop, the agent keeps running. No terminal to babysit, no machine to keep awake.
</details>
<details>
<summary>Do I need to pay for an AI subscription separately?</summary>
Yes. CloudCLI provides the environment, not the AI. You bring your own Claude, Cursor, Codex, or Gemini subscription. CloudCLI Cloud starts at $7/month for the hosted environment on top of that.
</details>
<details>
<summary>Can I use CloudCLI UI on my phone?</summary>
Yes. For self-hosted, run the server on your machine and open `[yourip]:port` in any browser on your network. For CloudCLI Cloud, open it from any device — no VPN, no port forwarding, no setup. A native app is also in the works.
</details>
<details>
<summary>Will changes I make in the UI affect my local Claude Code setup?</summary>
Yes, for self-hosted. CloudCLI UI reads from and writes to the same `~/.claude` config that Claude Code uses natively. MCP servers you add via the UI show up in Claude Code immediately and vice versa.
</details>
---
## Community & Support
- **[Documentation](https://cloudcli.ai/docs)** — installation, configuration, features, and troubleshooting
- **[Discord](https://discord.gg/buxwujPNRE)** — get help and connect with other users
- **[GitHub Issues](https://github.com/siteboon/claudecodeui/issues)** — bug reports and feature requests
- **[Contributing Guide](CONTRIBUTING.md)** — how to contribute to the project
## License
GNU General Public License v3.0 - see [LICENSE](LICENSE) file for details.
This project is open source and free to use, modify, and distribute under the GPL v3 license.
## Acknowledgments
### Built With
- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** - Anthropic's official CLI
- **[Cursor CLI](https://docs.cursor.com/en/cli/overview)** - Cursor's official CLI
- **[Codex](https://developers.openai.com/codex)** - OpenAI Codex
- **[Gemini-CLI](https://geminicli.com/)** - Google Gemini CLI
- **[React](https://react.dev/)** - User interface library
- **[Vite](https://vitejs.dev/)** - Fast build tool and dev server
- **[Tailwind CSS](https://tailwindcss.com/)** - Utility-first CSS framework
- **[CodeMirror](https://codemirror.net/)** - Advanced code editor
- **[TaskMaster AI](https://github.com/eyaltoledano/claude-task-master)** *(Optional)* - AI-powered project management and task planning
### Sponsors
- [Siteboon - AI powered website builder](https://siteboon.ai)
---
<div align="center">
<strong>Made with care for the Claude Code, Cursor and Codex community.</strong>
</div>
================================================
FILE: README.ru.md
================================================
<div align="center">
<img src="public/logo.svg" alt="CloudCLI UI" width="64" height="64">
<h1>Cloud CLI (aka Claude Code UI)</h1>
<p>Десктопный и мобильный UI для <a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>, <a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>, <a href="https://developers.openai.com/codex">Codex</a> и <a href="https://geminicli.com/">Gemini-CLI</a>.<br>Используйте локально или удалённо, чтобы просматривать активные проекты и сессии отовсюду.</p>
</div>
<p align="center">
<a href="https://cloudcli.ai">CloudCLI Cloud</a> · <a href="https://cloudcli.ai/docs">Документация</a> · <a href="https://discord.gg/buxwujPNRE">Discord</a> · <a href="https://github.com/siteboon/claudecodeui/issues">Сообщить об ошибке</a> · <a href="CONTRIBUTING.md">Участие в разработке</a>
</p>
<p align="center">
<a href="https://cloudcli.ai"><img src="https://img.shields.io/badge/☁️_CloudCLI_Cloud-Try_Now-0066FF?style=for-the-badge" alt="CloudCLI Cloud"></a>
<a href="https://discord.gg/buxwujPNRE"><img src="https://img.shields.io/badge/Discord-Join%20Community-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="Join our Discord"></a>
<br><br>
<a href="https://trendshift.io/repositories/15586" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15586" alt="siteboon%2Fclaudecodeui | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<div align="right"><i><a href="./README.md">English</a> · <b>Русский</b> · <a href="./README.de.md">Deutsch</a> · <a href="./README.ko.md">한국어</a> · <a href="./README.zh-CN.md">中文</a> · <a href="./README.ja.md">日本語</a></i></div>
---
## Скриншоты
<div align="center">
<table>
<tr>
<td align="center">
<h3>Версия для десктопа</h3>
<img src="public/screenshots/desktop-main.png" alt="Desktop Interface" width="400">
<br>
<em>Основной интерфейс с обзором проекта и чатом</em>
</td>
<td align="center">
<h3>Мобильный режим</h3>
<img src="public/screenshots/mobile-chat.png" alt="Mobile Interface" width="250">
<br>
<em>Адаптивный мобильный дизайн с сенсорной навигацией</em>
</td>
</tr>
<tr>
<td align="center" colspan="2">
<h3>Выбор CLI</h3>
<img src="public/screenshots/cli-selection.png" alt="CLI Selection" width="400">
<br>
<em>Выбирайте между Claude Code, Gemini, Cursor CLI и Codex</em>
</td>
</tr>
</table>
</div>
## Возможности
- **Адаптивный дизайн** - одинаково хорошо работает на десктопе, планшете и телефоне, поэтому можно пользоваться агентами и с мобильных устройств
- **Интерактивный чат-интерфейс** - встроенный чат для бесшовного общения с агентами
- **Интегрированный shell-терминал** - прямой доступ к CLI агентов через встроенную оболочку
- **Проводник файлов** - интерактивное дерево файлов с подсветкой синтаксиса и редактированием в реальном времени
- **Git Explorer** - просмотр, stage и commit изменений. Также можно переключать ветки
- **Управление сессиями** - возобновляйте диалоги, управляйте несколькими сессиями и отслеживайте историю
- **Система плагинов** - расширяйте CloudCLI кастомными плагинами — добавляйте новые вкладки, бэкенд-сервисы и интеграции. [Создать свой →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
- **Интеграция с TaskMaster AI** *(опционально)* - продвинутое управление проектами с планированием задач на базе AI, разбором PRD и автоматизацией workflow
- **Совместимость с моделями** - работает с семействами моделей Claude, GPT и Gemini (см. [`shared/modelConstants.js`](shared/modelConstants.js) для полного списка поддерживаемых моделей)
## Быстрый старт
### CloudCLI Cloud (рекомендуется)
Самый быстрый способ начать — локальная настройка не требуется. Получите полностью управляемую контейнеризированную среду разработки с доступом из веба, мобильного приложения, API или вашей любимой IDE.
**[Начать с CloudCLI Cloud](https://cloudcli.ai)**
### Self-Hosted (Open source)
Попробовать CloudCLI UI можно сразу через **npx** (требуется **Node.js** v22+):
```bash
npx @siteboon/claude-code-ui
```
Или установить **глобально** для регулярного использования:
```bash
npm install -g @siteboon/claude-code-ui
cloudcli
```
Откройте `http://localhost:3001` — все ваши существующие сессии будут обнаружены автоматически.
Посетите **[документацию →](https://cloudcli.ai/docs)**, чтобы узнать про дополнительные варианты конфигурации, PM2, настройку удалённого сервера и многое другое
---
## Какой вариант подходит вам?
CloudCLI UI — это open source UI-слой, на котором построен CloudCLI Cloud. Вы можете развернуть его на своей машине или использовать CloudCLI Cloud, который добавляет полностью управляемую облачную среду, командные функции и более глубокие интеграции.
| | CloudCLI UI (Self-hosted) | CloudCLI Cloud |
|---|---|---|
| **Лучше всего подходит для** | Разработчиков, которым нужен полноценный UI для локальных агентских сессий на своей машине | Команд и разработчиков, которым нужны агенты в облаке с доступом откуда угодно |
| **Как вы получаете доступ** | Браузер через `[yourip]:port` | Браузер, любая IDE, REST API, n8n |
| **Настройка** | `npx @siteboon/claude-code-ui` | Настройка не требуется |
| **Машина должна оставаться включённой** | Да | Нет |
| **Доступ с мобильных устройств** | Любой браузер в вашей сети | Любое устройство, нативное приложение в разработке |
| **Доступные сессии** | Все сессии автоматически обнаруживаются из `~/.claude` | Все сессии внутри вашей облачной среды |
| **Поддерживаемые агенты** | Claude Code, Cursor CLI, Codex, Gemini CLI | Claude Code, Cursor CLI, Codex, Gemini CLI |
| **Проводник файлов и Git** | Да, встроены в UI | Да, встроены в UI |
| **Конфигурация MCP** | Управляется через UI, синхронизируется с вашим локальным конфигом `~/.claude` | Управляется через UI |
| **Доступ из IDE** | Ваша локальная IDE | Любая IDE, подключенная к вашей облачной среде |
| **REST API** | Да | Да |
| **n8n node** | Нет | Да |
| **Совместная работа** | Нет | Да |
| **Стоимость платформы** | Бесплатно, open source | От $7/месяц |
> В обоих вариантах используются ваши собственные AI-подписки (Claude, Cursor и т.д.) — CloudCLI предоставляет среду, а не сам AI.
---
## Безопасность и конфигурация инструментов
**🔒 Важное примечание**: все инструменты Claude Code **по умолчанию отключены**. Это предотвращает автоматический запуск потенциально опасных операций.
### Включение инструментов
Чтобы использовать всю функциональность Claude Code, вам нужно вручную включить инструменты:
1. **Откройте настройки инструментов** - нажмите на иконку шестерёнки в боковой панели
2. **Включайте выборочно** - активируйте только те инструменты, которые вам нужны
3. **Примените настройки** - ваши предпочтения сохраняются локально
<div align="center">

*Интерфейс настройки инструментов — включайте только то, что вам нужно*
</div>
**Рекомендуемый подход**: начните с базовых инструментов и добавляйте остальные по мере необходимости. Эти настройки всегда можно изменить позже.
---
## Плагины
У CloudCLI есть система плагинов, которая позволяет добавлять кастомные вкладки со своим frontend UI и (опционально) Node.js бэкендом. Устанавливайте плагины напрямую из git-репозиториев в **Settings > Plugins** или создавайте свои.
### Доступные плагины
| Плагин | Описание |
|---|---|
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | Показывает количество файлов, строки кода, разбивку по типам файлов, самые большие файлы и недавно изменённые файлы для текущего проекта |
### Создать свой
**[Plugin Starter Template →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** — сделайте форк этого репозитория, чтобы создать свой плагин. В шаблоне есть рабочий пример с рендерингом на фронтенде, live-обновлением контекста и RPC-коммуникацией с бэкенд-сервером.
**[Plugin Documentation →](https://cloudcli.ai/docs/plugin-overview)** — полный гайд по plugin API, формату манифеста, модели безопасности и другому.
---
## FAQ
<details>
<summary>Чем это отличается от Claude Code Remote Control?</summary>
Claude Code Remote Control позволяет отправлять сообщения в сессию, которая уже запущена в вашем локальном терминале. Ваша машина должна оставаться включённой, терминал — открытым, а сессии завершаются примерно через 10 минут без сетевого соединения.
CloudCLI UI и CloudCLI Cloud расширяют Claude Code, а не работают рядом с ним — ваши MCP-серверы, разрешения, настройки и сессии остаются теми же самыми, что и в нативном Claude Code. Ничего не дублируется и не управляется отдельно.
Вот что это означает на практике:
- **Все ваши сессии, а не одна** — CloudCLI UI автоматически находит каждую сессию из папки `~/.claude`. Remote Control предоставляет только одну активную сессию, чтобы сделать её доступной в мобильном приложении Claude.
- **Ваши настройки — это ваши настройки** — MCP-серверы, права инструментов и конфигурация проекта, изменённые в CloudCLI UI, записываются напрямую в конфиг Claude Code и вступают в силу сразу же, и наоборот.
- **Работает с большим числом агентов** — Claude Code, Cursor CLI, Codex и Gemini CLI, а не только Claude Code.
- **Полноценный UI, а не просто окно чата** — проводник файлов, Git-интеграция, управление MCP и shell-терминал — всё встроено.
- **CloudCLI Cloud работает в облаке** — закройте ноутбук, и агент продолжит работать. Не нужно следить за терминалом и держать машину постоянно активной.
</details>
<details>
<summary>Нужно ли отдельно платить за AI-подписку?</summary>
Да. CloudCLI предоставляет среду, а не сам AI. Вы приносите свою подписку Claude, Cursor, Codex или Gemini. CloudCLI Cloud начинается от $7/месяц за хостируемую среду поверх этого.
</details>
<details>
<summary>Можно ли пользоваться CloudCLI UI с телефона?</summary>
Да. Для self-hosted запустите сервер на своей машине и откройте `[yourip]:port` в любом браузере в вашей сети. Для CloudCLI Cloud откройте сервис с любого устройства — без VPN, проброса портов и дополнительной настройки. Нативное приложение тоже в разработке.
</details>
<details>
<summary>Повлияют ли изменения, сделанные в UI, на мой локальный Claude Code?</summary>
Да, в self-hosted режиме. CloudCLI UI читает и записывает тот же конфиг `~/.claude`, который Claude Code использует нативно. MCP-серверы, добавленные через UI, сразу появляются в Claude Code, и наоборот.
</details>
---
## Сообщество и поддержка
- **[Документация](https://cloudcli.ai/docs)** — установка, настройка, возможности и устранение неполадок
- **[Discord](https://discord.gg/buxwujPNRE)** — помощь и общение с другими пользователями
- **[GitHub Issues](https://github.com/siteboon/claudecodeui/issues)** — сообщения об ошибках и запросы новых функций
- **[Руководство для контрибьюторов](CONTRIBUTING.md)** — как участвовать в развитии проекта
## Лицензия
GNU General Public License v3.0 - подробности в файле [LICENSE](LICENSE).
Этот проект open source и бесплатен для использования, модификации и распространения в рамках лицензии GPL v3.
## Благодарности
### Используется
- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** - официальный CLI от Anthropic
- **[Cursor CLI](https://docs.cursor.com/en/cli/overview)** - официальный CLI от Cursor
- **[Codex](https://developers.openai.com/codex)** - OpenAI Codex
- **[Gemini-CLI](https://geminicli.com/)** - Google Gemini CLI
- **[React](https://react.dev/)** - библиотека пользовательских интерфейсов
- **[Vite](https://vitejs.dev/)** - быстрый инструмент сборки и dev-сервер
- **[Tailwind CSS](https://tailwindcss.com/)** - utility-first CSS framework
- **[CodeMirror](https://codemirror.net/)** - продвинутый редактор кода
- **[TaskMaster AI](https://github.com/eyaltoledano/claude-task-master)** *(опционально)* - AI-управление проектами и планирование задач
### Спонсоры
- [Siteboon - AI powered website builder](https://siteboon.ai)
---
<div align="center">
<strong>Сделано с заботой для сообщества Claude Code, Cursor и Codex.</strong>
</div>
================================================
FILE: README.zh-CN.md
================================================
<div align="center">
<img src="public/logo.svg" alt="CloudCLI UI" width="64" height="64">
<h1>Cloud CLI(又名 Claude Code UI)</h1>
<p><a href="https://docs.anthropic.com/en/docs/claude-code">Claude Code</a>、<a href="https://docs.cursor.com/en/cli/overview">Cursor CLI</a>、<a href="https://developers.openai.com/codex">Codex</a> 和 <a href="https://geminicli.com/">Gemini-CLI</a> 的桌面和移动端 UI。可在本地或远程使用,从任何地方查看激活的项目与会话。</p>
</div>
<p align="center">
<a href="https://cloudcli.ai">CloudCLI Cloud</a> · <a href="https://cloudcli.ai/docs">文档</a> · <a href="https://discord.gg/buxwujPNRE">Discord</a> · <a href="https://github.com/siteboon/claudecodeui/issues">Bug 报告</a> · <a href="CONTRIBUTING.md">贡献指南</a>
</p>
<p align="center">
<a href="https://cloudcli.ai"><img src="https://img.shields.io/badge/☁️_CloudCLI_Cloud-Try_Now-0066FF?style=for-the-badge" alt="CloudCLI Cloud"></a>
<a href="https://discord.gg/buxwujPNRE"><img src="https://img.shields.io/badge/Discord-Join%20Community-5865F2?style=for-the-badge&logo=discord&logoColor=white" alt="加入 Discord 社区"></a>
<br><br>
<a href="https://trendshift.io/repositories/15586" target="_blank"><img src="https://trendshift.io/api/badge/repositories/15586" alt="siteboon%2Fclaudecodeui | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
</p>
<div align="right"><i><a href="./README.md">English</a> · <a href="./README.ru.md">Русский</a> · <a href="./README.de.md">Deutsch</a> · <a href="./README.ko.md">한국어</a> · <b>中文</b> · <a href="./README.ja.md">日本語</a></i></div>
---
## 截图
<div align="center">
<table>
<tr>
<td align="center">
<h3>桌面视图</h3>
<img src="public/screenshots/desktop-main.png" alt="桌面界面" width="400">
<br>
<em>显示项目概览和聊天的主界面</em>
</td>
<td align="center">
<h3>移动体验</h3>
<img src="public/screenshots/mobile-chat.png" alt="移动界面" width="250">
<br>
<em>具有触控导航的响应式移动设计</em>
</td>
</tr>
<tr>
<td align="center" colspan="2">
<h3>CLI 选择</h3>
<img src="public/screenshots/cli-selection.png" alt="CLI 选择" width="400">
<br>
<em>在 Claude Code、Gemini、Cursor CLI 与 Codex 之间进行选择</em>
</td>
</tr>
</table>
</div>
## 功能
- **响应式设计** - 在桌面、平板和移动设备上无缝运行,让您随时随地使用 Agents
- **交互聊天界面** - 内置聊天 UI,轻松与 Agents 交流
- **集成 Shell 终端** - 通过内置 shell 功能直接访问 Agents CLI
- **文件浏览器** - 交互式文件树,支持语法高亮与实时编辑
- **Git 浏览器** - 查看、暂存并提交更改,还可切换分支
- **会话管理** - 恢复对话、管理多个会话并跟踪历史记录
- **插件系统** - 通过自定义选项卡、后端服务与集成扩展 CloudCLI。 [开始构建 →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)
- **TaskMaster AI 集成** *(可选)* - 结合 AI 任务规划、PRD 分析与工作流自动化,实现高级项目管理
- **模型兼容性** - 支持 Claude、GPT、Gemini 模型家族(完整支持列表见 [`shared/modelConstants.js`](shared/modelConstants.js))
## 快速开始
### CloudCLI Cloud(推荐)
无需本地设置即可快速启动。提供可通过网络浏览器、移动应用、API 或喜欢的 IDE 访问的完全集装式托管开发环境。
**[立即开始 CloudCLI Cloud](https://cloudcli.ai)**
### 自托管(开源)
启动 CloudCLI UI,只需一行 `npx`(需要 Node.js v22+):
```bash
npx @siteboon/claude-code-ui
```
或进行全局安装,便于日常使用:
```bash
npm install -g @siteboon/claude-code-ui
cloudcli
```
打开 `http://localhost:3001`,系统会自动发现所有现有会话。
更多配置选项、PM2、远程服务器设置等,请参阅 **[文档 →](https://cloudcli.ai/docs)**
---
## 哪个选项更适合你?
CloudCLI UI 是 CloudCLI Cloud 的开源 UI 层。你可以在本地机器上自托管它,也可以使用提供团队功能与深入集成的 CloudCLI Cloud。
| | CloudCLI UI(自托管) | CloudCLI Cloud |
|---|---|---|
| **适合对象** | 需要为本地代理会话提供完整 UI 的开发者 | 需要部署在云端,随时从任何地方访问代理的团队与开发者 |
| **访问方式** | 通过 `[yourip]:port` 在浏览器中访问 | 浏览器、任意 IDE、REST API、n8n |
| **设置** | `npx @siteboon/claude-code-ui` | 无需设置 |
| **机器需保持开机吗** | 是 | 否 |
| **移动端访问** | 网络内任意浏览器 | 任意设备(原生应用即将推出) |
| **可用会话** | 自动发现 `~/.claude` 中的所有会话 | 云端环境内的会话 |
| **支持的 Agents** | Claude Code、Cursor CLI、Codex、Gemini CLI | Claude Code、Cursor CLI、Codex、Gemini CLI |
| **文件浏览与 Git** | 内置于 UI | 内置于 UI |
| **MCP 配置** | UI 管理,与本地 `~/.claude` 配置同步 | UI 管理 |
| **IDE 访问** | 本地 IDE | 任何连接到云环境的 IDE |
| **REST API** | 是 | 是 |
| **n8n 节点** | 否 | 是 |
| **团队共享** | 否 | 是 |
| **平台费用** | 免费开源 | 起价 $7/月 |
> 两种方式都使用你自己的 AI 订阅(Claude、Cursor 等)— CloudCLI 提供环境,而非 AI。
---
## 安全与工具配置
**🔒 重要提示**: 所有 Claude Code 工具默认**禁用**,可防止潜在的有害操作自动运行。
### 启用工具
1. **打开工具设置** - 点击侧边栏齿轮图标
2. **选择性启用** - 仅启用所需工具
3. **应用设置** - 偏好设置保存在本地
<div align="center">

*工具设置界面 - 只启用你需要的内容*
</div>
**推荐做法**: 先启用基础工具,再根据需要添加其他工具。随时可以调整。
---
## 插件
CloudCLI 配备插件系统,允许你添加带自定义前端 UI 和可选 Node.js 后端的选项卡。在 Settings > Plugins 中直接从 Git 仓库安装插件,或自行开发。
### 可用插件
| 插件 | 描述 |
|---|---|
| **[Project Stats](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** | 展示当前项目的文件数、代码行数、文件类型分布、最大文件以及最近修改的文件 |
### 自行构建
**[Plugin Starter Template →](https://github.com/cloudcli-ai/cloudcli-plugin-starter)** — Fork 该仓库以构建自己的插件。示例包括前端渲染、实时上下文更新和 RPC 通信。
**[插件文档 →](https://cloudcli.ai/docs/plugin-overview)** — 提供插件 API、清单格式、安全模型等完整指南。
---
## 常见问题
<details>
<summary>与 Claude Code Remote Control 有何不同?</summary>
Claude Code Remote Control 让你发送消息到本地终端中已经运行的会话。该方式要求你的机器保持开机,终端保持开启,断开网络后约 10 分钟会话会超时。
CloudCLI UI 与 CloudCLI Cloud 是对 Claude Code 的扩展,而非旁观 — MCP 服务器、权限、设置、会话与 Claude Code 完全一致。
- **覆盖全部会话** — CloudCLI UI 会自动扫描 `~/.claude` 文件夹中的每个会话。Remote Control 只暴露当前活动的会话。
- **设置统一** — 在 CloudCLI UI 中修改的 MCP、工具权限等设置会立即写入 Claude Code。
- **支持更多 Agents** — Claude Code、Cursor CLI、Codex、Gemini CLI。
- **完整 UI** — 除了聊天界面,还包括文件浏览器、Git 集成、MCP 管理和 Shell 终端。
- **CloudCLI Cloud 保持运行于云端** — 关闭本地设备也不会中断代理运行,无需监控终端。
</details>
<details>
<summary>需要额外购买 AI 订阅吗?</summary>
需要。CloudCLI 只提供环境。你仍需自行获取 Claude、Cursor、Codex 或 Gemini 订阅。CloudCLI Cloud 从 $7/月起提供托管环境。
</details>
<details>
<summary>能在手机上使用 CloudCLI UI 吗?</summary>
可以。自托管时,在你的设备上运行服务器,然后在网络中的任意浏览器打开 `[yourip]:port`。CloudCLI Cloud 可从任意设备访问,内置原生应用也在开发中。
</details>
<details>
<summary>UI 中的更改会影响本地 Claude Code 配置吗?</summary>
会的。自托管模式下,CloudCLI UI 读取并写入 Claude Code 使用的 `~/.claude` 配置。通过 UI 添加的 MCP 服务器会立即在 Claude Code 中可见。
</details>
---
## 社区与支持
- **[文档](https://cloudcli.ai/docs)** — 安装、配置、功能与故障排除指南
- **[Discord](https://discord.gg/buxwujPNRE)** — 获取帮助并与社区交流
- **[GitHub Issues](https://github.com/siteboon/claudecodeui/issues)** — 报告 Bug 与建议功能
- **[贡献指南](CONTRIBUTING.md)** — 如何参与项目贡献
## 许可证
GNU 通用公共许可证 v3.0 - 详见 [LICENSE](LICENSE) 文件。
该项目为开源软件,在 GPL v3 许可证下可自由使用、修改与分发。
## 致谢
### 使用技术
- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** - Anthropic 官方 CLI
- **[Cursor CLI](https://docs.cursor.com/en/cli/overview)** - Cursor 官方 CLI
- **[Codex](https://developers.openai.com/codex)** - OpenAI Codex
- **[Gemini-CLI](https://geminicli.com/)** - Google Gemini CLI
- **[React](https://react.dev/)** - 用户界面库
- **[Vite](https://vitejs.dev/)** - 快速构建工具与开发服务器
- **[Tailwind CSS](https://tailwindcss.com/)** - 实用先行 CSS 框架
- **[CodeMirror](https://codemirror.net/)** - 高级代码编辑器
- **[TaskMaster AI](https://github.com/eyaltoledano/claude-task-master)** *(可选)* - AI 驱动的项目管理与任务规划
### 赞助商
- [Siteboon - AI powered website builder](https://siteboon.ai)
---
<div align="center">
<strong>为 Claude Code、Cursor 和 Codex 社区精心打造。</strong>
</div>
================================================
FILE: commitlint.config.js
================================================
export default {
extends: ["@commitlint/config-conventional"],
};
================================================
FILE: eslint.config.js
================================================
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import importX from "eslint-plugin-import-x";
import tailwindcss from "eslint-plugin-tailwindcss";
import unusedImports from "eslint-plugin-unused-imports";
import globals from "globals";
export default tseslint.config(
{
ignores: ["dist/**", "node_modules/**", "public/**"],
},
{
files: ["src/**/*.{ts,tsx,js,jsx}"],
extends: [js.configs.recommended, ...tseslint.configs.recommended],
plugins: {
react,
"react-hooks": reactHooks, // for following React rules such as dependencies in hooks, keys in lists, etc.
"react-refresh": reactRefresh, // for Vite HMR compatibility
"import-x": importX, // for import order/sorting. It also detercts circular dependencies and duplicate imports.
tailwindcss, // for detecting invalid Tailwind classnames and enforcing classname order
"unused-imports": unusedImports, // for detecting unused imports
},
languageOptions: {
globals: {
...globals.browser,
},
parserOptions: {
ecmaFeatures: { jsx: true },
},
},
settings: {
react: { version: "detect" },
},
rules: {
// --- Unused imports/vars ---
"unused-imports/no-unused-imports": "warn",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": "off",
// --- React ---
"react/jsx-key": "warn",
"react/jsx-no-duplicate-props": "error",
"react/jsx-no-undef": "error",
"react/no-children-prop": "warn",
"react/no-danger-with-children": "error",
"react/no-direct-mutation-state": "error",
"react/no-unknown-property": "warn",
"react/react-in-jsx-scope": "off",
// --- React Hooks ---
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
// --- React Refresh (Vite HMR) ---
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
// --- Import ordering & hygiene ---
"import-x/no-duplicates": "warn",
"import-x/order": [
"warn",
{
groups: [
"builtin",
"external",
"internal",
"parent",
"sibling",
"index",
],
"newlines-between": "never",
},
],
// --- Tailwind CSS ---
"tailwindcss/classnames-order": "warn",
"tailwindcss/no-contradicting-classname": "warn",
"tailwindcss/no-unnecessary-arbitrary-value": "warn",
// --- Disabled base rules ---
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-require-imports": "off",
"no-case-declarations": "off",
"no-control-regex": "off",
"no-useless-escape": "off",
},
}
);
================================================
FILE: index.html
================================================
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" href="/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>CloudCLI UI</title>
<!-- PWA Manifest -->
<link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<!-- iOS Safari PWA Meta Tags -->
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Claude UI" />
<!-- iOS Safari Icons -->
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-192x192.png" />
<!-- Theme Color -->
<meta name="theme-color" content="#ffffff" />
<meta name="msapplication-TileColor" content="#ffffff" />
<!-- Prevent zoom on iOS -->
<meta name="format-detection" content="telephone=no" />
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
<!-- Service Worker Registration -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(registration => {
console.log('SW registered: ', registration);
})
.catch(registrationError => {
console.log('SW registration failed: ', registrationError);
});
});
}
</script>
</body>
</html>
================================================
FILE: package.json
================================================
{
"name": "@siteboon/claude-code-ui",
"version": "1.26.0",
"description": "A web-based UI for Claude Code CLI",
"type": "module",
"main": "server/index.js",
"bin": {
"claude-code-ui": "server/cli.js",
"cloudcli": "server/cli.js"
},
"files": [
"server/",
"shared/",
"dist/",
"scripts/",
"README.md"
],
"homepage": "https://cloudcli.ai",
"repository": {
"type": "git",
"url": "git+https://github.com/siteboon/claudecodeui.git"
},
"bugs": {
"url": "https://github.com/siteboon/claudecodeui/issues"
},
"scripts": {
"dev": "concurrently --kill-others \"npm run server\" \"npm run client\"",
"server": "node server/index.js",
"client": "vite",
"build": "vite build",
"preview": "vite preview",
"typecheck": "tsc --noEmit -p tsconfig.json",
"lint": "eslint src/",
"lint:fix": "eslint src/ --fix",
"start": "npm run build && npm run server",
"release": "./release.sh",
"prepublishOnly": "npm run build",
"postinstall": "node scripts/fix-node-pty.js",
"prepare": "husky"
},
"keywords": [
"claude code",
"ai",
"anthropic",
"ui",
"mobile"
],
"author": "CloudCLI UI Contributors",
"license": "GPL-3.0",
"dependencies": {
"@anthropic-ai/claude-agent-sdk": "^0.2.59",
"@codemirror/lang-css": "^6.3.1",
"@codemirror/lang-html": "^6.4.9",
"@codemirror/lang-javascript": "^6.2.4",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-markdown": "^6.3.3",
"@codemirror/lang-python": "^6.2.1",
"@codemirror/merge": "^6.11.1",
"@codemirror/theme-one-dark": "^6.1.2",
"@iarna/toml": "^2.2.5",
"@octokit/rest": "^22.0.0",
"@openai/codex-sdk": "^0.101.0",
"@replit/codemirror-minimap": "^0.5.2",
"@tailwindcss/typography": "^0.5.16",
"@uiw/react-codemirror": "^4.23.13",
"@xterm/addon-clipboard": "^0.1.0",
"@xterm/addon-fit": "^0.10.0",
"@xterm/addon-web-links": "^0.11.0",
"@xterm/addon-webgl": "^0.18.0",
"@xterm/xterm": "^5.5.0",
"bcrypt": "^6.0.0",
"better-sqlite3": "^12.6.2",
"chokidar": "^4.0.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cors": "^2.8.5",
"cross-spawn": "^7.0.3",
"express": "^4.18.2",
"fuse.js": "^7.0.0",
"gray-matter": "^4.0.3",
"i18next": "^25.7.4",
"i18next-browser-languagedetector": "^8.2.0",
"jsonwebtoken": "^9.0.2",
"jszip": "^3.10.1",
"katex": "^0.16.25",
"lucide-react": "^0.515.0",
"mime-types": "^3.0.1",
"multer": "^2.0.1",
"node-fetch": "^2.7.0",
"node-pty": "^1.1.0-beta34",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-dropzone": "^14.2.3",
"react-error-boundary": "^4.1.2",
"react-i18next": "^16.5.3",
"react-markdown": "^10.1.0",
"react-router-dom": "^6.8.1",
"react-syntax-highlighter": "^15.6.1",
"rehype-katex": "^7.0.1",
"rehype-raw": "^7.0.0",
"remark-gfm": "^4.0.0",
"remark-math": "^6.0.0",
"sqlite": "^5.1.1",
"sqlite3": "^5.1.7",
"tailwind-merge": "^3.3.1",
"web-push": "^3.6.7",
"ws": "^8.14.2"
},
"devDependencies": {
"@commitlint/cli": "^20.4.3",
"@commitlint/config-conventional": "^20.4.3",
"@eslint/js": "^9.39.3",
"@release-it/conventional-changelog": "^10.0.5",
"@types/node": "^22.19.7",
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.6.0",
"auto-changelog": "^2.5.0",
"autoprefixer": "^10.4.16",
"concurrently": "^8.2.2",
"eslint": "^9.39.3",
"eslint-plugin-import-x": "^4.16.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"eslint-plugin-tailwindcss": "^3.18.2",
"eslint-plugin-unused-imports": "^4.4.1",
"globals": "^17.4.0",
"husky": "^9.1.7",
"lint-staged": "^16.3.2",
"node-gyp": "^10.0.0",
"postcss": "^8.4.32",
"release-it": "^19.0.5",
"sharp": "^0.34.2",
"tailwindcss": "^3.4.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.56.1",
"vite": "^7.0.4"
},
"lint-staged": {
"src/**/*.{ts,tsx,js,jsx}": "eslint"
}
}
================================================
FILE: postcss.config.js
================================================
export default {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
================================================
FILE: public/api-docs.html
================================================
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claude Code UI - API Documentation</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" type="image/png" href="/favicon.png" />
<!-- Prism.js for syntax highlighting -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;
--primary: #2563eb;
--primary-dark: #1d4ed8;
--green: #10b981;
--red: #ef4444;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: var(--gray-900);
background: var(--gray-50);
margin: 0;
}
header {
background: white;
border-bottom: 1px solid var(--gray-200);
padding: 1.5rem 0;
position: sticky;
top: 0;
z-index: 100;
}
.header-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 2rem;
}
.brand {
display: flex;
align-items: center;
gap: 0.75rem;
}
.brand-icon {
width: 32px;
height: 32px;
background: var(--primary);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
.brand-icon svg {
width: 16px;
height: 16px;
stroke: white;
}
.brand-text h1 {
font-size: 1.25rem;
font-weight: 700;
color: var(--gray-900);
}
.brand-text .subtitle {
font-size: 0.875rem;
color: var(--gray-600);
}
.back-link {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--primary);
color: white;
text-decoration: none;
border-radius: 6px;
font-size: 0.875rem;
font-weight: 500;
transition: background 0.2s;
}
.back-link:hover {
background: var(--primary-dark);
}
.back-link svg {
width: 16px;
height: 16px;
}
.main-layout {
display: flex;
}
.sidebar {
width: 240px;
background: white;
border-right: 1px solid var(--gray-200);
padding: 2rem 0;
position: sticky;
top: 73px;
height: calc(100vh - 73px);
overflow-y: auto;
flex-shrink: 0;
}
.sidebar-title {
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
color: var(--gray-600);
padding: 0 1.5rem;
margin: 1.5rem 0 0.5rem;
}
.sidebar a {
display: block;
padding: 0.625rem 1.5rem;
color: var(--gray-700);
text-decoration: none;
font-size: 0.875rem;
transition: all 0.15s;
border-left: 3px solid transparent;
}
.sidebar a:hover {
background: var(--gray-50);
color: var(--primary);
border-left-color: var(--primary);
}
.content-wrapper {
flex: 1;
display: flex;
flex-direction: column;
min-height: calc(100vh - 73px);
}
.section-row {
display: grid;
grid-template-columns: 1fr 600px;
}
.docs-section {
padding: 3rem 3rem;
background: white;
border-right: 1px solid var(--gray-200);
}
.examples-section {
padding: 3rem 2rem;
background: #0d1117;
color: #e6edf3;
}
.examples-section h4 {
color: #e6edf3;
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 0.05em;
}
h2 {
font-size: 2rem;
font-weight: 700;
margin-bottom: 1rem;
color: var(--gray-900);
}
h3 {
font-size: 1.375rem;
font-weight: 600;
margin: 2.5rem 0 1rem;
color: var(--gray-900);
}
h4 {
font-size: 1rem;
font-weight: 600;
margin: 1.5rem 0 0.75rem;
color: var(--gray-700);
}
p {
margin-bottom: 1rem;
color: var(--gray-600);
}
.intro {
background: linear-gradient(135deg, rgba(37, 99, 235, 0.08) 0%, rgba(59, 130, 246, 0.08) 100%);
border: 1px solid rgba(37, 99, 235, 0.2);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 2rem;
}
.intro p {
color: var(--gray-700);
margin: 0;
}
.endpoint {
margin: 2rem 0;
padding: 1.5rem;
background: var(--gray-50);
border-radius: 8px;
border: 1px solid var(--gray-200);
}
.endpoint-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 1rem;
}
.method {
padding: 0.375rem 0.875rem;
border-radius: 6px;
font-weight: 700;
font-size: 0.75rem;
text-transform: uppercase;
}
.method-post {
background: var(--green);
color: white;
}
.endpoint-path {
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.9375rem;
font-weight: 600;
}
table {
width: 100%;
border-collapse: collapse;
margin: 1rem 0;
font-size: 0.875rem;
}
th {
text-align: left;
padding: 0.875rem;
background: var(--gray-100);
border: 1px solid var(--gray-200);
font-weight: 600;
color: var(--gray-800);
}
td {
padding: 0.875rem;
border: 1px solid var(--gray-200);
color: var(--gray-700);
}
code {
background: rgba(37, 99, 235, 0.08);
padding: 0.1875rem 0.5rem;
border-radius: 4px;
font-family: 'Monaco', 'Menlo', monospace;
font-size: 0.875em;
color: var(--primary-dark);
}
.api-url {
color: #60a5fa;
}
.badge {
display: inline-block;
padding: 0.1875rem 0.625rem;
border-radius: 12px;
font-size: 0.6875rem;
font-weight: 600;
text-transform: uppercase;
}
.badge-required {
background: var(--red);
color: white;
}
.badge-optional {
background: var(--gray-200);
color: var(--gray-700);
}
.note {
padding: 1.25rem;
background: rgba(37, 99, 235, 0.05);
border-left: 4px solid var(--primary);
border-radius: 8px;
margin: 1rem 0;
font-size: 0.875rem;
}
/* Code tabs in side panel */
.tab-buttons {
display: flex;
gap: 0.5rem;
margin-bottom: 1rem;
}
.tab-button {
padding: 0.5rem 1rem;
background: transparent;
border: 1px solid #30363d;
cursor: pointer;
font-size: 0.8125rem;
font-weight: 500;
color: #7d8590;
border-radius: 6px;
transition: all 0.2s;
}
.tab-button:hover {
color: #e6edf3;
border-color: #58a6ff;
}
.tab-button.active {
color: #e6edf3;
background: #1f6feb;
border-color: #1f6feb;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
pre[class*="language-"] {
margin: 0 0 1.5rem 0;
border-radius: 6px;
font-size: 0.8125rem;
}
.example-block {
margin-bottom: 2rem;
}
@media (max-width: 1400px) {
.section-row {
grid-template-columns: 1fr 500px;
}
}
@media (max-width: 1200px) {
.section-row {
grid-template-columns: 1fr;
}
.examples-section {
border-top: 1px solid #30363d;
}
}
@media (max-width: 768px) {
.main-layout {
flex-direction: column;
}
.sidebar {
width: 100%;
position: relative;
height: auto;
border-right: none;
border-bottom: 1px solid var(--gray-200);
}
.docs-section {
padding: 2rem 1.5rem;
}
.examples-section {
padding: 2rem 1.5rem;
}
}
</style>
</head>
<body>
<header>
<div class="header-content">
<div class="brand">
<div class="brand-icon">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"/>
</svg>
</div>
<div class="brand-text">
<h1>Claude Code UI</h1>
<div class="subtitle">API Documentation</div>
</div>
</div>
<a href="/" class="back-link">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
</svg>
Back to App
</a>
</div>
</header>
<div class="main-layout">
<nav class="sidebar">
<div class="sidebar-title">Getting Started</div>
<a href="#authentication">Authentication</a>
<a href="#credentials">GitHub Credentials</a>
<div class="sidebar-title">API Reference</div>
<a href="#agent">Agent</a>
<div class="sidebar-title">Examples</div>
<a href="#usage-examples">Usage Patterns</a>
</nav>
<div class="content-wrapper">
<!-- Intro Section -->
<div class="section-row">
<div class="docs-section">
<div class="intro">
<p><strong>Programmatically trigger AI agents to work on projects.</strong> Clone GitHub repositories or use existing project paths. Perfect for CI/CD pipelines, automated code reviews, and bulk processing.</p>
</div>
<section id="authentication">
<h2>Authentication</h2>
<p>All API requests require authentication using an API key in the <code>X-API-Key</code> header.</p>
<p>Generate API keys in Settings → API & Tokens.</p>
</section>
<section id="credentials">
<h3>GitHub Credentials</h3>
<p>For private repositories, store a GitHub token in settings or pass it with each request.</p>
<div class="note">
<strong>Note:</strong> GitHub tokens in the request override stored tokens.
</div>
</section>
</div>
<div class="examples-section">
<div class="example-block">
<h4>Authentication Header</h4>
<pre><code class="language-http">X-API-Key: ck_your_api_key_here</code></pre>
</div>
</div>
</div>
<!-- Agent API Section -->
<div class="section-row">
<div class="docs-section">
<section id="agent">
<h2>Agent</h2>
<div class="endpoint">
<div class="endpoint-header">
<span class="method method-post">POST</span>
<span class="endpoint-path"><span class="api-url">http://localhost:3001</span>/api/agent</span>
</div>
<p>Trigger an AI agent (Claude, Cursor, or Codex) to work on a project.</p>
<h4>Request Body Parameters</h4>
<table>
<thead>
<tr>
<th>Parameter</th>
<th>Type</th>
<th>Required</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>githubUrl</code></td>
<td>string</td>
<td><span class="badge badge-optional">Conditional</span></td>
<td>GitHub repository URL to clone. If path exists with same repo, reuses it. If path exists with different repo, returns error.</td>
</tr>
<tr>
<td><code>projectPath</code></td>
<td>string</td>
<td><span class="badge badge-optional">Conditional</span></td>
<td>Path to existing project OR destination for cloning. If omitted with <code>githubUrl</code>, auto-generates path. If used alone, must point to existing project directory.</td>
</tr>
<tr>
<td><code>message</code></td>
<td>string</td>
<td><span class="badge badge-required">Required</span></td>
<td>Task for the AI agent</td>
</tr>
<tr>
<td><code>provider</code></td>
<td>string</td>
<td><span class="badge badge-optional">Optional</span></td>
<td><code>claude</code>, <code>cursor</code>, or <code>codex</code> (default: <code>claude</code>)</td>
</tr>
<tr>
<td><code>stream</code></td>
<td>boolean</td>
<td><span class="badge badge-optional">Optional</span></td>
<td>Enable streaming (default: <code>true</code>)</td>
</tr>
<tr>
<td><code>model</code></td>
<td>string</td>
<td><span class="badge badge-optional">Optional</span></td>
<td id="model-options-cell">
Model identifier for the AI provider (loading from constants...)
</td>
</tr>
<tr>
<td><code>cleanup</code></td>
<td>boolean</td>
<td><span class="badge badge-optional">Optional</span></td>
<td>Auto-cleanup after completion (default: <code>true</code>). Only applies when cloning via <code>githubUrl</code>. Existing projects specified via <code>projectPath</code> are never cleaned up.</td>
</tr>
<tr>
<td><code>githubToken</code></td>
<td>string</td>
<td><span class="badge badge-optional">Optional</span></td>
<td>GitHub token for private repos</td>
</tr>
<tr>
<td><code>branchName</code></td>
<td>string</td>
<td><span class="badge badge-optional">Optional</span></td>
<td>Custom branch name to use. If provided, <code>createBranch</code> is automatically enabled. Branch names are validated against Git naming rules. Works with <code>githubUrl</code> or <code>projectPath</code> (if it has a GitHub remote).</td>
</tr>
<tr>
<td><code>createBranch</code></td>
<td>boolean</td>
<td><span class="badge badge-optional">Optional</span></td>
<td>Create a new branch after successful completion (default: <code>false</code>). Automatically set to <code>true</code> if <code>branchName</code> is provided. Works with <code>githubUrl</code> or <code>projectPath</code> (if it has a GitHub remote).</td>
</tr>
<tr>
<td><code>createPR</code></td>
<td>boolean</td>
<td><span class="badge badge-optional">Optional</span></td>
<td>Create a pull request after successful completion (default: <code>false</code>). PR title and description auto-generated from commit messages. Works with <code>githubUrl</code> or <code>projectPath</code> (if it has a GitHub remote).</td>
</tr>
</tbody>
</table>
<div class="note">
<strong>Path Handling Behavior:</strong><br><br>
<strong>Scenario 1:</strong> Only <code>githubUrl</code> → Clones to auto-generated temporary path<br>
<strong>Scenario 2:</strong> Only <code>projectPath</code> → Uses existing project at specified path<br>
<strong>Scenario 3:</strong> Both provided → Clones <code>githubUrl</code> to <code>projectPath</code><br><br>
<strong>Validation:</strong> If <code>projectPath</code> exists and contains a git repository, the remote URL is compared with <code>githubUrl</code>. If URLs match, the existing repo is reused. If URLs differ, an error is returned.
</div>
<h4>Response (Streaming)</h4>
<p>Server-sent events (SSE) format with real-time updates. Content-Type: <code>text/event-stream</code></p>
<h4>Response (Non-Streaming)</h4>
<p>JSON object containing session details, assistant messages only (filtered), and token usage summary. Content-Type: <code>application/json</code></p>
<h4>Error Response</h4>
<p>Returns error details with appropriate HTTP status code.</p>
</div>
</section>
</div>
<div class="examples-section">
<div class="example-block">
<h4>Basic Request</h4>
<div class="tab-buttons">
<button class="tab-button active" onclick="showTab('curl-basic')">cURL</button>
<button class="tab-button" onclick="showTab('js-basic')">JavaScript</button>
<button class="tab-button" onclick="showTab('python-basic')">Python</button>
</div>
<div class="tab-content active" id="curl-basic">
<pre><code class="language-bash">curl -X POST <span class="api-url">http://localhost:3001</span>/api/agent \
-H "Content-Type: application/json" \
-H "X-API-Key: ck_..." \
-d '{
"githubUrl": "https://github.com/user/repo",
"message": "Add error handling to main.js"
}'</code></pre>
</div>
<div class="tab-content" id="js-basic">
<pre><code class="language-javascript">const response = await fetch('<span class="api-url">http://localhost:3001</span>/api/agent', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.CLAUDE_API_KEY
},
body: JSON.stringify({
githubUrl: 'https://github.com/user/repo',
message: 'Add error handling',
stream: false
})
});
const result = await response.json();</code></pre>
</div>
<div class="tab-content" id="python-basic">
<pre><code class="language-python">import requests
import os
response = requests.post(
'<span class="api-url">http://localhost:3001</span>/api/agent',
headers={
'Content-Type': 'application/json',
'X-API-Key': os.environ['CLAUDE_API_KEY']
},
json={
'githubUrl': 'https://github.com/user/repo',
'message': 'Add error handling',
'stream': False
}
)
print(response.json())</code></pre>
</div>
</div>
<div class="example-block">
<h4>Streaming Response</h4>
<pre><code class="language-javascript">data: {"type":"status","message":"Repository cloned"}
data: {"type":"thinking","content":"Analyzing..."}
data: {"type":"tool_use","tool":"read_file"}
data: {"type":"content","content":"Done!"}
data: {"type":"done"}</code></pre>
</div>
<div class="example-block">
<h4>Non-Streaming Response</h4>
<pre><code class="language-json">{
"success": true,
"sessionId": "abc123",
"messages": [
{
"type": "assistant",
"message": {
"role": "assistant",
"content": [
{
"type": "text",
"text": "I've completed the task..."
}
],
"usage": {
"input_tokens": 150,
"output_tokens": 50
}
}
}
],
"tokens": {
"inputTokens": 150,
"outputTokens": 50,
"cacheReadTokens": 0,
"cacheCreationTokens": 0,
"totalTokens": 200
},
"projectPath": "/path/to/project",
"branch": {
"name": "fix-authentication-bug-abc123",
"url": "https://github.com/user/repo/tree/fix-authentication-bug-abc123"
},
"pullRequest": {
"number": 42,
"url": "https://github.com/user/repo/pull/42"
}
}</code></pre>
</div>
<div class="example-block">
<h4>Error Response</h4>
<pre><code class="language-json">{
"success": false,
"error": "Directory exists with different repo"
}</code></pre>
</div>
</div>
</div>
<!-- Usage Patterns Section -->
<div class="section-row">
<div class="docs-section">
<section id="usage-examples">
<h2>Usage Patterns</h2>
<h3>Clone and Process Repository</h3>
<p>Clone a repository to an auto-generated temporary path and process it.</p>
<h3>Use Existing Project</h3>
<p>Work with an existing project at a specific path.</p>
<h3>Clone to Specific Path</h3>
<p>Clone a repository to a custom location for later reuse.</p>
<h3>CI/CD Integration</h3>
<p>Integrate with GitHub Actions or other CI/CD pipelines.</p>
<h3>Create Branch and Pull Request</h3>
<p>Automatically create a new branch and pull request after the agent completes its work. Branch names are auto-generated from the message, and PR title/description are auto-generated from commit messages.</p>
</section>
</div>
<div class="examples-section">
<div class="example-block">
<h4>Use Existing Project</h4>
<pre><code class="language-bash">curl -X POST <span class="api-url">http://localhost:3001</span>/api/agent \
-H "Content-Type: application/json" \
-H "X-API-Key: ck_..." \
-d '{
"projectPath": "/home/user/my-project",
"message": "Refactor database queries"
}'</code></pre>
</div>
<div class="example-block">
<h4>Clone to Custom Path</h4>
<pre><code class="language-bash">curl -X POST <span class="api-url">http://localhost:3001</span>/api/agent \
-H "Content-Type: application/json" \
-H "X-API-Key: ck_..." \
-d '{
"githubUrl": "https://github.com/user/repo",
"projectPath": "/tmp/my-location",
"message": "Review security",
"cleanup": false
}'</code></pre>
</div>
<div class="example-block">
<h4>CI/CD (GitHub Actions)</h4>
<pre><code class="language-yaml">- name: Trigger Agent
run: |
curl -X POST ${{ secrets.API_URL }}/api/agent \
-H "X-API-Key: ${{ secrets.API_KEY }}" \
-H "Content-Type: application/json" \
-d '{
"githubUrl": "${{ github.repository }}",
"message": "Review for security",
"githubToken": "${{ secrets.GITHUB_TOKEN }}"
}'</code></pre>
</div>
<div class="example-block">
<h4>Create Branch and PR</h4>
<pre><code class="language-bash">curl -X POST <span class="api-url">http://localhost:3001</span>/api/agent \
-H "Content-Type: application/json" \
-H "X-API-Key: ck_..." \
-d '{
"githubUrl": "https://github.com/user/repo",
"message": "Fix authentication bug",
"createBranch": true,
"createPR": true,
"stream": false
}'</code></pre>
</div>
<div class="example-block">
<h4>Custom Branch Name</h4>
<pre><code class="language-bash">curl -X POST <span class="api-url">http://localhost:3001</span>/api/agent \
-H "Content-Type: application/json" \
-H "X-API-Key: ck_..." \
-d '{
"githubUrl": "https://github.com/user/repo",
"message": "Add user authentication",
"branchName": "feature/user-auth",
"createPR": true,
"stream": false
}'</code></pre>
</div>
<div class="example-block">
<h4>Branch & PR Response</h4>
<pre><code class="language-json">{
"success": true,
"branch": {
"name": "feature/user-auth",
"url": "https://github.com/user/repo/tree/feature/user-auth"
},
"pullRequest": {
"number": 42,
"url": "https://github.com/user/repo/pull/42"
}
}</code></pre>
</div>
</div>
</div>
</div>
</div>
<script type="module">
// Import model constants
import { CLAUDE_MODELS, CURSOR_MODELS, CODEX_MODELS } from '/shared/modelConstants.js';
// Dynamic URL replacement
const apiUrl = window.location.origin;
document.querySelectorAll('.api-url').forEach(el => {
el.textContent = apiUrl;
});
// Dynamically populate model documentation
window.addEventListener('DOMContentLoaded', () => {
const modelCell = document.getElementById('model-options-cell');
if (modelCell) {
const claudeModels = CLAUDE_MODELS.OPTIONS.map(m => `<code>${m.value}</code>`).join(', ');
const cursorModels = CURSOR_MODELS.OPTIONS.slice(0, 8).map(m => `<code>${m.value}</code>`).join(', ');
const codexModels = CODEX_MODELS.OPTIONS.map(m => `<code>${m.value}</code>`).join(', ');
modelCell.innerHTML = `
Model identifier for the AI provider:<br><br>
<strong>Claude:</strong> ${claudeModels} (default: <code>${CLAUDE_MODELS.DEFAULT}</code>)<br><br>
<strong>Cursor:</strong> ${cursorModels}, and more (default: <code>${CURSOR_MODELS.DEFAULT}</code>)<br><br>
<strong>Codex:</strong> ${codexModels} (default: <code>${CODEX_MODELS.DEFAULT}</code>)
`;
}
});
// Tab switching
window.showTab = function(tabName) {
const parentBlock = event.target.closest('.example-block');
if (!parentBlock) return;
parentBlock.querySelectorAll('.tab-content').forEach(tab => {
tab.classList.remove('active');
});
parentBlock.querySelectorAll('.tab-button').forEach(btn => {
btn.classList.remove('active');
});
const targetTab = parentBlock.querySelector('#' + tabName);
if (targetTab) {
targetTab.classList.add('active');
event.target.classList.add('active');
}
};
</script>
<!-- Prism.js -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-bash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-json.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-yaml.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-http.min.js"></script>
</body>
</html>
================================================
FILE: public/clear-cache.html
================================================
<!DOCTYPE html>
<html>
<head>
<title>Clear Cache - Claude Code UI</title>
<style>
body {
font-family: system-ui, -apple-system, sans-serif;
max-width: 600px;
margin: 50px auto;
padding: 20px;
line-height: 1.6;
}
.success { color: green; }
.error { color: red; }
button {
background: #007bff;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
margin: 10px 5px;
}
button:hover {
background: #0056b3;
}
#status {
margin-top: 20px;
padding: 15px;
border-radius: 5px;
background: #f0f0f0;
}
</style>
</head>
<body>
<h1>Clear Cache & Service Worker</h1>
<p>If you're seeing a blank page or old content, click the button below to clear all cached data.</p>
<button onclick="clearEverything()">Clear Cache & Reload</button>
<div id="status"></div>
<script>
async function clearEverything() {
const status = document.getElementById('status');
status.innerHTML = '<p>Clearing cache and service workers...</p>';
try {
// Unregister all service workers
if ('serviceWorker' in navigator) {
const registrations = await navigator.serviceWorker.getRegistrations();
for (let registration of registrations) {
await registration.unregister();
status.innerHTML += '<p class="success">✓ Unregistered service worker</p>';
}
}
// Clear all caches
if ('caches' in window) {
const cacheNames = await caches.keys();
for (let cacheName of cacheNames) {
await caches.delete(cacheName);
status.innerHTML += `<p class="success">✓ Deleted cache: ${cacheName}</p>`;
}
}
// Clear localStorage
localStorage.clear();
status.innerHTML += '<p class="success">✓ Cleared localStorage</p>';
// Clear sessionStorage
sessionStorage.clear();
status.innerHTML += '<p class="success">✓ Cleared sessionStorage</p>';
status.innerHTML += '<p class="success"><strong>✓ All caches cleared!</strong></p>';
status.innerHTML += '<p>Cache cleared successfully. You can now close this tab or <a href="/">go to home page</a>.</p>';
} catch (error) {
status.innerHTML += `<p class="error">✗ Error: ${error.message}</p>`;
}
}
</script>
</body>
</html>
================================================
FILE: public/convert-icons.md
================================================
# Convert SVG Icons to PNG
I've created SVG versions of the app icons that match the MessageSquare design from the sidebar. To convert them to PNG format, you can use one of these methods:
## Method 1: Online Converter (Easiest)
1. Go to https://cloudconvert.com/svg-to-png
2. Upload each SVG file from the `/icons/` directory
3. Download the PNG versions
4. Replace the existing PNG files
## Method 2: Using Node.js (if you have it)
```bash
npm install sharp
node -e "
const sharp = require('sharp');
const fs = require('fs');
const sizes = [72, 96, 128, 144, 152, 192, 384, 512];
sizes.forEach(size => {
const svgPath = \`./icons/icon-\${size}x\${size}.svg\`;
const pngPath = \`./icons/icon-\${size}x\${size}.png\`;
if (fs.existsSync(svgPath)) {
sharp(svgPath).png().toFile(pngPath);
console.log(\`Converted \${svgPath} to \${pngPath}\`);
}
});
"
```
## Method 3: Using ImageMagick (if installed)
```bash
cd public/icons
for size in 72 96 128 144 152 192 384 512; do
convert "icon-${size}x${size}.svg" "icon-${size}x${size}.png"
done
```
## Method 4: Using Inkscape (if installed)
```bash
cd public/icons
for size in 72 96 128 144 152 192 384 512; do
inkscape --export-type=png "icon-${size}x${size}.svg"
done
```
## Icon Design
The new icons feature:
- Clean MessageSquare (chat bubble) design matching the sidebar
- Primary color background with rounded corners
- White stroke icon that's clearly visible
- Consistent sizing and proportions across all sizes
- Proper PWA-compliant format
Once converted, the PNG files will replace the existing ones and provide a consistent icon experience across all platforms.
================================================
FILE: public/generate-icons.js
================================================
const fs = require('fs');
const path = require('path');
// Icon sizes needed
const sizes = [72, 96, 128, 144, 152, 192, 384, 512];
// SVG template function
function createIconSVG(size) {
const cornerRadius = Math.round(size * 0.25); // 25% corner radius
const strokeWidth = Math.max(2, Math.round(size * 0.06)); // Scale stroke width
// MessageSquare path scaled to size
const padding = Math.round(size * 0.25);
const iconSize = size - (padding * 2);
const startX = padding;
const startY = Math.round(padding * 0.7);
const endX = startX + iconSize;
const endY = startY + Math.round(iconSize * 0.6);
const tailX = startX;
const tailY = endY + Math.round(iconSize * 0.3);
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Background with rounded corners -->
<rect width="${size}" height="${size}" rx="${cornerRadius}" fill="hsl(262.1 83.3% 57.8%)"/>
<!-- MessageSquare icon -->
<path d="M${startX} ${startY}C${startX} ${startY - 10} ${startX + 10} ${startY - 20} ${startX + 20} ${startY - 20}H${endX - 20}C${endX - 10} ${startY - 20} ${endX} ${startY - 10} ${endX} ${startY}V${endY - 20}C${endX} ${endY - 10} ${endX - 10} ${endY} ${endX - 20} ${endY}H${startX + Math.round(iconSize * 0.4)}L${tailX} ${tailY}V${startY}Z"
stroke="white"
stroke-width="${strokeWidth}"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"/>
</svg>`;
}
// Generate SVG files for each size
sizes.forEach(size => {
const svgContent = createIconSVG(size);
const filename = `icon-${size}x${size}.svg`;
const filepath = path.join(__dirname, 'icons', filename);
fs.writeFileSync(filepath, svgContent);
console.log(`Created ${filename}`);
});
console.log('\nSVG icons created! To convert to PNG, you can use:');
console.log('1. Online converter like cloudconvert.com');
console.log('2. If you have ImageMagick: convert icon.svg icon.png');
console.log('3. If you have Inkscape: inkscape --export-type=png icon.svg');
================================================
FILE: public/manifest.json
================================================
{
"name": "CloudCLI UI",
"short_name": "CloudCLI UI",
"description": "CloudCLI UI web application",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#ffffff",
"orientation": "portrait-primary",
"scope": "/",
"icons": [
{
"src": "/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png",
"purpose": "maskable any"
},
{
"src": "/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable any"
}
]
}
================================================
FILE: public/sw.js
================================================
// Service Worker for Claude Code UI PWA
const CACHE_NAME = 'claude-ui-v1';
const urlsToCache = [
'/',
'/index.html',
'/manifest.json'
];
// Install event
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
return cache.addAll(urlsToCache);
})
);
self.skipWaiting();
});
// Fetch event
self.addEventListener('fetch', event => {
// Never cache API requests or WebSocket upgrades
if (event.request.url.includes('/api/') || event.request.url.includes('/ws')) {
return;
}
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});
// Activate event
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
self.clients.claim();
});
// Push notification event
self.addEventListener('push', event => {
if (!event.data) return;
let payload;
try {
payload = event.data.json();
} catch {
payload = { title: 'Claude Code UI', body: event.data.text() };
}
const options = {
body: payload.body || '',
icon: '/logo-256.png',
badge: '/logo-128.png',
data: payload.data || {},
tag: payload.data?.tag || `${payload.data?.sessionId || 'global'}:${payload.data?.code || 'default'}`,
renotify: true
};
event.waitUntil(
self.registration.showNotification(payload.title || 'Claude Code UI', options)
);
});
// Notification click event
self.addEventListener('notificationclick', event => {
event.notification.close();
const sessionId = event.notification.data?.sessionId;
const provider = event.notification.data?.provider || null;
const urlPath = sessionId ? `/session/${sessionId}` : '/';
event.waitUntil(
self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then(async clientList => {
for (const client of clientList) {
if (client.url.includes(self.location.origin)) {
await client.focus();
client.postMessage({
type: 'notification:navigate',
sessionId: sessionId || null,
provider,
urlPath
});
return;
}
}
return self.clients.openWindow(urlPath);
})
);
});
================================================
FILE: release.sh
================================================
#!/bin/bash
# Load environment variables from .env
export $(grep -v '^#' .env | grep '^GITHUB_TOKEN=' | xargs)
exec npx release-it "$@"
================================================
FILE: scripts/fix-node-pty.js
================================================
#!/usr/bin/env node
/**
* Fix node-pty spawn-helper permissions on macOS
*
* This script fixes a known issue with node-pty where the spawn-helper
* binary is shipped without execute permissions, causing "posix_spawnp failed" errors.
*
* @see https://github.com/microsoft/node-pty/issues/850
* @module scripts/fix-node-pty
*/
import { promises as fs } from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* Fixes the spawn-helper binary permissions for node-pty on macOS.
*
* The node-pty package ships the spawn-helper binary without execute permissions
* (644 instead of 755), which causes "posix_spawnp failed" errors when trying
* to spawn terminal processes.
*
* This function:
* 1. Checks if running on macOS (darwin)
* 2. Locates spawn-helper binaries for both arm64 and x64 architectures
* 3. Sets execute permissions (755) on each binary found
*
* @async
* @function fixSpawnHelper
* @returns {Promise<void>} Resolves when permissions are fixed or skipped
* @example
* // Run as postinstall script
* await fixSpawnHelper();
*/
async function fixSpawnHelper() {
const nodeModulesPath = path.join(__dirname, '..', 'node_modules', 'node-pty', 'prebuilds');
// Only run on macOS
if (process.platform !== 'darwin') {
return;
}
const darwinDirs = ['darwin-arm64', 'darwin-x64'];
for (const dir of darwinDirs) {
const spawnHelperPath = path.join(nodeModulesPath, dir, 'spawn-helper');
try {
// Check if file exists
await fs.access(spawnHelperPath);
// Make it executable (755)
await fs.chmod(spawnHelperPath, 0o755);
console.log(`[postinstall] Fixed permissions for ${spawnHelperPath}`);
} catch (err) {
// File doesn't exist or other error - ignore
if (err.code !== 'ENOENT') {
console.warn(`[postinstall] Warning: Could not fix ${spawnHelperPath}: ${err.message}`);
}
}
}
}
fixSpawnHelper().catch(console.error);
================================================
FILE: server/claude-sdk.js
================================================
/**
* Claude SDK Integration
*
* This module provides SDK-based integration with Claude using the @anthropic-ai/claude-agent-sdk.
* It mirrors the interface of claude-cli.js but uses the SDK internally for better performance
* and maintainability.
*
* Key features:
* - Direct SDK integration without child processes
* - Session management with abort capability
* - Options mapping between CLI and SDK formats
* - WebSocket message streaming
*/
import { query } from '@anthropic-ai/claude-agent-sdk';
import crypto from 'crypto';
import { promises as fs } from 'fs';
import path from 'path';
import os from 'os';
import { CLAUDE_MODELS } from '../shared/modelConstants.js';
import {
createNotificationEvent,
notifyRunFailed,
notifyRunStopped,
notifyUserIfEnabled
} from './services/notification-orchestrator.js';
import { claudeAdapter } from './providers/claude/adapter.js';
import { createNormalizedMessage } from './providers/types.js';
const activeSessions = new Map();
const pendingToolApprovals = new Map();
const TOOL_APPROVAL_TIMEOUT_MS = parseInt(process.env.CLAUDE_TOOL_APPROVAL_TIMEOUT_MS, 10) || 55000;
const TOOLS_REQUIRING_INTERACTION = new Set(['AskUserQuestion']);
function createRequestId() {
if (typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
return crypto.randomBytes(16).toString('hex');
}
function waitForToolApproval(requestId, options = {}) {
const { timeoutMs = TOOL_APPROVAL_TIMEOUT_MS, signal, onCancel, metadata } = options;
return new Promise(resolve => {
let settled = false;
const finalize = (decision) => {
if (settled) return;
settled = true;
cleanup();
resolve(decision);
};
let timeout;
const cleanup = () => {
pendingToolApprovals.delete(requestId);
if (timeout) clearTimeout(timeout);
if (signal && abortHandler) {
signal.removeEventListener('abort', abortHandler);
}
};
// timeoutMs 0 = wait indefinitely (interactive tools)
if (timeoutMs > 0) {
timeout = setTimeout(() => {
onCancel?.('timeout');
finalize(null);
}, timeoutMs);
}
const abortHandler = () => {
onCancel?.('cancelled');
finalize({ cancelled: true });
};
if (signal) {
if (signal.aborted) {
onCancel?.('cancelled');
finalize({ cancelled: true });
return;
}
signal.addEventListener('abort', abortHandler, { once: true });
}
const resolver = (decision) => {
finalize(decision);
};
// Attach metadata for getPendingApprovalsForSession lookup
if (metadata) {
Object.assign(resolver, metadata);
}
pendingToolApprovals.set(requestId, resolver);
});
}
function resolveToolApproval(requestId, decision) {
const resolver = pendingToolApprovals.get(requestId);
if (resolver) {
resolver(decision);
}
}
// Match stored permission entries against a tool + input combo.
// This only supports exact tool names and the Bash(command:*) shorthand
// used by the UI; it intentionally does not implement full glob semantics,
// introduced to stay consistent with the UI's "Allow rule" format.
function matchesToolPermission(entry, toolName, input) {
if (!entry || !toolName) {
return false;
}
if (entry === toolName) {
return true;
}
const bashMatch = entry.match(/^Bash\((.+):\*\)$/);
if (toolName === 'Bash' && bashMatch) {
const allowedPrefix = bashMatch[1];
let command = '';
if (typeof input === 'string') {
command = input.trim();
} else if (input && typeof input === 'object' && typeof input.command === 'string') {
command = input.command.trim();
}
if (!command) {
return false;
}
return command.startsWith(allowedPrefix);
}
return false;
}
/**
* Maps CLI options to SDK-compatible options format
* @param {Object} options - CLI options
* @returns {Object} SDK-compatible options
*/
function mapCliOptionsToSDK(options = {}) {
const { sessionId, cwd, toolsSettings, permissionMode } = options;
const sdkOptions = {};
// Map working directory
if (cwd) {
sdkOptions.cwd = cwd;
}
// Map permission mode
if (permissionMode && permissionMode !== 'default') {
sdkOptions.permissionMode = permissionMode;
}
// Map tool settings
const settings = toolsSettings || {
allowedTools: [],
disallowedTools: [],
skipPermissions: false
};
// Handle tool permissions
if (settings.skipPermissions && permissionMode !== 'plan') {
// When skipping permissions, use bypassPermissions mode
sdkOptions.permissionMode = 'bypassPermissions';
}
let allowedTools = [...(settings.allowedTools || [])];
// Add plan mode default tools
if (permissionMode === 'plan') {
const planModeTools = ['Read', 'Task', 'exit_plan_mode', 'TodoRead', 'TodoWrite', 'WebFetch', 'WebSearch'];
for (const tool of planModeTools) {
if (!allowedTools.includes(tool)) {
allowedTools.push(tool);
}
}
}
sdkOptions.allowedTools = allowedTools;
// Use the tools preset to make all default built-in tools available (including AskUserQuestion).
// This was introduced in SDK 0.1.57. Omitting this preserves existing behavior (all tools available),
// but being explicit ensures forward compatibility and clarity.
sdkOptions.tools = { type: 'preset', preset: 'claude_code' };
sdkOptions.disallowedTools = settings.disallowedTools || [];
// Map model (default to sonnet)
// Valid models: sonnet, opus, haiku, opusplan, sonnet[1m]
sdkOptions.model = options.model || CLAUDE_MODELS.DEFAULT;
// Model logged at query start below
// Map system prompt configuration
sdkOptions.systemPrompt = {
type: 'preset',
preset: 'claude_code' // Required to use CLAUDE.md
};
// Map setting sources for CLAUDE.md loading
// This loads CLAUDE.md from project, user (~/.config/claude/CLAUDE.md), and local directories
sdkOptions.settingSources = ['project', 'user', 'local'];
// Map resume session
if (sessionId) {
sdkOptions.resume = sessionId;
}
return sdkOptions;
}
/**
* Adds a session to the active sessions map
* @param {string} sessionId - Session identifier
* @param {Object} queryInstance - SDK query instance
* @param {Array<string>} tempImagePaths - Temp image file paths for cleanup
* @param {string} tempDir - Temp directory for cleanup
*/
function addSession(sessionId, queryInstance, tempImagePaths = [], tempDir = null, writer = null) {
activeSessions.set(sessionId, {
instance: queryInstance,
startTime: Date.now(),
status: 'active',
tempImagePaths,
tempDir,
writer
});
}
/**
* Removes a session from the active sessions map
* @param {string} sessionId - Session identifier
*/
function removeSession(sessionId) {
activeSessions.delete(sessionId);
}
/**
* Gets a session from the active sessions map
* @param {string} sessionId - Session identifier
* @returns {Object|undefined} Session data or undefined
*/
function getSession(sessionId) {
return activeSessions.get(sessionId);
}
/**
* Gets all active session IDs
* @returns {Array<string>} Array of active session IDs
*/
function getAllSessions() {
return Array.from(activeSessions.keys());
}
/**
* Transforms SDK messages to WebSocket format expected by frontend
* @param {Object} sdkMessage - SDK message object
* @returns {Object} Transformed message ready for WebSocket
*/
function transformMessage(sdkMessage) {
// Extract parent_tool_use_id for subagent tool grouping
if (sdkMessage.parent_tool_use_id) {
return {
...sdkMessage,
parentToolUseId: sdkMessage.parent_tool_use_id
};
}
return sdkMessage;
}
/**
* Extracts token usage from SDK result messages
* @param {Object} resultMessage - SDK result message
* @returns {Object|null} Token budget object or null
*/
function extractTokenBudget(resultMessage) {
if (resultMessage.type !== 'result' || !resultMessage.modelUsage) {
return null;
}
// Get the first model's usage data
const modelKey = Object.keys(resultMessage.modelUsage)[0];
const modelData = resultMessage.modelUsage[modelKey];
if (!modelData) {
return null;
}
// Use cumulative tokens if available (tracks total for the session)
// Otherwise fall back to per-request tokens
const inputTokens = modelData.cumulativeInputTokens || modelData.inputTokens || 0;
const outputTokens = modelData.cumulativeOutputTokens || modelData.outputTokens || 0;
const cacheReadTokens = modelData.cumulativeCacheReadInputTokens || modelData.cacheReadInputTokens || 0;
const cacheCreationTokens = modelData.cumulativeCacheCreationInputTokens || modelData.cacheCreationInputTokens || 0;
// Total used = input + output + cache tokens
const totalUsed = inputTokens + outputTokens + cacheReadTokens + cacheCreationTokens;
// Use configured context window budget from environment (default 160000)
// This is the user's budget limit, not the model's context window
const contextWindow = parseInt(process.env.CONTEXT_WINDOW) || 160000;
// Token calc logged via token-budget WS event
return {
used: totalUsed,
total: contextWindow
};
}
/**
* Handles image processing for SDK queries
* Saves base64 images to temporary files and returns modified prompt with file paths
* @param {string} command - Original user prompt
* @param {Array} images - Array of image objects with base64 data
* @param {string} cwd - Working directory for temp file creation
* @returns {Promise<Object>} {modifiedCommand, tempImagePaths, tempDir}
*/
async function handleImages(command, images, cwd) {
const tempImagePaths = [];
let tempDir = null;
if (!images || images.length === 0) {
return { modifiedCommand: command, tempImagePaths, tempDir };
}
try {
// Create temp directory in the project directory
const workingDir = cwd || process.cwd();
tempDir = path.join(workingDir, '.tmp', 'images', Date.now().toString());
await fs.mkdir(tempDir, { recursive: true });
// Save each image to a temp file
for (const [index, image] of images.entries()) {
// Extract base64 data and mime type
const matches = image.data.match(/^data:([^;]+);base64,(.+)$/);
if (!matches) {
console.error('Invalid image data format');
continue;
}
const [, mimeType, base64Data] = matches;
const extension = mimeType.split('/')[1] || 'png';
const filename = `image_${index}.${extension}`;
const filepath = path.join(tempDir, filename);
// Write base64 data to file
await fs.writeFile(filepath, Buffer.from(base64Data, 'base64'));
tempImagePaths.push(filepath);
}
// Include the full image paths in the prompt
let modifiedCommand = command;
if (tempImagePaths.length > 0 && command && command.trim()) {
const imageNote = `\n\n[Images provided at the following paths:]\n${tempImagePaths.map((p, i) => `${i + 1}. ${p}`).join('\n')}`;
modifiedCommand = command + imageNote;
}
// Images processed
return { modifiedCommand, tempImagePaths, tempDir };
} catch (error) {
console.error('Error processing images for SDK:', error);
return { modifiedCommand: command, tempImagePaths, tempDir };
}
}
/**
* Cleans up temporary image files
* @param {Array<string>} tempImagePaths - Array of temp file paths to delete
* @param {string} tempDir - Temp directory to remove
*/
async function cleanupTempFiles(tempImagePaths, tempDir) {
if (!tempImagePaths || tempImagePaths.length === 0) {
return;
}
try {
// Delete individual temp files
for (const imagePath of tempImagePaths) {
await fs.unlink(imagePath).catch(err =>
console.error(`Failed to delete temp image ${imagePath}:`, err)
);
}
// Delete temp directory
if (tempDir) {
await fs.rm(tempDir, { recursive: true, force: true }).catch(err =>
console.error(`Failed to delete temp directory ${tempDir}:`, err)
);
}
// Temp files cleaned
} catch (error) {
console.error('Error during temp file cleanup:', error);
}
}
/**
* Loads MCP server configurations from ~/.claude.json
* @param {string} cwd - Current working directory for project-specific configs
* @returns {Object|null} MCP servers object or null if none found
*/
async function loadMcpConfig(cwd) {
try {
const claudeConfigPath = path.join(os.homedir(), '.claude.json');
// Check if config file exists
try {
await fs.access(claudeConfigPath);
} catch (error) {
// File doesn't exist, return null
// No config file
return null;
}
// Read and parse config file
let claudeConfig;
try {
const configContent = await fs.readFile(claudeConfigPath, 'utf8');
claudeConfig = JSON.parse(configContent);
} catch (error) {
console.error('Failed to parse ~/.claude.json:', error.message);
return null;
}
// Extract MCP servers (merge global and project-specific)
let mcpServers = {};
// Add global MCP servers
if (claudeConfig.mcpServers && typeof claudeConfig.mcpServers === 'object') {
mcpServers = { ...claudeConfig.mcpServers };
// Global MCP servers loaded
}
// Add/override with project-specific MCP servers
if (claudeConfig.claudeProjects && cwd) {
const projectConfig = claudeConfig.claudeProjects[cwd];
if (projectConfig && projectConfig.mcpServers && typeof projectConfig.mcpServers === 'object') {
mcpServers = { ...mcpServers, ...projectConfig.mcpServers };
// Project MCP servers merged
}
}
// Return null if no servers found
if (Object.keys(mcpServers).length === 0) {
return null;
}
return mcpServers;
} catch (error) {
console.error('Error loading MCP config:', error.message);
return null;
}
}
/**
* Executes a Claude query using the SDK
* @param {string} command - User prompt/command
* @param {Object} options - Query options
* @param {Object} ws - WebSocket connection
* @returns {Promise<void>}
*/
async function queryClaudeSDK(command, options = {}, ws) {
const { sessionId, sessionSummary } = options;
let capturedSessionId = sessionId;
let sessionCreatedSent = false;
let tempImagePaths = [];
let tempDir = null;
const emitNotification = (event) => {
notifyUserIfEnabled({
userId: ws?.userId || null,
writer: ws,
event
});
};
try {
// Map CLI options to SDK format
const sdkOptions = mapCliOptionsToSDK(options);
// Load MCP configuration
const mcpServers = await loadMcpConfig(options.cwd);
if (mcpServers) {
sdkOptions.mcpServers = mcpServers;
}
// Handle images - save to temp files and modify prompt
const imageResult = await handleImages(command, options.images, options.cwd);
const finalCommand = imageResult.modifiedCommand;
tempImagePaths = imageResult.tempImagePaths;
tempDir = imageResult.tempDir;
sdkOptions.hooks = {
Notification: [{
matcher: '',
hooks: [async (input) => {
const message = typeof input?.message === 'string' ? input.message : 'Claude requires your attention.';
emitNotification(createNotificationEvent({
provider: 'claude',
sessionId: capturedSessionId || sessionId || null,
kind: 'action_required',
code: 'agent.notification',
meta: { message, sessionName: sessionSummary },
severity: 'warning',
requiresUserAction: true,
dedupeKey: `claude:hook:notification:${capturedSessionId || sessionId || 'none'}:${message}`
}));
return {};
}]
}]
};
sdkOptions.canUseTool = async (toolName, input, context) => {
const requiresInteraction = TOOLS_REQUIRING_INTERACTION.has(toolName);
if (!requiresInteraction) {
if (sdkOptions.permissionMode === 'bypassPermissions') {
return { behavior: 'allow', updatedInput: input };
}
const isDisallowed = (sdkOptions.disallowedTools || []).some(entry =>
matchesToolPermission(entry, toolName, input)
);
if (isDisallowed) {
return { behavior: 'deny', message: 'Tool disallowed by settings' };
}
const isAllowed = (sdkOptions.allowedTools || []).some(entry =>
matchesToolPermission(entry, toolName, input)
);
if (isAllowed) {
return { behavior: 'allow', updatedInput: input };
}
}
const requestId = createRequestId();
ws.send(createNormalizedMessage({ kind: 'permission_request', requestId, toolName, input, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
emitNotification(createNotificationEvent({
provider: 'claude',
sessionId: capturedSessionId || sessionId || null,
kind: 'action_required',
code: 'permission.required',
meta: { toolName, sessionName: sessionSummary },
severity: 'warning',
requiresUserAction: true,
dedupeKey: `claude:permission:${capturedSessionId || sessionId || 'none'}:${requestId}`
}));
const decision = await waitForToolApproval(requestId, {
timeoutMs: requiresInteraction ? 0 : undefined,
signal: context?.signal,
metadata: {
_sessionId: capturedSessionId || sessionId || null,
_toolName: toolName,
_input: input,
_receivedAt: new Date(),
},
onCancel: (reason) => {
ws.send(createNormalizedMessage({ kind: 'permission_cancelled', requestId, reason, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
}
});
if (!decision) {
return { behavior: 'deny', message: 'Permission request timed out' };
}
if (decision.cancelled) {
return { behavior: 'deny', message: 'Permission request cancelled' };
}
if (decision.allow) {
if (decision.rememberEntry && typeof decision.rememberEntry === 'string') {
if (!sdkOptions.allowedTools.includes(decision.rememberEntry)) {
sdkOptions.allowedTools.push(decision.rememberEntry);
}
if (Array.isArray(sdkOptions.disallowedTools)) {
sdkOptions.disallowedTools = sdkOptions.disallowedTools.filter(entry => entry !== decision.rememberEntry);
}
}
return { behavior: 'allow', updatedInput: decision.updatedInput ?? input };
}
return { behavior: 'deny', message: decision.message ?? 'User denied tool use' };
};
// Set stream-close timeout for interactive tools (Query constructor reads it synchronously). Claude Agent SDK has a default of 5s and this overrides it
const prevStreamTimeout = process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT;
process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT = '300000';
let queryInstance;
try {
queryInstance = query({
prompt: finalCommand,
options: sdkOptions
});
} catch (hookError) {
// Older/newer SDK versions may not accept hook shapes yet.
// Keep notification behavior operational via runtime events even if hook registration fails.
console.warn('Failed to initialize Claude query with hooks, retrying without hooks:', hookError?.message || hookError);
delete sdkOptions.hooks;
queryInstance = query({
prompt: finalCommand,
options: sdkOptions
});
}
// Restore immediately — Query constructor already captured the value
if (prevStreamTimeout !== undefined) {
process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT = prevStreamTimeout;
} else {
delete process.env.CLAUDE_CODE_STREAM_CLOSE_TIMEOUT;
}
// Track the query instance for abort capability
if (capturedSessionId) {
addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir, ws);
}
// Process streaming messages
console.log('Starting async generator loop for session:', capturedSessionId || 'NEW');
for await (const message of queryInstance) {
// Capture session ID from first message
if (message.session_id && !capturedSessionId) {
capturedSessionId = message.session_id;
addSession(capturedSessionId, queryInstance, tempImagePaths, tempDir, ws);
// Set session ID on writer
if (ws.setSessionId && typeof ws.setSessionId === 'function') {
ws.setSessionId(capturedSessionId);
}
// Send session-created event only once for new sessions
if (!sessionId && !sessionCreatedSent) {
sessionCreatedSent = true;
ws.send(createNormalizedMessage({ kind: 'session_created', newSessionId: capturedSessionId, sessionId: capturedSessionId, provider: 'claude' }));
}
} else {
// session_id already captured
}
// Transform and normalize message via adapter
const transformedMessage = transformMessage(message);
const sid = capturedSessionId || sessionId || null;
// Use adapter to normalize SDK events into NormalizedMessage[]
const normalized = claudeAdapter.normalizeMessage(transformedMessage, sid);
for (const msg of normalized) {
// Preserve parentToolUseId from SDK wrapper for subagent tool grouping
if (transformedMessage.parentToolUseId && !msg.parentToolUseId) {
msg.parentToolUseId = transformedMessage.parentToolUseId;
}
ws.send(msg);
}
// Extract and send token budget updates from result messages
if (message.type === 'result') {
const models = Object.keys(message.modelUsage || {});
if (models.length > 0) {
// Model info available in result message
}
const tokenBudgetData = extractTokenBudget(message);
if (tokenBudgetData) {
ws.send(createNormalizedMessage({ kind: 'status', text: 'token_budget', tokenBudget: tokenBudgetData, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
}
}
}
// Clean up session on completion
if (capturedSessionId) {
removeSession(capturedSessionId);
}
// Clean up temporary image files
await cleanupTempFiles(tempImagePaths, tempDir);
// Send completion event
ws.send(createNormalizedMessage({ kind: 'complete', exitCode: 0, isNewSession: !sessionId && !!command, sessionId: capturedSessionId, provider: 'claude' }));
notifyRunStopped({
userId: ws?.userId || null,
provider: 'claude',
sessionId: capturedSessionId || sessionId || null,
sessionName: sessionSummary,
stopReason: 'completed'
});
// Complete
} catch (error) {
console.error('SDK query error:', error);
// Clean up session on error
if (capturedSessionId) {
removeSession(capturedSessionId);
}
// Clean up temporary image files on error
await cleanupTempFiles(tempImagePaths, tempDir);
// Send error to WebSocket
ws.send(createNormalizedMessage({ kind: 'error', content: error.message, sessionId: capturedSessionId || sessionId || null, provider: 'claude' }));
notifyRunFailed({
userId: ws?.userId || null,
provider: 'claude',
sessionId: capturedSessionId || sessionId || null,
sessionName: sessionSummary,
error
});
throw error;
}
}
/**
* Aborts an active SDK session
* @param {string} sessionId - Session identifier
* @returns {boolean} True if session was aborted, false if not found
*/
async function abortClaudeSDKSession(sessionId) {
const session = getSession(sessionId);
if (!session) {
console.log(`Session ${sessionId} not found`);
return false;
}
try {
console.log(`Aborting SDK session: ${sessionId}`);
// Call interrupt() on the query instance
await session.instance.interrupt();
// Update session status
session.status = 'aborted';
// Clean up temporary image files
await cleanupTempFiles(session.tempImagePaths, session.tempDir);
// Clean up session
removeSession(sessionId);
return true;
} catch
gitextract_zr639i5w/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ └── workflows/ │ └── discord-release.yml ├── .gitignore ├── .gitmodules ├── .husky/ │ ├── commit-msg │ └── pre-commit ├── .npmignore ├── .nvmrc ├── .release-it.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.de.md ├── README.ja.md ├── README.ko.md ├── README.md ├── README.ru.md ├── README.zh-CN.md ├── commitlint.config.js ├── eslint.config.js ├── index.html ├── package.json ├── postcss.config.js ├── public/ │ ├── api-docs.html │ ├── clear-cache.html │ ├── convert-icons.md │ ├── generate-icons.js │ ├── manifest.json │ └── sw.js ├── release.sh ├── scripts/ │ └── fix-node-pty.js ├── server/ │ ├── claude-sdk.js │ ├── cli.js │ ├── constants/ │ │ └── config.js │ ├── cursor-cli.js │ ├── database/ │ │ ├── db.js │ │ └── init.sql │ ├── gemini-cli.js │ ├── gemini-response-handler.js │ ├── index.js │ ├── load-env.js │ ├── middleware/ │ │ └── auth.js │ ├── openai-codex.js │ ├── projects.js │ ├── providers/ │ │ ├── claude/ │ │ │ └── adapter.js │ │ ├── codex/ │ │ │ └── adapter.js │ │ ├── cursor/ │ │ │ └── adapter.js │ │ ├── gemini/ │ │ │ └── adapter.js │ │ ├── registry.js │ │ ├── types.js │ │ └── utils.js │ ├── routes/ │ │ ├── agent.js │ │ ├── auth.js │ │ ├── cli-auth.js │ │ ├── codex.js │ │ ├── commands.js │ │ ├── cursor.js │ │ ├── gemini.js │ │ ├── git.js │ │ ├── mcp-utils.js │ │ ├── mcp.js │ │ ├── messages.js │ │ ├── plugins.js │ │ ├── projects.js │ │ ├── settings.js │ │ ├── taskmaster.js │ │ └── user.js │ ├── services/ │ │ ├── notification-orchestrator.js │ │ └── vapid-keys.js │ ├── sessionManager.js │ └── utils/ │ ├── commandParser.js │ ├── frontmatter.js │ ├── gitConfig.js │ ├── mcp-detector.js │ ├── plugin-loader.js │ ├── plugin-process-manager.js │ └── taskmaster-websocket.js ├── shared/ │ ├── modelConstants.js │ └── networkHosts.js ├── src/ │ ├── App.tsx │ ├── components/ │ │ ├── app/ │ │ │ ├── AppContent.tsx │ │ │ └── MobileNav.tsx │ │ ├── auth/ │ │ │ ├── constants.ts │ │ │ ├── context/ │ │ │ │ └── AuthContext.tsx │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── utils.ts │ │ │ └── view/ │ │ │ ├── AuthErrorAlert.tsx │ │ │ ├── AuthInputField.tsx │ │ │ ├── AuthLoadingScreen.tsx │ │ │ ├── AuthScreenLayout.tsx │ │ │ ├── LoginForm.tsx │ │ │ ├── ProtectedRoute.tsx │ │ │ └── SetupForm.tsx │ │ ├── chat/ │ │ │ ├── constants/ │ │ │ │ └── thinkingModes.ts │ │ │ ├── hooks/ │ │ │ │ ├── useChatComposerState.ts │ │ │ │ ├── useChatMessages.ts │ │ │ │ ├── useChatProviderState.ts │ │ │ │ ├── useChatRealtimeHandlers.ts │ │ │ │ ├── useChatSessionState.ts │ │ │ │ ├── useFileMentions.tsx │ │ │ │ └── useSlashCommands.ts │ │ │ ├── tools/ │ │ │ │ ├── README.md │ │ │ │ ├── ToolRenderer.tsx │ │ │ │ ├── components/ │ │ │ │ │ ├── CollapsibleDisplay.tsx │ │ │ │ │ ├── CollapsibleSection.tsx │ │ │ │ │ ├── ContentRenderers/ │ │ │ │ │ │ ├── FileListContent.tsx │ │ │ │ │ │ ├── MarkdownContent.tsx │ │ │ │ │ │ ├── QuestionAnswerContent.tsx │ │ │ │ │ │ ├── TaskListContent.tsx │ │ │ │ │ │ ├── TextContent.tsx │ │ │ │ │ │ ├── TodoList.tsx │ │ │ │ │ │ ├── TodoListContent.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── InteractiveRenderers/ │ │ │ │ │ │ ├── AskUserQuestionPanel.tsx │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── OneLineDisplay.tsx │ │ │ │ │ ├── SubagentContainer.tsx │ │ │ │ │ ├── ToolDiffViewer.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── configs/ │ │ │ │ │ ├── permissionPanelRegistry.ts │ │ │ │ │ └── toolConfigs.ts │ │ │ │ └── index.ts │ │ │ ├── types/ │ │ │ │ └── types.ts │ │ │ ├── utils/ │ │ │ │ ├── chatFormatting.ts │ │ │ │ ├── chatPermissions.ts │ │ │ │ ├── chatStorage.ts │ │ │ │ ├── messageKeys.ts │ │ │ │ └── messageTransforms.ts │ │ │ └── view/ │ │ │ ├── ChatInterface.tsx │ │ │ └── subcomponents/ │ │ │ ├── AssistantThinkingIndicator.tsx │ │ │ ├── ChatComposer.tsx │ │ │ ├── ChatInputControls.tsx │ │ │ ├── ChatMessagesPane.tsx │ │ │ ├── ClaudeStatus.tsx │ │ │ ├── CommandMenu.tsx │ │ │ ├── ImageAttachment.tsx │ │ │ ├── Markdown.tsx │ │ │ ├── MessageComponent.tsx │ │ │ ├── MessageCopyControl.tsx │ │ │ ├── PermissionRequestsBanner.tsx │ │ │ ├── ProviderSelectionEmptyState.tsx │ │ │ ├── ThinkingModeSelector.tsx │ │ │ └── TokenUsagePie.tsx │ │ ├── code-editor/ │ │ │ ├── constants/ │ │ │ │ └── settings.ts │ │ │ ├── hooks/ │ │ │ │ ├── useCodeEditorDocument.ts │ │ │ │ ├── useCodeEditorSettings.ts │ │ │ │ ├── useEditorKeyboardShortcuts.ts │ │ │ │ └── useEditorSidebar.ts │ │ │ ├── types/ │ │ │ │ └── types.ts │ │ │ ├── utils/ │ │ │ │ ├── binaryFile.ts │ │ │ │ ├── editorExtensions.ts │ │ │ │ ├── editorStyles.ts │ │ │ │ └── editorToolbarPanel.ts │ │ │ └── view/ │ │ │ ├── CodeEditor.tsx │ │ │ ├── EditorSidebar.tsx │ │ │ └── subcomponents/ │ │ │ ├── CodeEditorBinaryFile.tsx │ │ │ ├── CodeEditorFooter.tsx │ │ │ ├── CodeEditorHeader.tsx │ │ │ ├── CodeEditorLoadingState.tsx │ │ │ ├── CodeEditorSurface.tsx │ │ │ └── markdown/ │ │ │ ├── MarkdownCodeBlock.tsx │ │ │ └── MarkdownPreview.tsx │ │ ├── file-tree/ │ │ │ ├── constants/ │ │ │ │ ├── constants.ts │ │ │ │ └── fileIcons.ts │ │ │ ├── hooks/ │ │ │ │ ├── useExpandedDirectories.ts │ │ │ │ ├── useFileTreeData.ts │ │ │ │ ├── useFileTreeOperations.ts │ │ │ │ ├── useFileTreeSearch.ts │ │ │ │ ├── useFileTreeUpload.ts │ │ │ │ └── useFileTreeViewMode.ts │ │ │ ├── types/ │ │ │ │ └── types.ts │ │ │ ├── utils/ │ │ │ │ └── fileTreeUtils.ts │ │ │ └── view/ │ │ │ ├── FileContextMenu.tsx │ │ │ ├── FileTree.tsx │ │ │ ├── FileTreeBody.tsx │ │ │ ├── FileTreeDetailedColumns.tsx │ │ │ ├── FileTreeEmptyState.tsx │ │ │ ├── FileTreeHeader.tsx │ │ │ ├── FileTreeList.tsx │ │ │ ├── FileTreeLoadingState.tsx │ │ │ ├── FileTreeNode.tsx │ │ │ └── ImageViewer.tsx │ │ ├── git-panel/ │ │ │ ├── constants/ │ │ │ │ └── constants.ts │ │ │ ├── hooks/ │ │ │ │ ├── useGitPanelController.ts │ │ │ │ ├── useRevertLocalCommit.ts │ │ │ │ └── useSelectedProvider.ts │ │ │ ├── types/ │ │ │ │ └── types.ts │ │ │ ├── utils/ │ │ │ │ └── gitPanelUtils.ts │ │ │ └── view/ │ │ │ ├── GitPanel.tsx │ │ │ ├── GitPanelHeader.tsx │ │ │ ├── GitRepositoryErrorState.tsx │ │ │ ├── GitViewTabs.tsx │ │ │ ├── branches/ │ │ │ │ └── BranchesView.tsx │ │ │ ├── changes/ │ │ │ │ ├── ChangesView.tsx │ │ │ │ ├── CommitComposer.tsx │ │ │ │ ├── FileChangeItem.tsx │ │ │ │ ├── FileChangeList.tsx │ │ │ │ ├── FileSelectionControls.tsx │ │ │ │ └── FileStatusLegend.tsx │ │ │ ├── history/ │ │ │ │ ├── CommitHistoryItem.tsx │ │ │ │ └── HistoryView.tsx │ │ │ ├── modals/ │ │ │ │ ├── ConfirmActionModal.tsx │ │ │ │ └── NewBranchModal.tsx │ │ │ └── shared/ │ │ │ └── GitDiffViewer.tsx │ │ ├── llm-logo-provider/ │ │ │ ├── ClaudeLogo.tsx │ │ │ ├── CodexLogo.tsx │ │ │ ├── CursorLogo.tsx │ │ │ ├── GeminiLogo.tsx │ │ │ └── SessionProviderLogo.tsx │ │ ├── main-content/ │ │ │ ├── hooks/ │ │ │ │ └── useMobileMenuHandlers.ts │ │ │ ├── types/ │ │ │ │ └── types.ts │ │ │ └── view/ │ │ │ ├── ErrorBoundary.tsx │ │ │ ├── MainContent.tsx │ │ │ └── subcomponents/ │ │ │ ├── MainContentHeader.tsx │ │ │ ├── MainContentStateView.tsx │ │ │ ├── MainContentTabSwitcher.tsx │ │ │ ├── MainContentTitle.tsx │ │ │ └── MobileMenuButton.tsx │ │ ├── mic-button/ │ │ │ ├── constants/ │ │ │ │ └── constants.ts │ │ │ ├── data/ │ │ │ │ └── whisper.ts │ │ │ ├── hooks/ │ │ │ │ └── useMicButtonController.ts │ │ │ ├── types/ │ │ │ │ └── types.ts │ │ │ └── view/ │ │ │ ├── MicButton.tsx │ │ │ └── MicButtonView.tsx │ │ ├── onboarding/ │ │ │ └── view/ │ │ │ ├── Onboarding.tsx │ │ │ ├── subcomponents/ │ │ │ │ ├── AgentConnectionCard.tsx │ │ │ │ ├── AgentConnectionsStep.tsx │ │ │ │ ├── GitConfigurationStep.tsx │ │ │ │ └── OnboardingStepProgress.tsx │ │ │ ├── types.ts │ │ │ └── utils.ts │ │ ├── plugins/ │ │ │ └── view/ │ │ │ ├── PluginIcon.tsx │ │ │ ├── PluginSettingsTab.tsx │ │ │ └── PluginTabContent.tsx │ │ ├── prd-editor/ │ │ │ ├── PRDEditor.tsx │ │ │ ├── constants.ts │ │ │ ├── hooks/ │ │ │ │ ├── usePrdDocument.ts │ │ │ │ ├── usePrdKeyboardShortcuts.ts │ │ │ │ ├── usePrdRegistry.ts │ │ │ │ └── usePrdSave.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── utils/ │ │ │ │ └── fileName.ts │ │ │ └── view/ │ │ │ ├── GenerateTasksModal.tsx │ │ │ ├── OverwriteConfirmModal.tsx │ │ │ ├── PrdEditorBody.tsx │ │ │ ├── PrdEditorFooter.tsx │ │ │ ├── PrdEditorHeader.tsx │ │ │ ├── PrdEditorLoadingState.tsx │ │ │ └── PrdEditorWorkspace.tsx │ │ ├── project-creation-wizard/ │ │ │ ├── ProjectCreationWizard.tsx │ │ │ ├── components/ │ │ │ │ ├── ErrorBanner.tsx │ │ │ │ ├── FolderBrowserModal.tsx │ │ │ │ ├── GithubAuthenticationCard.tsx │ │ │ │ ├── StepConfiguration.tsx │ │ │ │ ├── StepReview.tsx │ │ │ │ ├── StepTypeSelection.tsx │ │ │ │ ├── WizardFooter.tsx │ │ │ │ ├── WizardProgress.tsx │ │ │ │ └── WorkspacePathField.tsx │ │ │ ├── data/ │ │ │ │ └── workspaceApi.ts │ │ │ ├── hooks/ │ │ │ │ └── useGithubTokens.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ └── utils/ │ │ │ └── pathUtils.ts │ │ ├── provider-auth/ │ │ │ ├── types.ts │ │ │ └── view/ │ │ │ └── ProviderLoginModal.tsx │ │ ├── quick-settings-panel/ │ │ │ ├── constants.ts │ │ │ ├── hooks/ │ │ │ │ ├── useQuickSettingsDrag.ts │ │ │ │ └── useWhisperMode.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ └── view/ │ │ │ ├── QuickSettingsContent.tsx │ │ │ ├── QuickSettingsHandle.tsx │ │ │ ├── QuickSettingsPanelHeader.tsx │ │ │ ├── QuickSettingsPanelView.tsx │ │ │ ├── QuickSettingsSection.tsx │ │ │ ├── QuickSettingsToggleRow.tsx │ │ │ └── QuickSettingsWhisperSection.tsx │ │ ├── settings/ │ │ │ ├── constants/ │ │ │ │ └── constants.ts │ │ │ ├── hooks/ │ │ │ │ ├── useCredentialsSettings.ts │ │ │ │ ├── useGitSettings.ts │ │ │ │ └── useSettingsController.ts │ │ │ ├── types/ │ │ │ │ └── types.ts │ │ │ └── view/ │ │ │ ├── Settings.tsx │ │ │ ├── SettingsCard.tsx │ │ │ ├── SettingsMainTabs.tsx │ │ │ ├── SettingsRow.tsx │ │ │ ├── SettingsSection.tsx │ │ │ ├── SettingsSidebar.tsx │ │ │ ├── SettingsToggle.tsx │ │ │ ├── modals/ │ │ │ │ ├── ClaudeMcpFormModal.tsx │ │ │ │ └── CodexMcpFormModal.tsx │ │ │ └── tabs/ │ │ │ ├── AppearanceSettingsTab.tsx │ │ │ ├── NotificationsSettingsTab.tsx │ │ │ ├── agents-settings/ │ │ │ │ ├── AgentListItem.tsx │ │ │ │ ├── AgentsSettingsTab.tsx │ │ │ │ ├── sections/ │ │ │ │ │ ├── AgentCategoryContentSection.tsx │ │ │ │ │ ├── AgentCategoryTabsSection.tsx │ │ │ │ │ ├── AgentSelectorSection.tsx │ │ │ │ │ └── content/ │ │ │ │ │ ├── AccountContent.tsx │ │ │ │ │ ├── McpServersContent.tsx │ │ │ │ │ └── PermissionsContent.tsx │ │ │ │ └── types.ts │ │ │ ├── api-settings/ │ │ │ │ ├── CredentialsSettingsTab.tsx │ │ │ │ ├── sections/ │ │ │ │ │ ├── ApiKeysSection.tsx │ │ │ │ │ ├── GithubCredentialsSection.tsx │ │ │ │ │ ├── NewApiKeyAlert.tsx │ │ │ │ │ └── VersionInfoSection.tsx │ │ │ │ └── types.ts │ │ │ ├── git-settings/ │ │ │ │ └── GitSettingsTab.tsx │ │ │ └── tasks-settings/ │ │ │ └── TasksSettingsTab.tsx │ │ ├── shell/ │ │ │ ├── constants/ │ │ │ │ └── constants.ts │ │ │ ├── hooks/ │ │ │ │ ├── useShellConnection.ts │ │ │ │ ├── useShellRuntime.ts │ │ │ │ └── useShellTerminal.ts │ │ │ ├── types/ │ │ │ │ └── types.ts │ │ │ ├── utils/ │ │ │ │ ├── auth.ts │ │ │ │ ├── socket.ts │ │ │ │ └── terminalStyles.ts │ │ │ └── view/ │ │ │ ├── Shell.tsx │ │ │ └── subcomponents/ │ │ │ ├── ShellConnectionOverlay.tsx │ │ │ ├── ShellEmptyState.tsx │ │ │ ├── ShellHeader.tsx │ │ │ ├── ShellMinimalView.tsx │ │ │ └── TerminalShortcutsPanel.tsx │ │ ├── sidebar/ │ │ │ ├── hooks/ │ │ │ │ └── useSidebarController.ts │ │ │ ├── types/ │ │ │ │ └── types.ts │ │ │ ├── utils/ │ │ │ │ └── utils.ts │ │ │ └── view/ │ │ │ ├── Sidebar.tsx │ │ │ └── subcomponents/ │ │ │ ├── SidebarCollapsed.tsx │ │ │ ├── SidebarContent.tsx │ │ │ ├── SidebarFooter.tsx │ │ │ ├── SidebarHeader.tsx │ │ │ ├── SidebarModals.tsx │ │ │ ├── SidebarProjectItem.tsx │ │ │ ├── SidebarProjectList.tsx │ │ │ ├── SidebarProjectSessions.tsx │ │ │ ├── SidebarProjectsState.tsx │ │ │ ├── SidebarSessionItem.tsx │ │ │ └── TaskIndicator.tsx │ │ ├── standalone-shell/ │ │ │ └── view/ │ │ │ ├── StandaloneShell.tsx │ │ │ └── subcomponents/ │ │ │ ├── StandaloneShellEmptyState.tsx │ │ │ └── StandaloneShellHeader.tsx │ │ ├── task-master/ │ │ │ ├── context/ │ │ │ │ └── TaskMasterContext.tsx │ │ │ ├── hooks/ │ │ │ │ ├── useProjectPrdFiles.ts │ │ │ │ └── useTaskBoardState.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ ├── utils/ │ │ │ │ ├── taskKanban.ts │ │ │ │ └── taskSorting.ts │ │ │ └── view/ │ │ │ ├── NextTaskBanner.tsx │ │ │ ├── TaskBoard.tsx │ │ │ ├── TaskBoardContent.tsx │ │ │ ├── TaskBoardToolbar.tsx │ │ │ ├── TaskCard.tsx │ │ │ ├── TaskDetailModal.tsx │ │ │ ├── TaskEmptyState.tsx │ │ │ ├── TaskMasterPanel.tsx │ │ │ ├── modals/ │ │ │ │ ├── CreateTaskModal.tsx │ │ │ │ ├── TaskHelpModal.tsx │ │ │ │ └── TaskMasterSetupModal.tsx │ │ │ └── shared/ │ │ │ ├── TaskFiltersPanel.tsx │ │ │ └── TaskQuickSortBar.tsx │ │ └── version-upgrade/ │ │ └── view/ │ │ ├── VersionUpgradeModal.tsx │ │ └── index.ts │ ├── constants/ │ │ └── config.ts │ ├── contexts/ │ │ ├── AuthContext.jsx │ │ ├── PluginsContext.tsx │ │ ├── TaskMasterContext.ts │ │ ├── TasksSettingsContext.jsx │ │ ├── ThemeContext.jsx │ │ └── WebSocketContext.tsx │ ├── hooks/ │ │ ├── useDeviceSettings.ts │ │ ├── useLocalStorage.jsx │ │ ├── useProjectsState.ts │ │ ├── useSessionProtection.ts │ │ ├── useUiPreferences.ts │ │ ├── useVersionCheck.ts │ │ └── useWebPush.ts │ ├── i18n/ │ │ ├── config.js │ │ ├── languages.js │ │ └── locales/ │ │ ├── de/ │ │ │ ├── auth.json │ │ │ ├── chat.json │ │ │ ├── codeEditor.json │ │ │ ├── common.json │ │ │ ├── settings.json │ │ │ ├── sidebar.json │ │ │ └── tasks.json │ │ ├── en/ │ │ │ ├── auth.json │ │ │ ├── chat.json │ │ │ ├── codeEditor.json │ │ │ ├── common.json │ │ │ ├── settings.json │ │ │ ├── sidebar.json │ │ │ └── tasks.json │ │ ├── ja/ │ │ │ ├── auth.json │ │ │ ├── chat.json │ │ │ ├── codeEditor.json │ │ │ ├── common.json │ │ │ ├── settings.json │ │ │ ├── sidebar.json │ │ │ └── tasks.json │ │ ├── ko/ │ │ │ ├── auth.json │ │ │ ├── chat.json │ │ │ ├── codeEditor.json │ │ │ ├── common.json │ │ │ ├── settings.json │ │ │ └── sidebar.json │ │ ├── ru/ │ │ │ ├── auth.json │ │ │ ├── chat.json │ │ │ ├── codeEditor.json │ │ │ ├── common.json │ │ │ ├── settings.json │ │ │ ├── sidebar.json │ │ │ └── tasks.json │ │ └── zh-CN/ │ │ ├── auth.json │ │ ├── chat.json │ │ ├── codeEditor.json │ │ ├── common.json │ │ ├── settings.json │ │ └── sidebar.json │ ├── index.css │ ├── lib/ │ │ └── utils.js │ ├── main.jsx │ ├── shared/ │ │ └── view/ │ │ └── ui/ │ │ ├── Badge.tsx │ │ ├── Button.tsx │ │ ├── DarkModeToggle.tsx │ │ ├── Input.tsx │ │ ├── LanguageSelector.tsx │ │ ├── PillBar.tsx │ │ ├── ScrollArea.tsx │ │ ├── Tooltip.tsx │ │ └── index.ts │ ├── stores/ │ │ └── useSessionStore.ts │ ├── types/ │ │ ├── app.ts │ │ ├── global.d.ts │ │ ├── react-syntax-highlighter.d.ts │ │ └── sharedTypes.ts │ ├── utils/ │ │ ├── api.js │ │ ├── clipboard.ts │ │ └── dateUtils.ts │ └── vite-env.d.ts ├── tailwind.config.js ├── tsconfig.json └── vite.config.js
SYMBOL INDEX (1297 symbols across 336 files)
FILE: public/generate-icons.js
function createIconSVG (line 8) | function createIconSVG(size) {
FILE: public/sw.js
constant CACHE_NAME (line 2) | const CACHE_NAME = 'claude-ui-v1';
FILE: scripts/fix-node-pty.js
function fixSpawnHelper (line 38) | async function fixSpawnHelper() {
FILE: server/claude-sdk.js
constant TOOL_APPROVAL_TIMEOUT_MS (line 33) | const TOOL_APPROVAL_TIMEOUT_MS = parseInt(process.env.CLAUDE_TOOL_APPROV...
constant TOOLS_REQUIRING_INTERACTION (line 35) | const TOOLS_REQUIRING_INTERACTION = new Set(['AskUserQuestion']);
function createRequestId (line 37) | function createRequestId() {
function waitForToolApproval (line 44) | function waitForToolApproval(requestId, options = {}) {
function resolveToolApproval (line 100) | function resolveToolApproval(requestId, decision) {
function matchesToolPermission (line 111) | function matchesToolPermission(entry, toolName, input) {
function mapCliOptionsToSDK (line 146) | function mapCliOptionsToSDK(options = {}) {
function addSession (line 225) | function addSession(sessionId, queryInstance, tempImagePaths = [], tempD...
function removeSession (line 240) | function removeSession(sessionId) {
function getSession (line 249) | function getSession(sessionId) {
function getAllSessions (line 257) | function getAllSessions() {
function transformMessage (line 266) | function transformMessage(sdkMessage) {
function extractTokenBudget (line 282) | function extractTokenBudget(resultMessage) {
function handleImages (line 325) | async function handleImages(command, images, cwd) {
function cleanupTempFiles (line 378) | async function cleanupTempFiles(tempImagePaths, tempDir) {
function loadMcpConfig (line 409) | async function loadMcpConfig(cwd) {
function queryClaudeSDK (line 468) | async function queryClaudeSDK(command, options = {}, ws) {
function abortClaudeSDKSession (line 723) | async function abortClaudeSDKSession(sessionId) {
function isClaudeSDKSessionActive (line 758) | function isClaudeSDKSessionActive(sessionId) {
function getActiveClaudeSDKSessions (line 767) | function getActiveClaudeSDKSessions() {
function getPendingApprovalsForSession (line 776) | function getPendingApprovalsForSession(sessionId) {
function reconnectSessionWriter (line 800) | function reconnectSessionWriter(sessionId, newRawWs) {
FILE: server/cli.js
function loadEnvFile (line 56) | function loadEnvFile() {
function getDatabasePath (line 75) | function getDatabasePath() {
function getInstallDir (line 81) | function getInstallDir() {
function showStatus (line 86) | function showStatus() {
function showHelp (line 141) | function showHelp() {
function showVersion (line 187) | function showVersion() {
function isNewerVersion (line 192) | function isNewerVersion(v1, v2) {
function checkForUpdates (line 203) | async function checkForUpdates(silent = false) {
function updatePackage (line 226) | async function updatePackage() {
function startServer (line 248) | async function startServer() {
function parseArgs (line 257) | function parseArgs(args) {
function main (line 284) | async function main() {
FILE: server/constants/config.js
constant IS_PLATFORM (line 5) | const IS_PLATFORM = process.env.VITE_IS_PLATFORM === 'true';
FILE: server/cursor-cli.js
constant WORKSPACE_TRUST_PATTERNS (line 12) | const WORKSPACE_TRUST_PATTERNS = [
function isWorkspaceTrustPrompt (line 19) | function isWorkspaceTrustPrompt(text = '') {
function spawnCursor (line 27) | async function spawnCursor(command, options = {}, ws) {
function abortCursorSession (line 311) | function abortCursorSession(sessionId) {
function isCursorSessionActive (line 322) | function isCursorSessionActive(sessionId) {
function getActiveCursorSessions (line 326) | function getActiveCursorSessions() {
FILE: server/database/db.js
constant DB_PATH (line 26) | const DB_PATH = process.env.DATABASE_PATH || path.join(__dirname, 'auth....
constant INIT_SQL_PATH (line 27) | const INIT_SQL_PATH = path.join(__dirname, 'init.sql');
constant LEGACY_DB_PATH (line 44) | const LEGACY_DB_PATH = path.join(__dirname, 'auth.db');
constant DEFAULT_NOTIFICATION_PREFERENCES (line 408) | const DEFAULT_NOTIFICATION_PREFERENCES = {
function applyCustomSessionNames (line 558) | function applyCustomSessionNames(sessions, provider) {
FILE: server/database/init.sql
type users (line 5) | CREATE TABLE IF NOT EXISTS users (
type idx_users_username (line 18) | CREATE INDEX IF NOT EXISTS idx_users_username ON users(username)
type idx_users_active (line 19) | CREATE INDEX IF NOT EXISTS idx_users_active ON users(is_active)
type api_keys (line 22) | CREATE TABLE IF NOT EXISTS api_keys (
type idx_api_keys_key (line 33) | CREATE INDEX IF NOT EXISTS idx_api_keys_key ON api_keys(api_key)
type idx_api_keys_user_id (line 34) | CREATE INDEX IF NOT EXISTS idx_api_keys_user_id ON api_keys(user_id)
type idx_api_keys_active (line 35) | CREATE INDEX IF NOT EXISTS idx_api_keys_active ON api_keys(is_active)
type user_credentials (line 38) | CREATE TABLE IF NOT EXISTS user_credentials (
type idx_user_credentials_user_id (line 50) | CREATE INDEX IF NOT EXISTS idx_user_credentials_user_id ON user_credenti...
type idx_user_credentials_type (line 51) | CREATE INDEX IF NOT EXISTS idx_user_credentials_type ON user_credentials...
type idx_user_credentials_active (line 52) | CREATE INDEX IF NOT EXISTS idx_user_credentials_active ON user_credentia...
type user_notification_preferences (line 55) | CREATE TABLE IF NOT EXISTS user_notification_preferences (
type vapid_keys (line 63) | CREATE TABLE IF NOT EXISTS vapid_keys (
type push_subscriptions (line 71) | CREATE TABLE IF NOT EXISTS push_subscriptions (
type session_names (line 82) | CREATE TABLE IF NOT EXISTS session_names (
type idx_session_names_lookup (line 92) | CREATE INDEX IF NOT EXISTS idx_session_names_lookup ON session_names(ses...
type app_config (line 95) | CREATE TABLE IF NOT EXISTS app_config (
FILE: server/gemini-cli.js
function spawnGemini (line 16) | async function spawnGemini(command, options = {}, ws) {
function abortGeminiSession (line 407) | function abortGeminiSession(sessionId) {
function isGeminiSessionActive (line 440) | function isGeminiSessionActive(sessionId) {
function getActiveGeminiSessions (line 444) | function getActiveGeminiSessions() {
FILE: server/gemini-response-handler.js
class GeminiResponseHandler (line 4) | class GeminiResponseHandler {
method constructor (line 5) | constructor(ws, options = {}) {
method processData (line 15) | processData(data) {
method handleEvent (line 36) | handleEvent(event) {
method forceFlush (line 65) | forceFlush() {
method destroy (line 74) | destroy() {
FILE: server/index.js
constant VALID_PROVIDERS (line 77) | const VALID_PROVIDERS = ['claude', 'codex', 'cursor', 'gemini'];
constant PROVIDER_WATCH_PATHS (line 80) | const PROVIDER_WATCH_PATHS = [
constant WATCHER_IGNORED_PATTERNS (line 87) | const WATCHER_IGNORED_PATTERNS = [
constant WATCHER_DEBOUNCE_MS (line 96) | const WATCHER_DEBOUNCE_MS = 300;
function broadcastProgress (line 103) | function broadcastProgress(progress) {
function setupProjectsWatcher (line 116) | async function setupProjectsWatcher() {
constant PTY_SESSION_TIMEOUT (line 227) | const PTY_SESSION_TIMEOUT = 30 * 60 * 1000;
constant SHELL_URL_PARSE_BUFFER_LIMIT (line 228) | const SHELL_URL_PARSE_BUFFER_LIMIT = 32768;
constant ANSI_ESCAPE_SEQUENCE_REGEX (line 229) | const ANSI_ESCAPE_SEQUENCE_REGEX = /\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]...
constant TRAILING_URL_PUNCTUATION_REGEX (line 230) | const TRAILING_URL_PUNCTUATION_REGEX = /[)\]}>.,;:!?]+$/;
function stripAnsiSequences (line 232) | function stripAnsiSequences(value = '') {
function normalizeDetectedUrl (line 236) | function normalizeDetectedUrl(url) {
function extractUrlsFromText (line 253) | function extractUrlsFromText(value = '') {
function shouldAutoOpenUrlFromOutput (line 281) | function shouldAutoOpenUrlFromOutput(value = '') {
function validatePathInProject (line 958) | function validatePathInProject(projectRoot, targetPath) {
function validateFilename (line 974) | function validateFilename(name) {
function handlePluginWsProxy (line 1382) | function handlePluginWsProxy(clientWs, pathname) {
class WebSocketWriter (line 1450) | class WebSocketWriter {
method constructor (line 1451) | constructor(ws, userId = null) {
method send (line 1458) | send(data) {
method updateWebSocket (line 1464) | updateWebSocket(newRawWs) {
method setSessionId (line 1468) | setSessionId(sessionId) {
method getSessionId (line 1472) | getSessionId() {
function handleChatConnection (line 1478) | function handleChatConnection(ws, request) {
function handleShellConnection (line 1626) | function handleShellConnection(ws) {
function permToRwx (line 2431) | function permToRwx(perm) {
function getFileTree (line 2438) | async function getFileTree(dirPath, maxDepth = 3, currentDepth = 0, show...
constant SERVER_PORT (line 2514) | const SERVER_PORT = process.env.SERVER_PORT || 3001;
constant HOST (line 2515) | const HOST = process.env.HOST || '0.0.0.0';
constant DISPLAY_HOST (line 2516) | const DISPLAY_HOST = getConnectableHost(HOST);
constant VITE_PORT (line 2517) | const VITE_PORT = process.env.VITE_PORT || 5173;
function startServer (line 2520) | async function startServer() {
FILE: server/middleware/auth.js
constant JWT_SECRET (line 6) | const JWT_SECRET = process.env.JWT_SECRET || appConfigDb.getOrCreateJwtS...
FILE: server/openai-codex.js
function transformCodexEvent (line 29) | function transformCodexEvent(event) {
function mapPermissionModeToCodexOptions (line 167) | function mapPermissionModeToCodexOptions(permissionMode) {
function queryCodex (line 194) | async function queryCodex(command, options = {}, ws) {
function abortCodexSession (line 339) | function abortCodexSession(sessionId) {
function isCodexSessionActive (line 361) | function isCodexSessionActive(sessionId) {
function getActiveCodexSessions (line 370) | function getActiveCodexSessions() {
function sendMessage (line 391) | function sendMessage(ws, data) {
FILE: server/projects.js
function detectTaskMasterFolder (line 72) | async function detectTaskMasterFolder(projectPath) {
function clearProjectDirectoryCache (line 203) | function clearProjectDirectoryCache() {
function loadProjectConfig (line 208) | async function loadProjectConfig() {
function saveProjectConfig (line 220) | async function saveProjectConfig(config) {
function generateDisplayName (line 237) | async function generateDisplayName(projectName, actualProjectDir = null) {
function extractProjectDirectory (line 266) | async function extractProjectDirectory(projectName) {
function getProjects (line 384) | async function getProjects(progressCallback = null) {
function getSessions (line 644) | async function getSessions(projectName, limit = 5, offset = 0) {
function parseJsonlSessions (line 772) | async function parseJsonlSessions(filePath) {
function parseAgentTools (line 926) | async function parseAgentTools(filePath) {
function getSessionMessages (line 983) | async function getSessionMessages(projectName, sessionId, limit = null, ...
function renameProject (line 1084) | async function renameProject(projectName, newDisplayName) {
function deleteSession (line 1105) | async function deleteSession(projectName, sessionId) {
function isProjectEmpty (line 1157) | async function isProjectEmpty(projectName) {
function deleteProject (line 1168) | async function deleteProject(projectName, force = false) {
function addProjectManually (line 1225) | async function addProjectManually(projectPath, displayName = null) {
function getCursorSessions (line 1274) | async function getCursorSessions(projectPath) {
function normalizeComparablePath (line 1385) | function normalizeComparablePath(inputPath) {
function findCodexJsonlFiles (line 1403) | async function findCodexJsonlFiles(dir) {
function buildCodexSessionsIndex (line 1423) | async function buildCodexSessionsIndex() {
function getCodexSessions (line 1476) | async function getCodexSessions(projectPath, options = {}) {
function isVisibleCodexUserMessage (line 1500) | function isVisibleCodexUserMessage(payload) {
function parseCodexSessionFile (line 1518) | async function parseCodexSessionFile(filePath) {
function getCodexSessionMessages (line 1590) | async function getCodexSessionMessages(sessionId, limit = null, offset =...
function deleteCodexSession (line 1835) | async function deleteCodexSession(sessionId) {
function searchConversations (line 1872) | async function searchConversations(query, limit = 50, onProjectResult = ...
function searchCodexSessionsForProject (line 2142) | async function searchCodexSessionsForProject(
function searchGeminiSessionsForProject (line 2247) | async function searchGeminiSessionsForProject(
function getGeminiCliSessions (line 2411) | async function getGeminiCliSessions(projectPath) {
function getGeminiCliSessionMessages (line 2487) | async function getGeminiCliSessionMessages(sessionId) {
FILE: server/providers/claude/adapter.js
constant PROVIDER (line 12) | const PROVIDER = 'claude';
function normalizeMessage (line 22) | function normalizeMessage(raw, sessionId) {
method fetchHistory (line 213) | async fetchHistory(sessionId, opts = {}) {
FILE: server/providers/codex/adapter.js
constant PROVIDER (line 11) | const PROVIDER = 'codex';
function normalizeCodexHistoryEntry (line 19) | function normalizeCodexHistoryEntry(raw, sessionId) {
function normalizeMessage (line 110) | function normalizeMessage(raw, sessionId) {
method fetchHistory (line 203) | async fetchHistory(sessionId, opts = {}) {
FILE: server/providers/cursor/adapter.js
constant PROVIDER (line 13) | const PROVIDER = 'cursor';
function loadCursorBlobs (line 22) | async function loadCursorBlobs(sessionId, projectPath) {
function normalizeMessage (line 143) | function normalizeMessage(raw, sessionId) {
method fetchHistory (line 163) | async fetchHistory(sessionId, opts = {}) {
method normalizeCursorBlobs (line 203) | normalizeCursorBlobs(blobs, sessionId) {
FILE: server/providers/gemini/adapter.js
constant PROVIDER (line 12) | const PROVIDER = 'gemini';
function normalizeMessage (line 21) | function normalizeMessage(raw, sessionId) {
method fetchHistory (line 85) | async fetchHistory(sessionId, opts = {}) {
FILE: server/providers/registry.js
function getProvider (line 34) | function getProvider(name) {
function getAllProviders (line 42) | function getAllProviders() {
FILE: server/providers/types.js
function generateMessageId (line 102) | function generateMessageId(prefix = 'msg') {
function createNormalizedMessage (line 111) | function createNormalizedMessage(fields) {
FILE: server/providers/utils.js
constant INTERNAL_CONTENT_PREFIXES (line 11) | const INTERNAL_CONTENT_PREFIXES = Object.freeze([
function isInternalContent (line 27) | function isInternalContent(content) {
FILE: server/routes/agent.js
function getGitRemoteUrl (line 69) | async function getGitRemoteUrl(repoPath) {
function normalizeGitHubUrl (line 106) | function normalizeGitHubUrl(url) {
function parseGitHubUrl (line 121) | function parseGitHubUrl(url) {
function autogenerateBranchName (line 139) | function autogenerateBranchName(message) {
function validateBranchName (line 188) | function validateBranchName(branchName) {
function getCommitMessages (line 227) | async function getCommitMessages(projectPath, limit = 5) {
function createGitHubBranch (line 269) | async function createGitHubBranch(octokit, owner, repo, branchName, base...
function createGitHubPR (line 309) | async function createGitHubPR(octokit, owner, repo, branchName, title, b...
function cloneGitHubRepo (line 334) | async function cloneGitHubRepo(githubUrl, githubToken = null, projectPat...
function cleanupProject (line 421) | async function cleanupProject(projectPath, sessionId = null) {
class SSEStreamWriter (line 452) | class SSEStreamWriter {
method constructor (line 453) | constructor(res, userId = null) {
method send (line 460) | send(data) {
method end (line 469) | end() {
method setSessionId (line 476) | setSessionId(sessionId) {
method getSessionId (line 480) | getSessionId() {
class ResponseCollector (line 488) | class ResponseCollector {
method constructor (line 489) | constructor(userId = null) {
method send (line 495) | send(data) {
method end (line 514) | end() {
method setSessionId (line 518) | setSessionId(sessionId) {
method getSessionId (line 522) | getSessionId() {
method getMessages (line 526) | getMessages() {
method getAssistantMessages (line 533) | getAssistantMessages() {
method getTotalTokens (line 562) | getTotalTokens() {
FILE: server/routes/cli-auth.js
function loadClaudeSettingsEnv (line 99) | async function loadClaudeSettingsEnv() {
function checkClaudeCredentials (line 136) | async function checkClaudeCredentials() {
function checkCursorStatus (line 204) | function checkCursorStatus() {
function checkCodexCredentials (line 297) | async function checkCodexCredentials() {
function checkGeminiCredentials (line 360) | async function checkGeminiCredentials() {
FILE: server/routes/codex.js
function createCliResponder (line 12) | function createCliResponder(res) {
function parseCodexListOutput (line 277) | function parseCodexListOutput(output) {
function parseCodexGetOutput (line 307) | function parseCodexGetOutput(output) {
FILE: server/routes/commands.js
function scanCommandsDirectory (line 21) | async function scanCommandsDirectory(dir, baseDir, namespace) {
FILE: server/routes/cursor.js
function visit (line 657) | function visit(nodeId) {
FILE: server/routes/git.js
constant COMMIT_DIFF_CHARACTER_LIMIT (line 10) | const COMMIT_DIFF_CHARACTER_LIMIT = 500_000;
function spawnAsync (line 12) | function spawnAsync(command, args, options = {}) {
function validateCommitRef (line 50) | function validateCommitRef(commit) {
function validateBranchName (line 58) | function validateBranchName(branch) {
function validateFilePath (line 65) | function validateFilePath(file, projectPath) {
function validateRemoteName (line 81) | function validateRemoteName(remote) {
function validateProjectPath (line 88) | function validateProjectPath(projectPath) {
function getActualProjectPath (line 105) | async function getActualProjectPath(projectName) {
function stripDiffHeaders (line 117) | function stripDiffHeaders(diff) {
function validateGitRepository (line 146) | async function validateGitRepository(projectPath) {
function getGitErrorDetails (line 169) | function getGitErrorDetails(error) {
function isMissingHeadRevisionError (line 173) | function isMissingHeadRevisionError(error) {
function getCurrentBranchName (line 181) | async function getCurrentBranchName(projectPath) {
function repositoryHasCommits (line 197) | async function repositoryHasCommits(projectPath) {
function getRepositoryRootPath (line 209) | async function getRepositoryRootPath(projectPath) {
function normalizeRepositoryRelativeFilePath (line 214) | function normalizeRepositoryRelativeFilePath(filePath) {
function parseStatusFilePaths (line 222) | function parseStatusFilePaths(statusOutput) {
function buildFilePathCandidates (line 235) | function buildFilePathCandidates(projectPath, repositoryRootPath, filePa...
function resolveRepositoryFilePath (line 251) | async function resolveRepositoryFilePath(projectPath, filePath) {
function generateCommitMessageWithAI (line 915) | async function generateCommitMessageWithAI(files, diffContext, provider,...
function cleanCommitMessage (line 1015) | function cleanCommitMessage(text) {
FILE: server/routes/mcp.js
function parseClaudeListOutput (line 468) | function parseClaudeListOutput(output) {
function parseClaudeGetOutput (line 520) | function parseClaudeGetOutput(output) {
FILE: server/routes/projects.js
function sanitizeGitError (line 10) | function sanitizeGitError(message, token) {
constant WORKSPACES_ROOT (line 16) | const WORKSPACES_ROOT = process.env.WORKSPACES_ROOT || os.homedir();
constant FORBIDDEN_PATHS (line 19) | const FORBIDDEN_PATHS = [
function validateWorkspacePath (line 51) | async function validateWorkspacePath(requestedPath) {
function getGithubTokenById (line 313) | async function getGithubTokenById(tokenId, userId) {
function cloneGitHubRepository (line 484) | function cloneGitHubRepository(githubUrl, destinationPath, githubToken =...
FILE: server/routes/taskmaster.js
function checkTaskMasterInstallation (line 32) | async function checkTaskMasterInstallation() {
function detectTaskMasterFolder (line 108) | async function detectTaskMasterFolder(projectPath) {
function getAvailableTemplates (line 1924) | async function getAvailableTemplates() {
FILE: server/routes/user.js
function spawnAsync (line 9) | function spawnAsync(command, args, options = {}) {
FILE: server/services/notification-orchestrator.js
constant KIND_TO_PREF_KEY (line 4) | const KIND_TO_PREF_KEY = {
constant PROVIDER_LABELS (line 10) | const PROVIDER_LABELS = {
constant DEDUPE_WINDOW_MS (line 19) | const DEDUPE_WINDOW_MS = 20000;
function shouldSendPush (line 30) | function shouldSendPush(preferences, event) {
function isDuplicate (line 38) | function isDuplicate(event) {
function createNotificationEvent (line 48) | function createNotificationEvent({
function normalizeErrorMessage (line 71) | function normalizeErrorMessage(error) {
function normalizeSessionName (line 87) | function normalizeSessionName(sessionName) {
function resolveSessionName (line 100) | function resolveSessionName(event) {
function buildPushBody (line 113) | function buildPushBody(event) {
function sendWebPush (line 140) | async function sendWebPush(userId, event) {
function notifyUserIfEnabled (line 172) | function notifyUserIfEnabled({ userId, event }) {
function notifyRunStopped (line 190) | function notifyRunStopped({ userId, provider, sessionId = null, stopReas...
function notifyRunFailed (line 205) | function notifyRunFailed({ userId, provider, sessionId = null, error, se...
FILE: server/services/vapid-keys.js
function ensureVapidKeys (line 6) | function ensureVapidKeys() {
function getPublicKey (line 21) | function getPublicKey() {
function configureWebPush (line 25) | function configureWebPush() {
FILE: server/sessionManager.js
class SessionManager (line 5) | class SessionManager {
method constructor (line 6) | constructor() {
method init (line 14) | async init() {
method initSessionsDir (line 19) | async initSessionsDir() {
method createSession (line 28) | createSession(sessionId, projectPath) {
method addMessage (line 50) | addMessage(sessionId, role, content) {
method getSession (line 73) | getSession(sessionId) {
method getProjectSessions (line 78) | getProjectSessions(projectPath) {
method getSessionSummary (line 98) | getSessionSummary(session) {
method buildConversationContext (line 114) | buildConversationContext(sessionId, maxMessages = 10) {
method _safeFilePath (line 140) | _safeFilePath(sessionId) {
method saveSession (line 146) | async saveSession(sessionId) {
method loadSessions (line 159) | async loadSessions() {
method deleteSession (line 195) | async deleteSession(sessionId) {
method getSessionMessages (line 207) | getSessionMessages(sessionId) {
FILE: server/utils/commandParser.js
constant MAX_INCLUDE_DEPTH (line 11) | const MAX_INCLUDE_DEPTH = 3;
constant BASH_TIMEOUT (line 12) | const BASH_TIMEOUT = 30000;
constant BASH_COMMAND_ALLOWLIST (line 13) | const BASH_COMMAND_ALLOWLIST = [
function parseCommand (line 33) | function parseCommand(content) {
function replaceArguments (line 52) | function replaceArguments(content, args) {
function isPathSafe (line 80) | function isPathSafe(filePath, basePath) {
function processFileIncludes (line 98) | async function processFileIncludes(content, basePath, depth = 0) {
function validateCommand (line 150) | function validateCommand(commandString) {
function isBashCommandAllowed (line 219) | function isBashCommandAllowed(command) {
function sanitizeOutput (line 229) | function sanitizeOutput(output) {
function processBashCommands (line 250) | async function processBashCommands(content, options = {}) {
FILE: server/utils/frontmatter.js
function parseFrontmatter (line 16) | function parseFrontmatter(content) {
FILE: server/utils/gitConfig.js
function spawnAsync (line 3) | function spawnAsync(command, args) {
function getSystemGitConfig (line 20) | async function getSystemGitConfig() {
FILE: server/utils/mcp-detector.js
function detectTaskMasterMCPServer (line 18) | async function detectTaskMasterMCPServer() {
function getAllMCPServers (line 152) | async function getAllMCPServers() {
FILE: server/utils/plugin-loader.js
constant PLUGINS_DIR (line 6) | const PLUGINS_DIR = path.join(os.homedir(), '.claude-code-ui', 'plugins');
constant PLUGINS_CONFIG_PATH (line 7) | const PLUGINS_CONFIG_PATH = path.join(os.homedir(), '.claude-code-ui', '...
constant REQUIRED_MANIFEST_FIELDS (line 9) | const REQUIRED_MANIFEST_FIELDS = ['name', 'displayName', 'entry'];
function sanitizeRepoUrl (line 12) | function sanitizeRepoUrl(raw) {
constant ALLOWED_TYPES (line 23) | const ALLOWED_TYPES = ['react', 'module'];
constant ALLOWED_SLOTS (line 24) | const ALLOWED_SLOTS = ['tab'];
function getPluginsDir (line 26) | function getPluginsDir() {
function getPluginsConfig (line 33) | function getPluginsConfig() {
function savePluginsConfig (line 44) | function savePluginsConfig(config) {
function validateManifest (line 52) | function validateManifest(manifest) {
constant BUILD_TIMEOUT_MS (line 96) | const BUILD_TIMEOUT_MS = 60_000;
function runBuildIfNeeded (line 99) | function runBuildIfNeeded(dir, packageJsonPath, onSuccess, onError) {
function scanPlugins (line 145) | function scanPlugins() {
function getPluginDir (line 225) | function getPluginDir(name) {
function resolvePluginAssetPath (line 232) | function resolvePluginAssetPath(name, assetPath) {
function installPluginFromGit (line 250) | function installPluginFromGit(url) {
function updatePluginFromGit (line 370) | function updatePluginFromGit(name) {
function uninstallPlugin (line 430) | async function uninstallPlugin(name) {
FILE: server/utils/plugin-process-manager.js
function startPluginServer (line 15) | function startPluginServer(name, pluginDir, serverEntry) {
function stopPluginServer (line 111) | function stopPluginServer(name) {
function getPluginPort (line 141) | function getPluginPort(name) {
function isPluginRunning (line 148) | function isPluginRunning(name) {
function stopAllPlugins (line 155) | function stopAllPlugins() {
function startEnabledPluginServers (line 167) | async function startEnabledPluginServers() {
FILE: server/utils/taskmaster-websocket.js
function broadcastTaskMasterProjectUpdate (line 15) | function broadcastTaskMasterProjectUpdate(wss, projectName, taskMasterDa...
function broadcastTaskMasterTasksUpdate (line 46) | function broadcastTaskMasterTasksUpdate(wss, projectName, tasksData) {
function broadcastMCPStatusChange (line 76) | function broadcastMCPStatusChange(wss, mcpStatus) {
function broadcastTaskMasterUpdate (line 106) | function broadcastTaskMasterUpdate(wss, updateType, data = {}) {
FILE: shared/modelConstants.js
constant CLAUDE_MODELS (line 13) | const CLAUDE_MODELS = {
constant CURSOR_MODELS (line 29) | const CURSOR_MODELS = {
constant CODEX_MODELS (line 58) | const CODEX_MODELS = {
constant GEMINI_MODELS (line 75) | const GEMINI_MODELS = {
FILE: shared/networkHosts.js
function isWildcardHost (line 1) | function isWildcardHost(host) {
function isLoopbackHost (line 5) | function isLoopbackHost(host) {
function normalizeLoopbackHost (line 9) | function normalizeLoopbackHost(host) {
function getConnectableHost (line 17) | function getConnectableHost(host) {
FILE: src/App.tsx
function App (line 12) | function App() {
FILE: src/components/app/AppContent.tsx
function AppContent (line 12) | function AppContent() {
FILE: src/components/app/MobileNav.tsx
constant PLUGIN_ICON_MAP (line 23) | const PLUGIN_ICON_MAP: Record<string, LucideIcon> = {
type CoreTabId (line 27) | type CoreTabId = Exclude<AppTab, `plugin:${string}` | 'preview'>;
type CoreNavItem (line 28) | type CoreNavItem = {
type MobileNavProps (line 34) | type MobileNavProps = {
function MobileNav (line 40) | function MobileNav({ activeTab, setActiveTab, isInputFocused }: MobileNa...
FILE: src/components/auth/constants.ts
constant AUTH_TOKEN_STORAGE_KEY (line 1) | const AUTH_TOKEN_STORAGE_KEY = 'auth-token';
constant AUTH_ERROR_MESSAGES (line 3) | const AUTH_ERROR_MESSAGES = {
FILE: src/components/auth/context/AuthContext.tsx
function useAuth (line 28) | function useAuth(): AuthContextValue {
function AuthProvider (line 37) | function AuthProvider({ children }: AuthProviderProps) {
FILE: src/components/auth/types.ts
type AuthUser (line 3) | type AuthUser = {
type AuthActionResult (line 9) | type AuthActionResult = { success: true } | { success: false; error: str...
type AuthSessionPayload (line 11) | type AuthSessionPayload = {
type AuthStatusPayload (line 18) | type AuthStatusPayload = {
type AuthUserPayload (line 22) | type AuthUserPayload = {
type OnboardingStatusPayload (line 26) | type OnboardingStatusPayload = {
type ApiErrorPayload (line 30) | type ApiErrorPayload = {
type AuthContextValue (line 35) | type AuthContextValue = {
type AuthProviderProps (line 48) | type AuthProviderProps = {
FILE: src/components/auth/utils.ts
function parseJsonSafely (line 3) | async function parseJsonSafely<T>(response: Response): Promise<T | null> {
function resolveApiErrorMessage (line 11) | function resolveApiErrorMessage(payload: ApiErrorPayload | null, fallbac...
FILE: src/components/auth/view/AuthErrorAlert.tsx
type AuthErrorAlertProps (line 1) | type AuthErrorAlertProps = {
function AuthErrorAlert (line 5) | function AuthErrorAlert({ errorMessage }: AuthErrorAlertProps) {
FILE: src/components/auth/view/AuthInputField.tsx
type AuthInputFieldProps (line 1) | type AuthInputFieldProps = {
function AuthInputField (line 19) | function AuthInputField({
FILE: src/components/auth/view/AuthLoadingScreen.tsx
function AuthLoadingScreen (line 5) | function AuthLoadingScreen() {
FILE: src/components/auth/view/AuthScreenLayout.tsx
type AuthScreenLayoutProps (line 4) | type AuthScreenLayoutProps = {
function AuthScreenLayout (line 12) | function AuthScreenLayout({
FILE: src/components/auth/view/LoginForm.tsx
type LoginFormState (line 9) | type LoginFormState = {
function LoginForm (line 24) | function LoginForm() {
FILE: src/components/auth/view/ProtectedRoute.tsx
type ProtectedRouteProps (line 9) | type ProtectedRouteProps = {
function ProtectedRoute (line 13) | function ProtectedRoute({ children }: ProtectedRouteProps) {
FILE: src/components/auth/view/SetupForm.tsx
type SetupFormState (line 8) | type SetupFormState = {
function validateSetupForm (line 25) | function validateSetupForm(formState: SetupFormState): string | null {
function SetupForm (line 51) | function SetupForm() {
FILE: src/components/chat/hooks/useChatComposerState.ts
type PendingViewSession (line 27) | type PendingViewSession = {
type UseChatComposerStateArgs (line 32) | interface UseChatComposerStateArgs {
type MentionableFile (line 65) | interface MentionableFile {
type CommandExecutionResult (line 70) | interface CommandExecutionResult {
function useChatComposerState (line 104) | function useChatComposerState({
FILE: src/components/chat/hooks/useChatMessages.ts
function normalizedToChatMessages (line 17) | function normalizedToChatMessages(messages: NormalizedMessage[]): ChatMe...
FILE: src/components/chat/hooks/useChatProviderState.ts
type UseChatProviderStateArgs (line 7) | interface UseChatProviderStateArgs {
function useChatProviderState (line 11) | function useChatProviderState({ selectedSession }: UseChatProviderStateA...
FILE: src/components/chat/hooks/useChatRealtimeHandlers.ts
type PendingViewSession (line 7) | type PendingViewSession = {
type LatestChatMessage (line 12) | type LatestChatMessage = {
type UseChatRealtimeHandlersArgs (line 49) | interface UseChatRealtimeHandlersArgs {
function useChatRealtimeHandlers (line 78) | function useChatRealtimeHandlers({
FILE: src/components/chat/hooks/useChatSessionState.ts
constant MESSAGES_PER_PAGE (line 10) | const MESSAGES_PER_PAGE = 20;
constant INITIAL_VISIBLE_MESSAGES (line 11) | const INITIAL_VISIBLE_MESSAGES = 100;
type PendingViewSession (line 13) | type PendingViewSession = {
type UseChatSessionStateArgs (line 18) | interface UseChatSessionStateArgs {
type ScrollRestoreState (line 31) | interface ScrollRestoreState {
function chatMessageToNormalized (line 40) | function chatMessageToNormalized(
function useChatSessionState (line 91) | function useChatSessionState({
FILE: src/components/chat/hooks/useFileMentions.tsx
type ProjectFileNode (line 7) | interface ProjectFileNode {
type MentionableFile (line 14) | interface MentionableFile {
type UseFileMentionsOptions (line 20) | interface UseFileMentionsOptions {
function useFileMentions (line 49) | function useFileMentions({ selectedProject, input, setInput, textareaRef...
FILE: src/components/chat/hooks/useSlashCommands.ts
constant COMMAND_QUERY_DEBOUNCE_MS (line 8) | const COMMAND_QUERY_DEBOUNCE_MS = 150;
type SlashCommand (line 10) | interface SlashCommand {
type UseSlashCommandsOptions (line 20) | interface UseSlashCommandsOptions {
function useSlashCommands (line 51) | function useSlashCommands({
FILE: src/components/chat/tools/ToolRenderer.tsx
type DiffLine (line 7) | type DiffLine = {
type ToolRendererProps (line 13) | interface ToolRendererProps {
function getToolCategory (line 33) | function getToolCategory(toolName: string): string {
FILE: src/components/chat/tools/components/CollapsibleDisplay.tsx
type CollapsibleDisplayProps (line 4) | interface CollapsibleDisplayProps {
FILE: src/components/chat/tools/components/CollapsibleSection.tsx
type CollapsibleSectionProps (line 3) | interface CollapsibleSectionProps {
FILE: src/components/chat/tools/components/ContentRenderers/FileListContent.tsx
type FileListItem (line 3) | interface FileListItem {
type FileListContentProps (line 8) | interface FileListContentProps {
FILE: src/components/chat/tools/components/ContentRenderers/MarkdownContent.tsx
type MarkdownContentProps (line 4) | interface MarkdownContentProps {
FILE: src/components/chat/tools/components/ContentRenderers/QuestionAnswerContent.tsx
type QuestionAnswerContentProps (line 4) | interface QuestionAnswerContentProps {
FILE: src/components/chat/tools/components/ContentRenderers/TaskListContent.tsx
type TaskItem (line 3) | interface TaskItem {
type TaskListContentProps (line 11) | interface TaskListContentProps {
function parseTaskContent (line 15) | function parseTaskContent(content: string): TaskItem[] {
FILE: src/components/chat/tools/components/ContentRenderers/TextContent.tsx
type TextContentProps (line 3) | interface TextContentProps {
FILE: src/components/chat/tools/components/ContentRenderers/TodoList.tsx
type TodoStatus (line 5) | type TodoStatus = 'completed' | 'in_progress' | 'pending';
type TodoPriority (line 6) | type TodoPriority = 'high' | 'medium' | 'low';
type TodoItem (line 8) | type TodoItem = {
type NormalizedTodoItem (line 15) | type NormalizedTodoItem = {
type StatusConfig (line 22) | type StatusConfig = {
constant STATUS_CONFIG (line 30) | const STATUS_CONFIG: Record<TodoStatus, StatusConfig> = {
constant PRIORITY_BADGE_CLASS (line 54) | const PRIORITY_BADGE_CLASS: Record<TodoPriority, string> = {
FILE: src/components/chat/tools/components/OneLineDisplay.tsx
type ActionType (line 4) | type ActionType = 'copy' | 'open-file' | 'jump-to-results' | 'none';
type OneLineDisplayProps (line 6) | interface OneLineDisplayProps {
FILE: src/components/chat/tools/components/SubagentContainer.tsx
type SubagentContainerProps (line 5) | interface SubagentContainerProps {
FILE: src/components/chat/tools/components/ToolDiffViewer.tsx
type DiffLine (line 3) | type DiffLine = {
type ToolDiffViewerProps (line 9) | interface ToolDiffViewerProps {
FILE: src/components/chat/tools/configs/permissionPanelRegistry.ts
type PermissionPanelProps (line 4) | interface PermissionPanelProps {
function registerPermissionPanel (line 14) | function registerPermissionPanel(
function getPermissionPanel (line 21) | function getPermissionPanel(
FILE: src/components/chat/tools/configs/toolConfigs.ts
type ToolDisplayConfig (line 6) | interface ToolDisplayConfig {
constant TOOL_CONFIGS (line 44) | const TOOL_CONFIGS: Record<string, ToolDisplayConfig> = {
function getToolConfig (line 585) | function getToolConfig(toolName: string): ToolDisplayConfig {
function shouldHideToolResult (line 592) | function shouldHideToolResult(toolName: string, toolResult: any): boolean {
FILE: src/components/chat/types/types.ts
type Provider (line 3) | type Provider = SessionProvider;
type PermissionMode (line 5) | type PermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions' | ...
type ChatImage (line 7) | interface ChatImage {
type ToolResult (line 12) | interface ToolResult {
type SubagentChildTool (line 20) | interface SubagentChildTool {
type ChatMessage (line 28) | interface ChatMessage {
type ClaudeSettings (line 52) | interface ClaudeSettings {
type ClaudePermissionSuggestion (line 61) | interface ClaudePermissionSuggestion {
type PermissionGrantResult (line 67) | interface PermissionGrantResult {
type PendingPermissionRequest (line 73) | interface PendingPermissionRequest {
type QuestionOption (line 82) | interface QuestionOption {
type Question (line 87) | interface Question {
type ChatInterfaceProps (line 94) | interface ChatInterfaceProps {
FILE: src/components/chat/utils/chatFormatting.ts
function decodeHtmlEntities (line 1) | function decodeHtmlEntities(text: string) {
function normalizeInlineCodeFences (line 11) | function normalizeInlineCodeFences(text: string) {
function unescapeWithMathProtection (line 20) | function unescapeWithMathProtection(text: string) {
function escapeRegExp (line 45) | function escapeRegExp(value: string) {
function formatUsageLimitText (line 49) | function formatUsageLimitText(text: string) {
FILE: src/components/chat/utils/chatPermissions.ts
function buildClaudeToolPermissionEntry (line 5) | function buildClaudeToolPermissionEntry(toolName?: string, toolInput?: u...
function formatToolInputForDisplay (line 22) | function formatToolInputForDisplay(input: unknown) {
function getClaudePermissionSuggestion (line 32) | function getClaudePermissionSuggestion(
function grantClaudeToolPermission (line 48) | function grantClaudeToolPermission(entry: string | null): PermissionGran...
FILE: src/components/chat/utils/chatStorage.ts
constant CLAUDE_SETTINGS_KEY (line 3) | const CLAUDE_SETTINGS_KEY = 'claude-settings';
function getClaudeSettings (line 46) | function getClaudeSettings(): ClaudeSettings {
FILE: src/components/chat/utils/messageTransforms.ts
type DiffLine (line 1) | interface DiffLine {
type DiffCalculator (line 7) | type DiffCalculator = (oldStr: string, newStr: string) => DiffLine[];
FILE: src/components/chat/view/ChatInterface.tsx
type PendingViewSession (line 16) | type PendingViewSession = {
function ChatInterface (line 21) | function ChatInterface({
FILE: src/components/chat/view/subcomponents/AssistantThinkingIndicator.tsx
type AssistantThinkingIndicatorProps (line 4) | type AssistantThinkingIndicatorProps = {
function AssistantThinkingIndicator (line 9) | function AssistantThinkingIndicator({ selectedProvider }: AssistantThink...
FILE: src/components/chat/view/subcomponents/ChatComposer.tsx
type MentionableFile (line 22) | interface MentionableFile {
type SlashCommand (line 27) | interface SlashCommand {
type ChatComposerProps (line 37) | interface ChatComposerProps {
function ChatComposer (line 97) | function ChatComposer({
FILE: src/components/chat/view/subcomponents/ChatInputControls.tsx
type ChatInputControlsProps (line 7) | interface ChatInputControlsProps {
function ChatInputControls (line 23) | function ChatInputControls({
FILE: src/components/chat/view/subcomponents/ChatMessagesPane.tsx
type ChatMessagesPaneProps (line 11) | interface ChatMessagesPaneProps {
function ChatMessagesPane (line 57) | function ChatMessagesPane({
FILE: src/components/chat/view/subcomponents/ClaudeStatus.tsx
type ClaudeStatusProps (line 6) | type ClaudeStatusProps = {
constant ACTION_KEYS (line 17) | const ACTION_KEYS = [
constant DEFAULT_ACTION_WORDS (line 25) | const DEFAULT_ACTION_WORDS = ['Thinking', 'Processing', 'Analyzing', 'Wo...
constant ANIMATION_STEPS (line 26) | const ANIMATION_STEPS = 40;
constant PROVIDER_LABEL_KEYS (line 28) | const PROVIDER_LABEL_KEYS: Record<string, string> = {
function formatElapsedTime (line 35) | function formatElapsedTime(totalSeconds: number, t: (key: string, option...
function ClaudeStatus (line 50) | function ClaudeStatus({
FILE: src/components/chat/view/subcomponents/CommandMenu.tsx
type CommandMenuCommand (line 4) | type CommandMenuCommand = {
type CommandMenuProps (line 14) | type CommandMenuProps = {
function CommandMenu (line 80) | function CommandMenu({
FILE: src/components/chat/view/subcomponents/ImageAttachment.tsx
type ImageAttachmentProps (line 3) | interface ImageAttachmentProps {
FILE: src/components/chat/view/subcomponents/Markdown.tsx
type MarkdownProps (line 12) | type MarkdownProps = {
type CodeBlockProps (line 17) | type CodeBlockProps = {
function Markdown (line 146) | function Markdown({ children, className }: MarkdownProps) {
FILE: src/components/chat/view/subcomponents/MessageComponent.tsx
type DiffLine (line 17) | type DiffLine = {
type MessageComponentProps (line 23) | type MessageComponentProps = {
type InteractiveOption (line 37) | type InteractiveOption = {
type PermissionGrantState (line 43) | type PermissionGrantState = 'idle' | 'granted' | 'error';
constant COPY_HIDDEN_TOOL_NAMES (line 44) | const COPY_HIDDEN_TOOL_NAMES = new Set(['Bash', 'Edit', 'Write', 'ApplyP...
FILE: src/components/chat/view/subcomponents/MessageCopyControl.tsx
constant COPY_SUCCESS_TIMEOUT_MS (line 5) | const COPY_SUCCESS_TIMEOUT_MS = 2000;
type CopyFormat (line 7) | type CopyFormat = 'text' | 'markdown';
type CopyFormatOption (line 9) | type CopyFormatOption = {
FILE: src/components/chat/view/subcomponents/PermissionRequestsBanner.tsx
type PermissionRequestsBannerProps (line 10) | interface PermissionRequestsBannerProps {
function PermissionRequestsBanner (line 19) | function PermissionRequestsBanner({
FILE: src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx
type ProviderSelectionEmptyStateProps (line 14) | type ProviderSelectionEmptyStateProps = {
type ProviderDef (line 34) | type ProviderDef = {
constant PROVIDERS (line 43) | const PROVIDERS: ProviderDef[] = [
function getModelConfig (line 78) | function getModelConfig(p: SessionProvider) {
function getModelValue (line 85) | function getModelValue(
function ProviderSelectionEmptyState (line 98) | function ProviderSelectionEmptyState({
FILE: src/components/chat/view/subcomponents/ThinkingModeSelector.tsx
type ThinkingModeSelectorProps (line 6) | type ThinkingModeSelectorProps = {
function ThinkingModeSelector (line 13) | function ThinkingModeSelector({ selectedMode, onModeChange, onClose, cla...
FILE: src/components/chat/view/subcomponents/TokenUsagePie.tsx
type TokenUsagePieProps (line 1) | type TokenUsagePieProps = {
function TokenUsagePie (line 6) | function TokenUsagePie({ used, total }: TokenUsagePieProps) {
FILE: src/components/code-editor/constants/settings.ts
constant CODE_EDITOR_STORAGE_KEYS (line 1) | const CODE_EDITOR_STORAGE_KEYS = {
constant CODE_EDITOR_DEFAULTS (line 9) | const CODE_EDITOR_DEFAULTS = {
constant CODE_EDITOR_SETTINGS_CHANGED_EVENT (line 17) | const CODE_EDITOR_SETTINGS_CHANGED_EVENT = 'codeEditorSettingsChanged';
FILE: src/components/code-editor/hooks/useCodeEditorDocument.ts
type UseCodeEditorDocumentParams (line 6) | type UseCodeEditorDocumentParams = {
FILE: src/components/code-editor/hooks/useEditorKeyboardShortcuts.ts
type UseEditorKeyboardShortcutsParams (line 3) | type UseEditorKeyboardShortcutsParams = {
FILE: src/components/code-editor/hooks/useEditorSidebar.ts
type UseEditorSidebarOptions (line 6) | type UseEditorSidebarOptions = {
FILE: src/components/code-editor/types/types.ts
type CodeEditorDiffInfo (line 1) | type CodeEditorDiffInfo = {
type CodeEditorFile (line 7) | type CodeEditorFile = {
type CodeEditorSettingsState (line 15) | type CodeEditorSettingsState = {
FILE: src/components/code-editor/utils/binaryFile.ts
constant BINARY_EXTENSIONS (line 2) | const BINARY_EXTENSIONS = [
FILE: src/components/code-editor/utils/editorExtensions.ts
method token (line 15) | token(stream) {
method constructor (line 122) | constructor(view: EditorView) {
method update (line 136) | update() {}
method destroy (line 138) | destroy() {}
FILE: src/components/code-editor/utils/editorToolbarPanel.ts
type EditorToolbarLabels (line 5) | type EditorToolbarLabels = {
type CreateEditorToolbarPanelParams (line 15) | type CreateEditorToolbarPanelParams = {
FILE: src/components/code-editor/view/CodeEditor.tsx
type CodeEditorProps (line 19) | type CodeEditorProps = {
function CodeEditor (line 29) | function CodeEditor({
FILE: src/components/code-editor/view/EditorSidebar.tsx
type EditorSidebarProps (line 6) | type EditorSidebarProps = {
constant MIN_LEFT_CONTENT_WIDTH (line 21) | const MIN_LEFT_CONTENT_WIDTH = 200;
constant MIN_EDITOR_WIDTH (line 23) | const MIN_EDITOR_WIDTH = 280;
function EditorSidebar (line 25) | function EditorSidebar({
FILE: src/components/code-editor/view/subcomponents/CodeEditorBinaryFile.tsx
type CodeEditorBinaryFileProps (line 3) | type CodeEditorBinaryFileProps = {
function CodeEditorBinaryFile (line 13) | function CodeEditorBinaryFile({
FILE: src/components/code-editor/view/subcomponents/CodeEditorFooter.tsx
type CodeEditorFooterProps (line 1) | type CodeEditorFooterProps = {
function CodeEditorFooter (line 8) | function CodeEditorFooter({
FILE: src/components/code-editor/view/subcomponents/CodeEditorHeader.tsx
type CodeEditorHeaderProps (line 4) | type CodeEditorHeaderProps = {
function CodeEditorHeader (line 33) | function CodeEditorHeader({
FILE: src/components/code-editor/view/subcomponents/CodeEditorLoadingState.tsx
type CodeEditorLoadingStateProps (line 3) | type CodeEditorLoadingStateProps = {
function CodeEditorLoadingState (line 9) | function CodeEditorLoadingState({
FILE: src/components/code-editor/view/subcomponents/CodeEditorSurface.tsx
type CodeEditorSurfaceProps (line 6) | type CodeEditorSurfaceProps = {
function CodeEditorSurface (line 17) | function CodeEditorSurface({
FILE: src/components/code-editor/view/subcomponents/markdown/MarkdownCodeBlock.tsx
type MarkdownCodeBlockProps (line 7) | type MarkdownCodeBlockProps = {
function MarkdownCodeBlock (line 12) | function MarkdownCodeBlock({
FILE: src/components/code-editor/view/subcomponents/markdown/MarkdownPreview.tsx
type MarkdownPreviewProps (line 9) | type MarkdownPreviewProps = {
function MarkdownPreview (line 39) | function MarkdownPreview({ content }: MarkdownPreviewProps) {
FILE: src/components/file-tree/constants/constants.ts
constant FILE_TREE_VIEW_MODE_STORAGE_KEY (line 3) | const FILE_TREE_VIEW_MODE_STORAGE_KEY = 'file-tree-view-mode';
constant FILE_TREE_DEFAULT_VIEW_MODE (line 5) | const FILE_TREE_DEFAULT_VIEW_MODE: FileTreeViewMode = 'detailed';
constant FILE_TREE_VIEW_MODES (line 7) | const FILE_TREE_VIEW_MODES: FileTreeViewMode[] = ['simple', 'compact', '...
constant IMAGE_FILE_EXTENSIONS (line 9) | const IMAGE_FILE_EXTENSIONS = new Set([
FILE: src/components/file-tree/constants/fileIcons.ts
constant ICON_SIZE_CLASS (line 41) | const ICON_SIZE_CLASS = 'w-4 h-4 flex-shrink-0';
constant FILE_ICON_MAP (line 43) | const FILE_ICON_MAP: FileIconMap = {
constant FILENAME_ICON_MAP (line 156) | const FILENAME_ICON_MAP: FileIconMap = {
function getFileIconData (line 209) | function getFileIconData(filename: string): FileIconData {
FILE: src/components/file-tree/hooks/useExpandedDirectories.ts
type UseExpandedDirectoriesResult (line 3) | type UseExpandedDirectoriesResult = {
function useExpandedDirectories (line 10) | function useExpandedDirectories(): UseExpandedDirectoriesResult {
FILE: src/components/file-tree/hooks/useFileTreeData.ts
type UseFileTreeDataResult (line 6) | type UseFileTreeDataResult = {
function useFileTreeData (line 12) | function useFileTreeData(selectedProject: Project | null): UseFileTreeDa...
FILE: src/components/file-tree/hooks/useFileTreeOperations.ts
constant INVALID_FILENAME_CHARS (line 9) | const INVALID_FILENAME_CHARS = /[<>:"/\\|?*\x00-\x1f]/;
constant RESERVED_NAMES (line 10) | const RESERVED_NAMES = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i;
type ToastMessage (line 12) | type ToastMessage = {
type DeleteConfirmation (line 17) | type DeleteConfirmation = {
type UseFileTreeOperationsOptions (line 22) | type UseFileTreeOperationsOptions = {
type UseFileTreeOperationsResult (line 28) | type UseFileTreeOperationsResult = {
function useFileTreeOperations (line 64) | function useFileTreeOperations({
FILE: src/components/file-tree/hooks/useFileTreeSearch.ts
type UseFileTreeSearchArgs (line 5) | type UseFileTreeSearchArgs = {
type UseFileTreeSearchResult (line 10) | type UseFileTreeSearchResult = {
function useFileTreeSearch (line 16) | function useFileTreeSearch({
FILE: src/components/file-tree/hooks/useFileTreeUpload.ts
type UseFileTreeUploadOptions (line 5) | type UseFileTreeUploadOptions = {
FILE: src/components/file-tree/hooks/useFileTreeViewMode.ts
type UseFileTreeViewModeResult (line 9) | type UseFileTreeViewModeResult = {
function useFileTreeViewMode (line 14) | function useFileTreeViewMode(): UseFileTreeViewModeResult {
FILE: src/components/file-tree/types/types.ts
type FileTreeViewMode (line 3) | type FileTreeViewMode = 'simple' | 'compact' | 'detailed';
type FileTreeItemType (line 5) | type FileTreeItemType = 'file' | 'directory';
type FileTreeNode (line 7) | interface FileTreeNode {
type FileTreeImageSelection (line 18) | interface FileTreeImageSelection {
type FileIconData (line 25) | interface FileIconData {
type FileIconMap (line 30) | type FileIconMap = Record<string, FileIconData>;
FILE: src/components/file-tree/utils/fileTreeUtils.ts
function filterFileTree (line 5) | function filterFileTree(items: FileTreeNode[], query: string): FileTreeN...
function collectExpandedDirectoryPaths (line 23) | function collectExpandedDirectoryPaths(items: FileTreeNode[]): string[] {
function formatFileSize (line 39) | function formatFileSize(bytes?: number): string {
function formatRelativeTime (line 51) | function formatRelativeTime(date: string | undefined, t: TFunction): str...
function isImageFile (line 79) | function isImageFile(filename: string): boolean {
FILE: src/components/file-tree/view/FileContextMenu.tsx
type FileContextItem (line 6) | type FileContextItem = {
type ContextMenuAction (line 17) | type ContextMenuAction = {
constant CONTEXT_MENU_WIDTH (line 28) | const CONTEXT_MENU_WIDTH = 200;
constant CONTEXT_MENU_HEIGHT (line 29) | const CONTEXT_MENU_HEIGHT = 300;
constant VIEWPORT_PADDING (line 30) | const VIEWPORT_PADDING = 10;
function calculateViewportSafePosition (line 32) | function calculateViewportSafePosition(clientX: number, clientY: number) {
function FileContextMenu (line 46) | function FileContextMenu({
FILE: src/components/file-tree/view/FileTree.tsx
type FileTreeProps (line 23) | type FileTreeProps = {
function FileTree (line 28) | function FileTree({ selectedProject, onFileOpen }: FileTreeProps) {
FILE: src/components/file-tree/view/FileTreeBody.tsx
type FileTreeBodyProps (line 8) | type FileTreeBodyProps = {
function FileTreeBody (line 35) | function FileTreeBody({
FILE: src/components/file-tree/view/FileTreeDetailedColumns.tsx
function FileTreeDetailedColumns (line 3) | function FileTreeDetailedColumns() {
FILE: src/components/file-tree/view/FileTreeEmptyState.tsx
type FileTreeEmptyStateProps (line 3) | type FileTreeEmptyStateProps = {
function FileTreeEmptyState (line 9) | function FileTreeEmptyState({ icon: Icon, title, description }: FileTree...
FILE: src/components/file-tree/view/FileTreeHeader.tsx
type FileTreeHeaderProps (line 7) | type FileTreeHeaderProps = {
function FileTreeHeader (line 22) | function FileTreeHeader({
FILE: src/components/file-tree/view/FileTreeList.tsx
type FileTreeListProps (line 5) | type FileTreeListProps = {
function FileTreeList (line 30) | function FileTreeList({
FILE: src/components/file-tree/view/FileTreeLoadingState.tsx
function FileTreeLoadingState (line 3) | function FileTreeLoadingState() {
FILE: src/components/file-tree/view/FileTreeNode.tsx
type FileTreeNodeProps (line 8) | type FileTreeNodeProps = {
type TreeItemIconProps (line 34) | type TreeItemIconProps = {
function TreeItemIcon (line 40) | function TreeItemIcon({ item, isOpen, renderFileIcon }: TreeItemIconProp...
function FileTreeNode (line 62) | function FileTreeNode({
FILE: src/components/file-tree/view/ImageViewer.tsx
type ImageViewerProps (line 7) | type ImageViewerProps = {
function ImageViewer (line 12) | function ImageViewer({ file, onClose }: ImageViewerProps) {
FILE: src/components/git-panel/constants/constants.ts
constant DEFAULT_BRANCH (line 3) | const DEFAULT_BRANCH = 'main';
constant RECENT_COMMITS_LIMIT (line 4) | const RECENT_COMMITS_LIMIT = 10;
constant FILE_STATUS_GROUPS (line 6) | const FILE_STATUS_GROUPS: GitStatusGroupEntry[] = [
constant FILE_STATUS_LABELS (line 13) | const FILE_STATUS_LABELS: Record<FileStatusCode, string> = {
constant FILE_STATUS_BADGE_CLASSES (line 20) | const FILE_STATUS_BADGE_CLASSES: Record<FileStatusCode, string> = {
constant CONFIRMATION_TITLES (line 27) | const CONFIRMATION_TITLES: Record<ConfirmActionType, string> = {
constant CONFIRMATION_ACTION_LABELS (line 38) | const CONFIRMATION_ACTION_LABELS: Record<ConfirmActionType, string> = {
constant CONFIRMATION_BUTTON_CLASSES (line 49) | const CONFIRMATION_BUTTON_CLASSES: Record<ConfirmActionType, string> = {
constant CONFIRMATION_ICON_CONTAINER_CLASSES (line 60) | const CONFIRMATION_ICON_CONTAINER_CLASSES: Record<ConfirmActionType, str...
constant CONFIRMATION_ICON_CLASSES (line 71) | const CONFIRMATION_ICON_CLASSES: Record<ConfirmActionType, string> = {
FILE: src/components/git-panel/hooks/useGitPanelController.ts
function isAbortError (line 25) | function isAbortError(error: unknown): boolean {
function readJson (line 29) | async function readJson<T>(response: Response, signal?: AbortSignal): Pr...
function useGitPanelController (line 43) | function useGitPanelController({
FILE: src/components/git-panel/hooks/useRevertLocalCommit.ts
type UseRevertLocalCommitOptions (line 5) | type UseRevertLocalCommitOptions = {
function readJson (line 10) | async function readJson<T>(response: Response): Promise<T> {
function useRevertLocalCommit (line 14) | function useRevertLocalCommit({ projectName, onSuccess }: UseRevertLocal...
FILE: src/components/git-panel/hooks/useSelectedProvider.ts
function useSelectedProvider (line 3) | function useSelectedProvider() {
FILE: src/components/git-panel/types/types.ts
type GitPanelView (line 3) | type GitPanelView = 'changes' | 'history' | 'branches';
type FileStatusCode (line 4) | type FileStatusCode = 'M' | 'A' | 'D' | 'U';
type GitStatusFileGroup (line 5) | type GitStatusFileGroup = 'modified' | 'added' | 'deleted' | 'untracked';
type ConfirmActionType (line 6) | type ConfirmActionType = 'discard' | 'delete' | 'commit' | 'pull' | 'pus...
type FileDiffInfo (line 8) | type FileDiffInfo = {
type FileOpenHandler (line 13) | type FileOpenHandler = (filePath: string, diffInfo?: FileDiffInfo) => void;
type GitPanelProps (line 15) | type GitPanelProps = {
type GitStatusResponse (line 21) | type GitStatusResponse = {
type GitRemoteStatus (line 32) | type GitRemoteStatus = {
type GitCommitSummary (line 45) | type GitCommitSummary = {
type GitDiffMap (line 54) | type GitDiffMap = Record<string, string>;
type GitStatusGroupEntry (line 56) | type GitStatusGroupEntry = {
type ConfirmationRequest (line 61) | type ConfirmationRequest = {
type UseGitPanelControllerOptions (line 67) | type UseGitPanelControllerOptions = {
type GitPanelController (line 73) | type GitPanelController = {
type GitApiErrorResponse (line 109) | type GitApiErrorResponse = {
type GitDiffResponse (line 114) | type GitDiffResponse = GitApiErrorResponse & {
type GitBranchesResponse (line 118) | type GitBranchesResponse = GitApiErrorResponse & {
type GitCommitsResponse (line 124) | type GitCommitsResponse = GitApiErrorResponse & {
type GitOperationResponse (line 128) | type GitOperationResponse = GitApiErrorResponse & {
type GitGenerateMessageResponse (line 133) | type GitGenerateMessageResponse = GitApiErrorResponse & {
type GitFileWithDiffResponse (line 137) | type GitFileWithDiffResponse = GitApiErrorResponse & {
FILE: src/components/git-panel/utils/gitPanelUtils.ts
function getAllChangedFiles (line 4) | function getAllChangedFiles(gitStatus: GitStatusResponse | null): string...
function getChangedFileCount (line 12) | function getChangedFileCount(gitStatus: GitStatusResponse | null): number {
function hasChangedFiles (line 16) | function hasChangedFiles(gitStatus: GitStatusResponse | null): boolean {
function getStatusLabel (line 20) | function getStatusLabel(status: FileStatusCode): string {
function getStatusBadgeClass (line 24) | function getStatusBadgeClass(status: FileStatusCode): string {
type CommitFileChange (line 32) | type CommitFileChange = {
type CommitFileSummary (line 41) | type CommitFileSummary = {
function parseCommitFiles (line 48) | function parseCommitFiles(showOutput: string): CommitFileSummary {
FILE: src/components/git-panel/view/GitPanel.tsx
function GitPanel (line 14) | function GitPanel({ selectedProject, isMobile = false, onFileOpen }: Git...
FILE: src/components/git-panel/view/GitPanelHeader.tsx
type GitPanelHeaderProps (line 6) | type GitPanelHeaderProps = {
function GitPanelHeader (line 31) | function GitPanelHeader({
FILE: src/components/git-panel/view/GitRepositoryErrorState.tsx
type GitRepositoryErrorStateProps (line 3) | type GitRepositoryErrorStateProps = {
function GitRepositoryErrorState (line 8) | function GitRepositoryErrorState({ error, details }: GitRepositoryErrorS...
FILE: src/components/git-panel/view/GitViewTabs.tsx
type GitViewTabsProps (line 4) | type GitViewTabsProps = {
constant TABS (line 11) | const TABS: { id: GitPanelView; label: string; Icon: typeof FileText }[]...
function GitViewTabs (line 17) | function GitViewTabs({ activeView, isHidden, changeCount, onChange }: Gi...
FILE: src/components/git-panel/view/branches/BranchesView.tsx
type BranchesViewProps (line 6) | type BranchesViewProps = {
type BranchRowProps (line 24) | type BranchRowProps = {
function BranchRow (line 35) | function BranchRow({ name, isCurrent, isRemote, aheadCount, behindCount,...
function SectionHeader (line 114) | function SectionHeader({ label, count }: { label: string; count: number ...
function BranchesView (line 127) | function BranchesView({
FILE: src/components/git-panel/view/changes/ChangesView.tsx
type ChangesViewProps (line 9) | type ChangesViewProps = {
function ChangesView (line 28) | function ChangesView({
FILE: src/components/git-panel/view/changes/CommitComposer.tsx
type CommitComposerProps (line 9) | type CommitComposerProps = {
function CommitComposer (line 19) | function CommitComposer({
FILE: src/components/git-panel/view/changes/FileChangeItem.tsx
type FileChangeItemProps (line 6) | type FileChangeItemProps = {
function FileChangeItem (line 21) | function FileChangeItem({
FILE: src/components/git-panel/view/changes/FileChangeList.tsx
type FileChangeListProps (line 5) | type FileChangeListProps = {
function FileChangeList (line 20) | function FileChangeList({
FILE: src/components/git-panel/view/changes/FileSelectionControls.tsx
type FileSelectionControlsProps (line 1) | type FileSelectionControlsProps = {
function FileSelectionControls (line 10) | function FileSelectionControls({
FILE: src/components/git-panel/view/changes/FileStatusLegend.tsx
type FileStatusLegendProps (line 5) | type FileStatusLegendProps = {
constant LEGEND_ITEMS (line 9) | const LEGEND_ITEMS = [
function FileStatusLegend (line 16) | function FileStatusLegend({ isMobile }: FileStatusLegendProps) {
FILE: src/components/git-panel/view/history/CommitHistoryItem.tsx
function formatDate (line 7) | function formatDate(dateString: string): string {
type CommitHistoryItemProps (line 15) | type CommitHistoryItemProps = {
function CommitHistoryItem (line 24) | function CommitHistoryItem({
FILE: src/components/git-panel/view/history/HistoryView.tsx
type HistoryViewProps (line 6) | type HistoryViewProps = {
function HistoryView (line 15) | function HistoryView({
FILE: src/components/git-panel/view/modals/ConfirmActionModal.tsx
type ConfirmActionModalProps (line 11) | type ConfirmActionModalProps = {
function renderConfirmActionIcon (line 17) | function renderConfirmActionIcon(actionType: ConfirmationRequest['type']) {
function ConfirmActionModal (line 37) | function ConfirmActionModal({ action, onCancel, onConfirm }: ConfirmActi...
FILE: src/components/git-panel/view/modals/NewBranchModal.tsx
type NewBranchModalProps (line 4) | type NewBranchModalProps = {
function NewBranchModal (line 12) | function NewBranchModal({
FILE: src/components/git-panel/view/shared/GitDiffViewer.tsx
type GitDiffViewerProps (line 3) | type GitDiffViewerProps = {
constant PREVIEW_CHARACTER_LIMIT (line 9) | const PREVIEW_CHARACTER_LIMIT = 200_000;
constant PREVIEW_LINE_LIMIT (line 10) | const PREVIEW_LINE_LIMIT = 1_500;
type DiffPreview (line 12) | type DiffPreview = {
function buildDiffPreview (line 18) | function buildDiffPreview(diff: string): DiffPreview {
function GitDiffViewer (line 31) | function GitDiffViewer({ diff, isMobile, wrapText }: GitDiffViewerProps) {
FILE: src/components/llm-logo-provider/ClaudeLogo.tsx
type ClaudeLogoProps (line 3) | type ClaudeLogoProps = {
FILE: src/components/llm-logo-provider/CodexLogo.tsx
type CodexLogoProps (line 4) | type CodexLogoProps = {
FILE: src/components/llm-logo-provider/CursorLogo.tsx
type CursorLogoProps (line 4) | type CursorLogoProps = {
FILE: src/components/llm-logo-provider/SessionProviderLogo.tsx
type SessionProviderLogoProps (line 7) | type SessionProviderLogoProps = {
function SessionProviderLogo (line 12) | function SessionProviderLogo({
FILE: src/components/main-content/hooks/useMobileMenuHandlers.ts
type MenuEvent (line 4) | type MenuEvent = MouseEvent<HTMLButtonElement> | TouchEvent<HTMLButtonEl...
function useMobileMenuHandlers (line 6) | function useMobileMenuHandlers(onMenuClick: () => void) {
FILE: src/components/main-content/types/types.ts
type SessionLifecycleHandler (line 4) | type SessionLifecycleHandler = (sessionId?: string | null) => void;
type TaskMasterTask (line 6) | type TaskMasterTask = {
type TaskReference (line 20) | type TaskReference = {
type TaskSelection (line 26) | type TaskSelection = TaskMasterTask | TaskReference;
type PrdFile (line 28) | type PrdFile = {
type MainContentProps (line 35) | type MainContentProps = {
type MainContentHeaderProps (line 58) | type MainContentHeaderProps = {
type MainContentStateViewProps (line 68) | type MainContentStateViewProps = {
type MobileMenuButtonProps (line 74) | type MobileMenuButtonProps = {
type TaskMasterPanelProps (line 79) | type TaskMasterPanelProps = {
FILE: src/components/main-content/view/ErrorBoundary.tsx
type ErrorFallbackProps (line 7) | type ErrorFallbackProps = FallbackProps & {
type ErrorBoundaryProps (line 12) | type ErrorBoundaryProps = {
function formatError (line 19) | function formatError(error: unknown): string {
function ErrorFallback (line 27) | function ErrorFallback({
function ErrorBoundary (line 73) | function ErrorBoundary({
FILE: src/components/main-content/view/MainContent.tsx
type TaskMasterContextValue (line 19) | type TaskMasterContextValue = {
type TasksSettingsContextValue (line 24) | type TasksSettingsContextValue = {
function MainContent (line 30) | function MainContent({
FILE: src/components/main-content/view/subcomponents/MainContentHeader.tsx
function MainContentHeader (line 7) | function MainContentHeader({
FILE: src/components/main-content/view/subcomponents/MainContentStateView.tsx
function MainContentStateView (line 6) | function MainContentStateView({ mode, isMobile, onMenuClick }: MainConte...
FILE: src/components/main-content/view/subcomponents/MainContentTabSwitcher.tsx
type MainContentTabSwitcherProps (line 9) | type MainContentTabSwitcherProps = {
type BuiltInTab (line 15) | type BuiltInTab = {
type PluginTab (line 22) | type PluginTab = {
type TabDefinition (line 30) | type TabDefinition = BuiltInTab | PluginTab;
constant BASE_TABS (line 32) | const BASE_TABS: BuiltInTab[] = [
constant TASKS_TAB (line 39) | const TASKS_TAB: BuiltInTab = {
function MainContentTabSwitcher (line 46) | function MainContentTabSwitcher({
FILE: src/components/main-content/view/subcomponents/MainContentTitle.tsx
type MainContentTitleProps (line 6) | type MainContentTitleProps = {
function getTabTitle (line 13) | function getTabTitle(activeTab: AppTab, shouldShowTasksTab: boolean, t: ...
function getSessionTitle (line 33) | function getSessionTitle(session: ProjectSession): string {
function MainContentTitle (line 41) | function MainContentTitle({
FILE: src/components/main-content/view/subcomponents/MobileMenuButton.tsx
function MobileMenuButton (line 4) | function MobileMenuButton({ onMenuClick, compact = false }: MobileMenuBu...
FILE: src/components/mic-button/constants/constants.ts
constant MIC_BUTTON_STATES (line 3) | const MIC_BUTTON_STATES = {
constant MIC_TAP_DEBOUNCE_MS (line 10) | const MIC_TAP_DEBOUNCE_MS = 300;
constant PROCESSING_STATE_DELAY_MS (line 11) | const PROCESSING_STATE_DELAY_MS = 2000;
constant DEFAULT_WHISPER_MODE (line 13) | const DEFAULT_WHISPER_MODE = 'default';
constant ENHANCEMENT_WHISPER_MODES (line 16) | const ENHANCEMENT_WHISPER_MODES = new Set([
constant BUTTON_BACKGROUND_BY_STATE (line 23) | const BUTTON_BACKGROUND_BY_STATE: Record<MicButtonState, string> = {
constant MIC_ERROR_BY_NAME (line 30) | const MIC_ERROR_BY_NAME = {
constant MIC_NOT_AVAILABLE_ERROR (line 37) | const MIC_NOT_AVAILABLE_ERROR =
constant MIC_NOT_SUPPORTED_ERROR (line 40) | const MIC_NOT_SUPPORTED_ERROR =
constant MIC_SECURE_CONTEXT_ERROR (line 43) | const MIC_SECURE_CONTEXT_ERROR =
FILE: src/components/mic-button/data/whisper.ts
type WhisperStatus (line 3) | type WhisperStatus = 'transcribing';
type WhisperResponse (line 5) | type WhisperResponse = {
function transcribeWithWhisper (line 10) | async function transcribeWithWhisper(
FILE: src/components/mic-button/hooks/useMicButtonController.ts
type UseMicButtonControllerArgs (line 17) | type UseMicButtonControllerArgs = {
type UseMicButtonControllerResult (line 21) | type UseMicButtonControllerResult = {
function useMicButtonController (line 44) | function useMicButtonController({
FILE: src/components/mic-button/types/types.ts
type MicButtonState (line 1) | type MicButtonState = 'idle' | 'recording' | 'transcribing' | 'processing';
FILE: src/components/mic-button/view/MicButton.tsx
type MicButtonProps (line 4) | type MicButtonProps = {
function MicButton (line 10) | function MicButton({
FILE: src/components/mic-button/view/MicButtonView.tsx
type MicButtonViewProps (line 6) | type MicButtonViewProps = {
function MicButtonView (line 34) | function MicButtonView({
FILE: src/components/onboarding/view/Onboarding.tsx
type OnboardingProps (line 17) | type OnboardingProps = {
function Onboarding (line 21) | function Onboarding({ onComplete }: OnboardingProps) {
FILE: src/components/onboarding/view/subcomponents/AgentConnectionCard.tsx
type AgentConnectionCardProps (line 5) | type AgentConnectionCardProps = {
function AgentConnectionCard (line 15) | function AgentConnectionCard({
FILE: src/components/onboarding/view/subcomponents/AgentConnectionsStep.tsx
type AgentConnectionsStepProps (line 4) | type AgentConnectionsStepProps = {
function AgentConnectionsStep (line 40) | function AgentConnectionsStep({
FILE: src/components/onboarding/view/subcomponents/GitConfigurationStep.tsx
type GitConfigurationStepProps (line 3) | type GitConfigurationStepProps = {
function GitConfigurationStep (line 11) | function GitConfigurationStep({
FILE: src/components/onboarding/view/subcomponents/OnboardingStepProgress.tsx
type OnboardingStepProgressProps (line 3) | type OnboardingStepProgressProps = {
function OnboardingStepProgress (line 12) | function OnboardingStepProgress({ currentStep }: OnboardingStepProgressP...
FILE: src/components/onboarding/view/types.ts
type ProviderAuthStatus (line 5) | type ProviderAuthStatus = {
type ProviderStatusMap (line 12) | type ProviderStatusMap = Record<CliProvider, ProviderAuthStatus>;
FILE: src/components/plugins/view/PluginIcon.tsx
type Props (line 4) | type Props = {
function PluginIcon (line 13) | function PluginIcon({ pluginName, iconFile, className }: Props) {
FILE: src/components/plugins/view/PluginSettingsTab.tsx
constant STARTER_PLUGIN_URL (line 8) | const STARTER_PLUGIN_URL = 'https://github.com/cloudcli-ai/cloudcli-plug...
function ToggleSwitch (line 11) | function ToggleSwitch({ checked, onChange, ariaLabel }: { checked: boole...
function ServerDot (line 36) | function ServerDot({ running, t }: { running: boolean; t: any }) {
type PluginCardProps (line 52) | type PluginCardProps = {
function PluginCard (line 64) | function PluginCard({
function StarterPluginCard (line 211) | function StarterPluginCard({ onInstall, installing }: { onInstall: () =>...
function PluginSettingsTab (line 268) | function PluginSettingsTab() {
FILE: src/components/plugins/view/PluginTabContent.tsx
type PluginTabContentProps (line 7) | type PluginTabContentProps = {
type PluginContext (line 13) | type PluginContext = {
function buildContext (line 19) | function buildContext(
function PluginTabContent (line 41) | function PluginTabContent({
FILE: src/components/prd-editor/PRDEditor.tsx
type PRDEditorProps (line 13) | type PRDEditorProps = {
function PRDEditor (line 23) | function PRDEditor({
FILE: src/components/prd-editor/constants.ts
constant PRD_TEMPLATE (line 1) | const PRD_TEMPLATE = `# Product Requirements Document
constant PRD_DOCS_URL (line 63) | const PRD_DOCS_URL =
constant INVALID_FILE_NAME_CHARACTERS (line 66) | const INVALID_FILE_NAME_CHARACTERS = /[<>:"/\\|?*]/g;
constant PRD_EXTENSION_PATTERN (line 67) | const PRD_EXTENSION_PATTERN = /\.(txt|md)$/i;
FILE: src/components/prd-editor/hooks/usePrdDocument.ts
type UsePrdDocumentArgs (line 7) | type UsePrdDocumentArgs = {
type UsePrdDocumentResult (line 14) | type UsePrdDocumentResult = {
function usePrdDocument (line 23) | function usePrdDocument({
FILE: src/components/prd-editor/hooks/usePrdKeyboardShortcuts.ts
type UsePrdKeyboardShortcutsArgs (line 3) | type UsePrdKeyboardShortcutsArgs = {
function usePrdKeyboardShortcuts (line 8) | function usePrdKeyboardShortcuts({
FILE: src/components/prd-editor/hooks/usePrdRegistry.ts
type UsePrdRegistryArgs (line 5) | type UsePrdRegistryArgs = {
type UsePrdRegistryResult (line 9) | type UsePrdRegistryResult = {
function getPrdFiles (line 14) | function getPrdFiles(data: PrdListResponse): ExistingPrdFile[] {
function usePrdRegistry (line 18) | function usePrdRegistry({ projectName }: UsePrdRegistryArgs): UsePrdRegi...
FILE: src/components/prd-editor/hooks/usePrdSave.ts
type UsePrdSaveArgs (line 6) | type UsePrdSaveArgs = {
type UsePrdSaveResult (line 13) | type UsePrdSaveResult = {
function usePrdSave (line 19) | function usePrdSave({
FILE: src/components/prd-editor/types.ts
type PrdFile (line 1) | type PrdFile = {
type ExistingPrdFile (line 9) | type ExistingPrdFile = {
type PrdListResponse (line 16) | type PrdListResponse = {
type SavePrdInput (line 21) | type SavePrdInput = {
type SavePrdResult (line 27) | type SavePrdResult =
FILE: src/components/prd-editor/utils/fileName.ts
function sanitizeFileName (line 3) | function sanitizeFileName(value: string): string {
function stripPrdExtension (line 7) | function stripPrdExtension(value: string): string {
function ensurePrdExtension (line 11) | function ensurePrdExtension(value: string): string {
function createDefaultPrdName (line 15) | function createDefaultPrdName(date: Date): string {
FILE: src/components/prd-editor/view/GenerateTasksModal.tsx
type GenerateTasksModalProps (line 4) | type GenerateTasksModalProps = {
function GenerateTasksModal (line 10) | function GenerateTasksModal({
FILE: src/components/prd-editor/view/OverwriteConfirmModal.tsx
type OverwriteConfirmModalProps (line 3) | type OverwriteConfirmModalProps = {
function OverwriteConfirmModal (line 11) | function OverwriteConfirmModal({
FILE: src/components/prd-editor/view/PrdEditorBody.tsx
type PrdEditorBodyProps (line 8) | type PrdEditorBodyProps = {
function PrdEditorBody (line 16) | function PrdEditorBody({
FILE: src/components/prd-editor/view/PrdEditorFooter.tsx
type PrdEditorFooterProps (line 3) | type PrdEditorFooterProps = {
type ContentStats (line 7) | type ContentStats = {
function getContentStats (line 13) | function getContentStats(content: string): ContentStats {
function PrdEditorFooter (line 21) | function PrdEditorFooter({ content }: PrdEditorFooterProps) {
FILE: src/components/prd-editor/view/PrdEditorHeader.tsx
type PrdEditorHeaderProps (line 17) | type PrdEditorHeaderProps = {
type HeaderIconButtonProps (line 38) | type HeaderIconButtonProps = {
function HeaderIconButton (line 45) | function HeaderIconButton({ title, onClick, icon, active = false }: Head...
function PrdEditorHeader (line 62) | function PrdEditorHeader({
FILE: src/components/prd-editor/view/PrdEditorLoadingState.tsx
function PrdEditorLoadingState (line 1) | function PrdEditorLoadingState() {
FILE: src/components/prd-editor/view/PrdEditorWorkspace.tsx
type PrdEditorWorkspaceProps (line 9) | type PrdEditorWorkspaceProps = {
function PrdEditorWorkspace (line 23) | function PrdEditorWorkspace({
FILE: src/components/project-creation-wizard/ProjectCreationWizard.tsx
type ProjectCreationWizardProps (line 15) | type ProjectCreationWizardProps = {
function ProjectCreationWizard (line 29) | function ProjectCreationWizard({
FILE: src/components/project-creation-wizard/components/ErrorBanner.tsx
type ErrorBannerProps (line 3) | type ErrorBannerProps = {
function ErrorBanner (line 7) | function ErrorBanner({ message }: ErrorBannerProps) {
FILE: src/components/project-creation-wizard/components/FolderBrowserModal.tsx
type FolderBrowserModalProps (line 8) | type FolderBrowserModalProps = {
function FolderBrowserModal (line 15) | function FolderBrowserModal({
FILE: src/components/project-creation-wizard/components/GithubAuthenticationCard.tsx
type GithubAuthenticationCardProps (line 6) | type GithubAuthenticationCardProps = {
function GithubAuthenticationCard (line 27) | function GithubAuthenticationCard({
FILE: src/components/project-creation-wizard/components/StepConfiguration.tsx
type StepConfigurationProps (line 8) | type StepConfigurationProps = {
function StepConfiguration (line 27) | function StepConfiguration({
FILE: src/components/project-creation-wizard/components/StepReview.tsx
type StepReviewProps (line 6) | type StepReviewProps = {
function StepReview (line 13) | function StepReview({
FILE: src/components/project-creation-wizard/components/StepTypeSelection.tsx
type StepTypeSelectionProps (line 5) | type StepTypeSelectionProps = {
function StepTypeSelection (line 10) | function StepTypeSelection({
FILE: src/components/project-creation-wizard/components/WizardFooter.tsx
type WizardFooterProps (line 6) | type WizardFooterProps = {
function WizardFooter (line 16) | function WizardFooter({
FILE: src/components/project-creation-wizard/components/WizardProgress.tsx
type WizardProgressProps (line 6) | type WizardProgressProps = {
function WizardProgress (line 10) | function WizardProgress({ step }: WizardProgressProps) {
FILE: src/components/project-creation-wizard/components/WorkspacePathField.tsx
type WorkspacePathFieldProps (line 9) | type WorkspacePathFieldProps = {
function WorkspacePathField (line 17) | function WorkspacePathField({
FILE: src/components/project-creation-wizard/data/workspaceApi.ts
type CloneWorkspaceParams (line 13) | type CloneWorkspaceParams = {
type CloneProgressHandlers (line 21) | type CloneProgressHandlers = {
FILE: src/components/project-creation-wizard/hooks/useGithubTokens.ts
type UseGithubTokensParams (line 5) | type UseGithubTokensParams = {
FILE: src/components/project-creation-wizard/types.ts
type WizardStep (line 1) | type WizardStep = 1 | 2 | 3;
type WorkspaceType (line 3) | type WorkspaceType = 'existing' | 'new';
type TokenMode (line 5) | type TokenMode = 'stored' | 'new' | 'none';
type FolderSuggestion (line 7) | type FolderSuggestion = {
type GithubTokenCredential (line 13) | type GithubTokenCredential = {
type CredentialsResponse (line 19) | type CredentialsResponse = {
type BrowseFilesystemResponse (line 24) | type BrowseFilesystemResponse = {
type CreateFolderResponse (line 30) | type CreateFolderResponse = {
type CreateWorkspacePayload (line 37) | type CreateWorkspacePayload = {
type CreateWorkspaceResponse (line 42) | type CreateWorkspaceResponse = {
type CloneProgressEvent (line 49) | type CloneProgressEvent = {
type WizardFormState (line 55) | type WizardFormState = {
FILE: src/components/project-creation-wizard/utils/pathUtils.ts
constant SSH_PREFIXES (line 3) | const SSH_PREFIXES = ['git@', 'ssh://'];
constant WINDOWS_DRIVE_PATTERN (line 4) | const WINDOWS_DRIVE_PATTERN = /^[A-Za-z]:\\?$/;
FILE: src/components/provider-auth/types.ts
type CliProvider (line 1) | type CliProvider = 'claude' | 'cursor' | 'codex' | 'gemini';
FILE: src/components/provider-auth/view/ProviderLoginModal.tsx
type LoginModalProject (line 6) | type LoginModalProject = {
type ProviderLoginModalProps (line 14) | type ProviderLoginModalProps = {
function ProviderLoginModal (line 74) | function ProviderLoginModal({
FILE: src/components/quick-settings-panel/constants.ts
constant HANDLE_POSITION_STORAGE_KEY (line 17) | const HANDLE_POSITION_STORAGE_KEY = 'quickSettingsHandlePosition';
constant WHISPER_MODE_STORAGE_KEY (line 18) | const WHISPER_MODE_STORAGE_KEY = 'whisperMode';
constant WHISPER_MODE_CHANGED_EVENT (line 19) | const WHISPER_MODE_CHANGED_EVENT = 'whisperModeChanged';
constant DEFAULT_HANDLE_POSITION (line 21) | const DEFAULT_HANDLE_POSITION = 50;
constant HANDLE_POSITION_MIN (line 22) | const HANDLE_POSITION_MIN = 10;
constant HANDLE_POSITION_MAX (line 23) | const HANDLE_POSITION_MAX = 90;
constant DRAG_THRESHOLD_PX (line 24) | const DRAG_THRESHOLD_PX = 5;
constant SETTING_ROW_CLASS (line 26) | const SETTING_ROW_CLASS =
constant TOGGLE_ROW_CLASS (line 29) | const TOGGLE_ROW_CLASS = `${SETTING_ROW_CLASS} cursor-pointer`;
constant CHECKBOX_CLASS (line 31) | const CHECKBOX_CLASS =
constant TOOL_DISPLAY_TOGGLES (line 34) | const TOOL_DISPLAY_TOGGLES: PreferenceToggleItem[] = [
constant VIEW_OPTION_TOGGLES (line 52) | const VIEW_OPTION_TOGGLES: PreferenceToggleItem[] = [
constant INPUT_SETTING_TOGGLES (line 60) | const INPUT_SETTING_TOGGLES: PreferenceToggleItem[] = [
constant WHISPER_OPTIONS (line 68) | const WHISPER_OPTIONS: WhisperOption[] = [
constant VIBE_MODE_ALIASES (line 89) | const VIBE_MODE_ALIASES: WhisperMode[] = [
FILE: src/components/quick-settings-panel/hooks/useQuickSettingsDrag.ts
type UseQuickSettingsDragProps (line 12) | type UseQuickSettingsDragProps = {
type StartDragEvent (line 16) | type StartDragEvent = ReactMouseEvent<HTMLButtonElement> | ReactTouchEve...
type MoveDragEvent (line 17) | type MoveDragEvent = MouseEvent | TouchEvent;
type EventWithClientY (line 18) | type EventWithClientY = StartDragEvent | MoveDragEvent;
function useQuickSettingsDrag (line 59) | function useQuickSettingsDrag({ isMobile }: UseQuickSettingsDragProps) {
FILE: src/components/quick-settings-panel/hooks/useWhisperMode.ts
constant ALL_VALID_MODES (line 9) | const ALL_VALID_MODES: WhisperMode[] = [
function useWhisperMode (line 34) | function useWhisperMode() {
FILE: src/components/quick-settings-panel/types.ts
type PreferenceToggleKey (line 4) | type PreferenceToggleKey =
type QuickSettingsPreferences (line 11) | type QuickSettingsPreferences = Record<PreferenceToggleKey, boolean>;
type PreferenceToggleItem (line 13) | type PreferenceToggleItem = {
type WhisperMode (line 19) | type WhisperMode =
type WhisperOptionValue (line 26) | type WhisperOptionValue = 'default' | 'prompt' | 'vibe';
type WhisperOption (line 28) | type WhisperOption = {
type QuickSettingsHandleStyle (line 35) | type QuickSettingsHandleStyle = CSSProperties;
FILE: src/components/quick-settings-panel/view/QuickSettingsContent.tsx
type QuickSettingsContentProps (line 20) | type QuickSettingsContentProps = {
function QuickSettingsContent (line 27) | function QuickSettingsContent({
FILE: src/components/quick-settings-panel/view/QuickSettingsHandle.tsx
type QuickSettingsHandleProps (line 13) | type QuickSettingsHandleProps = {
function QuickSettingsHandle (line 22) | function QuickSettingsHandle({
FILE: src/components/quick-settings-panel/view/QuickSettingsPanelHeader.tsx
function QuickSettingsPanelHeader (line 4) | function QuickSettingsPanelHeader() {
FILE: src/components/quick-settings-panel/view/QuickSettingsPanelView.tsx
function QuickSettingsPanelView (line 12) | function QuickSettingsPanelView() {
FILE: src/components/quick-settings-panel/view/QuickSettingsSection.tsx
type QuickSettingsSectionProps (line 3) | type QuickSettingsSectionProps = {
function QuickSettingsSection (line 9) | function QuickSettingsSection({
FILE: src/components/quick-settings-panel/view/QuickSettingsToggleRow.tsx
type QuickSettingsToggleRowProps (line 5) | type QuickSettingsToggleRowProps = {
function QuickSettingsToggleRow (line 12) | function QuickSettingsToggleRow({
FILE: src/components/quick-settings-panel/view/QuickSettingsWhisperSection.tsx
function QuickSettingsWhisperSection (line 6) | function QuickSettingsWhisperSection() {
FILE: src/components/settings/constants/constants.ts
constant SETTINGS_MAIN_TABS (line 15) | const SETTINGS_MAIN_TABS: SettingsMainTab[] = [
constant AGENT_PROVIDERS (line 24) | const AGENT_PROVIDERS: AgentProvider[] = ['claude', 'cursor', 'codex'];
constant AGENT_CATEGORIES (line 25) | const AGENT_CATEGORIES: AgentCategory[] = ['account', 'permissions', 'mc...
constant DEFAULT_PROJECT_SORT_ORDER (line 27) | const DEFAULT_PROJECT_SORT_ORDER: ProjectSortOrder = 'name';
constant DEFAULT_SAVE_STATUS (line 28) | const DEFAULT_SAVE_STATUS = null;
constant DEFAULT_CODE_EDITOR_SETTINGS (line 29) | const DEFAULT_CODE_EDITOR_SETTINGS: CodeEditorSettingsState = {
constant DEFAULT_AUTH_STATUS (line 37) | const DEFAULT_AUTH_STATUS: AuthStatus = {
constant DEFAULT_MCP_TEST_RESULT (line 44) | const DEFAULT_MCP_TEST_RESULT: McpTestResult = {
constant DEFAULT_MCP_TOOLS_RESULT (line 51) | const DEFAULT_MCP_TOOLS_RESULT: McpToolsResult = {
constant DEFAULT_CLAUDE_MCP_FORM (line 58) | const DEFAULT_CLAUDE_MCP_FORM: ClaudeMcpFormState = {
constant DEFAULT_CODEX_MCP_FORM (line 75) | const DEFAULT_CODEX_MCP_FORM: CodexMcpFormState = {
constant DEFAULT_CURSOR_PERMISSIONS (line 85) | const DEFAULT_CURSOR_PERMISSIONS: CursorPermissionsState = {
constant AUTH_STATUS_ENDPOINTS (line 91) | const AUTH_STATUS_ENDPOINTS: Record<AgentProvider, string> = {
FILE: src/components/settings/hooks/useCredentialsSettings.ts
type UseCredentialsSettingsArgs (line 12) | type UseCredentialsSettingsArgs = {
function useCredentialsSettings (line 21) | function useCredentialsSettings({
FILE: src/components/settings/hooks/useGitSettings.ts
type GitConfigResponse (line 4) | type GitConfigResponse = {
type SaveStatus (line 10) | type SaveStatus = 'success' | 'error' | null;
function useGitSettings (line 12) | function useGitSettings() {
FILE: src/components/settings/hooks/useSettingsController.ts
type ThemeContextValue (line 29) | type ThemeContextValue = {
type UseSettingsControllerArgs (line 34) | type UseSettingsControllerArgs = {
type StatusApiResponse (line 41) | type StatusApiResponse = {
type JsonResult (line 48) | type JsonResult = {
type McpReadResponse (line 53) | type McpReadResponse = {
type McpCliServer (line 58) | type McpCliServer = {
type McpCliReadResponse (line 68) | type McpCliReadResponse = {
type McpTestResponse (line 73) | type McpTestResponse = {
type McpToolsResponse (line 78) | type McpToolsResponse = {
type ClaudeSettingsStorage (line 83) | type ClaudeSettingsStorage = {
type CursorSettingsStorage (line 90) | type CursorSettingsStorage = {
type CodexSettingsStorage (line 96) | type CodexSettingsStorage = {
type NotificationPreferencesResponse (line 100) | type NotificationPreferencesResponse = {
type ActiveLoginProvider (line 105) | type ActiveLoginProvider = AgentProvider | '';
constant KNOWN_MAIN_TABS (line 107) | const KNOWN_MAIN_TABS: SettingsMainTab[] = ['agents', 'appearance', 'git...
function useSettingsController (line 207) | function useSettingsController({ isOpen, initialTab, projects, onClose }...
FILE: src/components/settings/types/types.ts
type SettingsMainTab (line 3) | type SettingsMainTab = 'agents' | 'appearance' | 'git' | 'api' | 'tasks'...
type AgentProvider (line 4) | type AgentProvider = 'claude' | 'cursor' | 'codex' | 'gemini';
type AgentCategory (line 5) | type AgentCategory = 'account' | 'permissions' | 'mcp';
type ProjectSortOrder (line 6) | type ProjectSortOrder = 'name' | 'date';
type SaveStatus (line 7) | type SaveStatus = 'success' | 'error' | null;
type CodexPermissionMode (line 8) | type CodexPermissionMode = 'default' | 'acceptEdits' | 'bypassPermissions';
type GeminiPermissionMode (line 9) | type GeminiPermissionMode = 'default' | 'auto_edit' | 'yolo';
type McpImportMode (line 10) | type McpImportMode = 'form' | 'json';
type McpScope (line 11) | type McpScope = 'user' | 'local';
type McpTransportType (line 12) | type McpTransportType = 'stdio' | 'sse' | 'http';
type SettingsProject (line 14) | type SettingsProject = {
type AuthStatus (line 21) | type AuthStatus = {
type KeyValueMap (line 29) | type KeyValueMap = Record<string, string>;
type McpServerConfig (line 31) | type McpServerConfig = {
type McpServer (line 40) | type McpServer = {
type ClaudeMcpFormConfig (line 52) | type ClaudeMcpFormConfig = {
type ClaudeMcpFormState (line 61) | type ClaudeMcpFormState = {
type CodexMcpFormConfig (line 72) | type CodexMcpFormConfig = {
type CodexMcpFormState (line 78) | type CodexMcpFormState = {
type McpTestResult (line 84) | type McpTestResult = {
type McpTool (line 91) | type McpTool = {
type McpToolsResult (line 96) | type McpToolsResult = {
type ClaudePermissionsState (line 103) | type ClaudePermissionsState = {
type NotificationPreferencesState (line 109) | type NotificationPreferencesState = {
type CursorPermissionsState (line 121) | type CursorPermissionsState = {
type CodeEditorSettingsState (line 127) | type CodeEditorSettingsState = {
type SettingsStoragePayload (line 135) | type SettingsStoragePayload = {
type SettingsProps (line 141) | type SettingsProps = {
type SetState (line 148) | type SetState<T> = Dispatch<SetStateAction<T>>;
FILE: src/components/settings/view/Settings.tsx
function Settings (line 19) | function Settings({ isOpen, onClose, projects = [], initialTab = 'agents...
FILE: src/components/settings/view/SettingsCard.tsx
type SettingsCardProps (line 4) | type SettingsCardProps = {
function SettingsCard (line 10) | function SettingsCard({ children, className, divided }: SettingsCardProp...
FILE: src/components/settings/view/SettingsMainTabs.tsx
type SettingsMainTabsProps (line 5) | type SettingsMainTabsProps = {
type MainTabConfig (line 10) | type MainTabConfig = {
constant TAB_CONFIG (line 17) | const TAB_CONFIG: MainTabConfig[] = [
function SettingsMainTabs (line 27) | function SettingsMainTabs({ activeTab, onChange }: SettingsMainTabsProps) {
FILE: src/components/settings/view/SettingsRow.tsx
type SettingsRowProps (line 4) | type SettingsRowProps = {
function SettingsRow (line 11) | function SettingsRow({ label, description, children, className }: Settin...
FILE: src/components/settings/view/SettingsSection.tsx
type SettingsSectionProps (line 4) | type SettingsSectionProps = {
function SettingsSection (line 11) | function SettingsSection({ title, description, children, className }: Se...
FILE: src/components/settings/view/SettingsSidebar.tsx
type SettingsSidebarProps (line 7) | type SettingsSidebarProps = {
type NavItem (line 12) | type NavItem = {
constant NAV_ITEMS (line 18) | const NAV_ITEMS: NavItem[] = [
function SettingsSidebar (line 28) | function SettingsSidebar({ activeTab, onChange }: SettingsSidebarProps) {
FILE: src/components/settings/view/SettingsToggle.tsx
type SettingsToggleProps (line 3) | type SettingsToggleProps = {
function SettingsToggle (line 10) | function SettingsToggle({ checked, onChange, ariaLabel, disabled }: Sett...
FILE: src/components/settings/view/modals/ClaudeMcpFormModal.tsx
type ClaudeMcpFormModalProps (line 9) | type ClaudeMcpFormModalProps = {
function ClaudeMcpFormModal (line 49) | function ClaudeMcpFormModal({
FILE: src/components/settings/view/modals/CodexMcpFormModal.tsx
type CodexMcpFormModalProps (line 9) | type CodexMcpFormModalProps = {
function CodexMcpFormModal (line 30) | function CodexMcpFormModal({
FILE: src/components/settings/view/tabs/AppearanceSettingsTab.tsx
type AppearanceSettingsTabProps (line 10) | type AppearanceSettingsTabProps = {
function AppearanceSettingsTab (line 21) | function AppearanceSettingsTab({
FILE: src/components/settings/view/tabs/NotificationsSettingsTab.tsx
type NotificationsSettingsTabProps (line 5) | type NotificationsSettingsTabProps = {
function NotificationsSettingsTab (line 15) | function NotificationsSettingsTab({
FILE: src/components/settings/view/tabs/agents-settings/AgentListItem.tsx
type AgentListItemProps (line 5) | type AgentListItemProps = {
type AgentConfig (line 13) | type AgentConfig = {
function AgentListItem (line 52) | function AgentListItem({
FILE: src/components/settings/view/tabs/agents-settings/AgentsSettingsTab.tsx
function AgentsSettingsTab (line 8) | function AgentsSettingsTab({
FILE: src/components/settings/view/tabs/agents-settings/sections/AgentCategoryContentSection.tsx
function AgentCategoryContentSection (line 6) | function AgentCategoryContentSection({
FILE: src/components/settings/view/tabs/agents-settings/sections/AgentCategoryTabsSection.tsx
constant AGENT_CATEGORIES (line 6) | const AGENT_CATEGORIES: AgentCategory[] = ['account', 'permissions', 'mc...
function AgentCategoryTabsSection (line 8) | function AgentCategoryTabsSection({
FILE: src/components/settings/view/tabs/agents-settings/sections/AgentSelectorSection.tsx
constant AGENT_PROVIDERS (line 6) | const AGENT_PROVIDERS: AgentProvider[] = ['claude', 'cursor', 'codex', '...
constant AGENT_NAMES (line 8) | const AGENT_NAMES: Record<AgentProvider, string> = {
function AgentSelectorSection (line 15) | function AgentSelectorSection({
FILE: src/components/settings/view/tabs/agents-settings/sections/content/AccountContent.tsx
type AccountContentProps (line 7) | type AccountContentProps = {
type AgentVisualConfig (line 13) | type AgentVisualConfig = {
function AccountContent (line 59) | function AccountContent({ agent, authStatus, onLogin }: AccountContentPr...
FILE: src/components/settings/view/tabs/agents-settings/sections/content/McpServersContent.tsx
type ClaudeMcpServersProps (line 31) | type ClaudeMcpServersProps = {
function ClaudeMcpServers (line 45) | function ClaudeMcpServers({
type CursorMcpServersProps (line 186) | type CursorMcpServersProps = {
function CursorMcpServers (line 194) | function CursorMcpServers({ servers, onAdd, onEdit, onDelete }: Omit<Cur...
type CodexMcpServersProps (line 266) | type CodexMcpServersProps = {
function CodexMcpServers (line 275) | function CodexMcpServers({ servers, onAdd, onEdit, onDelete, deleteError...
type McpServersContentProps (line 369) | type McpServersContentProps = ClaudeMcpServersProps | CursorMcpServersPr...
function McpServersContent (line 371) | function McpServersContent(props: McpServersContentProps) {
FILE: src/components/settings/view/tabs/agents-settings/sections/content/PermissionsContent.tsx
constant COMMON_CLAUDE_TOOLS (line 7) | const COMMON_CLAUDE_TOOLS = [
constant COMMON_CURSOR_COMMANDS (line 24) | const COMMON_CURSOR_COMMANDS = [
type ClaudePermissionsProps (line 52) | type ClaudePermissionsProps = {
function ClaudePermissions (line 62) | function ClaudePermissions({
type CursorPermissionsProps (line 263) | type CursorPermissionsProps = {
function CursorPermissions (line 273) | function CursorPermissions({
type CodexPermissionsProps (line 473) | type CodexPermissionsProps = {
function CodexPermissions (line 479) | function CodexPermissions({ permissionMode, onPermissionModeChange }: Om...
type GeminiPermissionsProps (line 582) | type GeminiPermissionsProps = {
function GeminiPermissions (line 589) | function GeminiPermissions({ permissionMode, onPermissionModeChange }: O...
type PermissionsContentProps (line 686) | type PermissionsContentProps = ClaudePermissionsProps | CursorPermission...
function PermissionsContent (line 688) | function PermissionsContent(props: PermissionsContentProps) {
FILE: src/components/settings/view/tabs/agents-settings/types.ts
type AgentContext (line 14) | type AgentContext = {
type AgentContextByProvider (line 19) | type AgentContextByProvider = Record<AgentProvider, AgentContext>;
type AgentsSettingsTabProps (line 21) | type AgentsSettingsTabProps = {
type AgentCategoryTabsSectionProps (line 53) | type AgentCategoryTabsSectionProps = {
type AgentSelectorSectionProps (line 58) | type AgentSelectorSectionProps = {
type AgentCategoryContentSectionProps (line 64) | type AgentCategoryContentSectionProps = {
FILE: src/components/settings/view/tabs/api-settings/CredentialsSettingsTab.tsx
function CredentialsSettingsTab (line 9) | function CredentialsSettingsTab() {
FILE: src/components/settings/view/tabs/api-settings/sections/ApiKeysSection.tsx
type ApiKeysSectionProps (line 6) | type ApiKeysSectionProps = {
function ApiKeysSection (line 18) | function ApiKeysSection({
FILE: src/components/settings/view/tabs/api-settings/sections/GithubCredentialsSection.tsx
type GithubCredentialsSectionProps (line 6) | type GithubCredentialsSectionProps = {
function GithubCredentialsSection (line 24) | function GithubCredentialsSection({
FILE: src/components/settings/view/tabs/api-settings/sections/NewApiKeyAlert.tsx
type NewApiKeyAlertProps (line 6) | type NewApiKeyAlertProps = {
function NewApiKeyAlert (line 13) | function NewApiKeyAlert({
FILE: src/components/settings/view/tabs/api-settings/sections/VersionInfoSection.tsx
type VersionInfoSectionProps (line 5) | type VersionInfoSectionProps = {
function VersionInfoSection (line 12) | function VersionInfoSection({
FILE: src/components/settings/view/tabs/api-settings/types.ts
type ApiKeyItem (line 1) | type ApiKeyItem = {
type CreatedApiKey (line 10) | type CreatedApiKey = {
type GithubCredentialItem (line 17) | type GithubCredentialItem = {
type ApiKeysResponse (line 25) | type ApiKeysResponse = {
type GithubCredentialsResponse (line 32) | type GithubCredentialsResponse = {
FILE: src/components/settings/view/tabs/git-settings/GitSettingsTab.tsx
function GitSettingsTab (line 8) | function GitSettingsTab() {
FILE: src/components/settings/view/tabs/tasks-settings/TasksSettingsTab.tsx
type TasksSettingsContextValue (line 8) | type TasksSettingsContextValue = {
function TasksSettingsTab (line 15) | function TasksSettingsTab() {
FILE: src/components/shell/constants/constants.ts
constant CODEX_DEVICE_AUTH_URL (line 3) | const CODEX_DEVICE_AUTH_URL = 'https://auth.openai.com/codex/device';
constant SHELL_RESTART_DELAY_MS (line 4) | const SHELL_RESTART_DELAY_MS = 200;
constant TERMINAL_INIT_DELAY_MS (line 5) | const TERMINAL_INIT_DELAY_MS = 100;
constant TERMINAL_RESIZE_DELAY_MS (line 6) | const TERMINAL_RESIZE_DELAY_MS = 50;
constant PROMPT_DEBOUNCE_MS (line 9) | const PROMPT_DEBOUNCE_MS = 500;
constant PROMPT_BUFFER_SCAN_LINES (line 10) | const PROMPT_BUFFER_SCAN_LINES = 20;
constant PROMPT_OPTION_SCAN_LINES (line 11) | const PROMPT_OPTION_SCAN_LINES = 15;
constant PROMPT_MAX_OPTIONS (line 12) | const PROMPT_MAX_OPTIONS = 5;
constant PROMPT_MIN_OPTIONS (line 13) | const PROMPT_MIN_OPTIONS = 2;
constant TERMINAL_OPTIONS (line 15) | const TERMINAL_OPTIONS: ITerminalOptions = {
FILE: src/components/shell/hooks/useShellConnection.ts
constant ANSI_ESCAPE_REGEX (line 9) | const ANSI_ESCAPE_REGEX =
constant PROCESS_EXIT_REGEX (line 11) | const PROCESS_EXIT_REGEX = /Process exited with code (\d+)/;
type UseShellConnectionOptions (line 13) | type UseShellConnectionOptions = {
type UseShellConnectionResult (line 30) | type UseShellConnectionResult = {
function useShellConnection (line 38) | function useShellConnection({
FILE: src/components/shell/hooks/useShellRuntime.ts
function useShellRuntime (line 9) | function useShellRuntime({
FILE: src/components/shell/hooks/useShellTerminal.ts
type UseShellTerminalOptions (line 19) | type UseShellTerminalOptions = {
type UseShellTerminalResult (line 34) | type UseShellTerminalResult = {
function useShellTerminal (line 40) | function useShellTerminal({
FILE: src/components/shell/types/types.ts
type AuthCopyStatus (line 6) | type AuthCopyStatus = 'idle' | 'copied' | 'failed';
type ShellInitMessage (line 8) | type ShellInitMessage = {
type ShellResizeMessage (line 20) | type ShellResizeMessage = {
type ShellInputMessage (line 26) | type ShellInputMessage = {
type ShellOutgoingMessage (line 31) | type ShellOutgoingMessage = ShellInitMessage | ShellResizeMessage | Shel...
type ShellIncomingMessage (line 33) | type ShellIncomingMessage =
type UseShellRuntimeOptions (line 39) | type UseShellRuntimeOptions = {
type ShellSharedRefs (line 51) | type ShellSharedRefs = {
type UseShellRuntimeResult (line 63) | type UseShellRuntimeResult = {
FILE: src/components/shell/utils/auth.ts
function isCodexLoginCommand (line 4) | function isCodexLoginCommand(command: string | null | undefined): boolean {
function resolveAuthUrlForDisplay (line 8) | function resolveAuthUrlForDisplay(command: string | null | undefined, au...
function getSessionDisplayName (line 16) | function getSessionDisplayName(session: ProjectSession | null | undefine...
FILE: src/components/shell/utils/socket.ts
function getShellWebSocketUrl (line 4) | function getShellWebSocketUrl(): string | null {
function parseShellMessage (line 20) | function parseShellMessage(payload: string): ShellIncomingMessage | null {
function sendSocketMessage (line 28) | function sendSocketMessage(ws: WebSocket | null, message: ShellOutgoingM...
FILE: src/components/shell/utils/terminalStyles.ts
constant XTERM_STYLE_ELEMENT_ID (line 1) | const XTERM_STYLE_ELEMENT_ID = 'shell-xterm-focus-style';
constant XTERM_FOCUS_STYLES (line 3) | const XTERM_FOCUS_STYLES = `
function ensureXtermFocusStyles (line 15) | function ensureXtermFocusStyles(): void {
FILE: src/components/shell/view/Shell.tsx
type CliPromptOption (line 22) | type CliPromptOption = { number: string; label: string };
type ShellProps (line 24) | type ShellProps = {
function Shell (line 35) | function Shell({
FILE: src/components/shell/view/subcomponents/ShellConnectionOverlay.tsx
type ShellConnectionOverlayProps (line 1) | type ShellConnectionOverlayProps = {
function ShellConnectionOverlay (line 11) | function ShellConnectionOverlay({
FILE: src/components/shell/view/subcomponents/ShellEmptyState.tsx
type ShellEmptyStateProps (line 1) | type ShellEmptyStateProps = {
function ShellEmptyState (line 6) | function ShellEmptyState({ title, description }: ShellEmptyStateProps) {
FILE: src/components/shell/view/subcomponents/ShellHeader.tsx
type ShellHeaderProps (line 1) | type ShellHeaderProps = {
function ShellHeader (line 19) | function ShellHeader({
FILE: src/components/shell/view/subcomponents/ShellMinimalView.tsx
type ShellMinimalViewProps (line 6) | type ShellMinimalViewProps = {
function ShellMinimalView (line 16) | function ShellMinimalView({
FILE: src/components/shell/view/subcomponents/TerminalShortcutsPanel.tsx
constant SHORTCUTS (line 12) | const SHORTCUTS = [
type TerminalShortcutsPanelProps (line 20) | type TerminalShortcutsPanelProps = {
function TerminalShortcutsPanel (line 28) | function TerminalShortcutsPanel({
FILE: src/components/sidebar/hooks/useSidebarController.ts
type SnippetHighlight (line 23) | type SnippetHighlight = {
type ConversationMatch (line 28) | type ConversationMatch = {
type ConversationSession (line 37) | type ConversationSession = {
type ConversationProjectResult (line 44) | type ConversationProjectResult = {
type ConversationSearchResults (line 50) | type ConversationSearchResults = {
type SearchProgress (line 56) | type SearchProgress = {
type UseSidebarControllerArgs (line 61) | type UseSidebarControllerArgs = {
function useSidebarController (line 78) | function useSidebarController({
FILE: src/components/sidebar/types/types.ts
type ProjectSortOrder (line 3) | type ProjectSortOrder = 'name' | 'date';
type SessionWithProvider (line 5) | type SessionWithProvider = ProjectSession & {
type AdditionalSessionsByProject (line 9) | type AdditionalSessionsByProject = Record<string, ProjectSession[]>;
type LoadingSessionsByProject (line 10) | type LoadingSessionsByProject = Record<string, boolean>;
type DeleteProjectConfirmation (line 12) | type DeleteProjectConfirmation = {
type SessionDeleteConfirmation (line 17) | type SessionDeleteConfirmation = {
type SidebarProps (line 24) | type SidebarProps = {
type SessionViewModel (line 43) | type SessionViewModel = {
type MCPServerStatus (line 53) | type MCPServerStatus = {
type SettingsProject (line 58) | type SettingsProject = Pick<Project, 'name' | 'displayName' | 'fullPath'...
FILE: src/components/sidebar/view/Sidebar.tsx
type TaskMasterSidebarContext (line 16) | type TaskMasterSidebarContext = {
function Sidebar (line 21) | function Sidebar({
FILE: src/components/sidebar/view/subcomponents/SidebarCollapsed.tsx
constant DISCORD_INVITE_URL (line 4) | const DISCORD_INVITE_URL = 'https://discord.gg/buxwujPNRE';
function DiscordIcon (line 6) | function DiscordIcon({ className }: { className?: string }) {
type SidebarCollapsedProps (line 14) | type SidebarCollapsedProps = {
function SidebarCollapsed (line 22) | function SidebarCollapsed({
FILE: src/components/sidebar/view/subcomponents/SidebarContent.tsx
type SearchMode (line 12) | type SearchMode = 'projects' | 'conversations';
function HighlightedSnippet (line 14) | function HighlightedSnippet({ snippet, highlights }: { snippet: string; ...
type SidebarContentProps (line 38) | type SidebarContentProps = {
function SidebarContent (line 65) | function SidebarContent({
FILE: src/components/sidebar/view/subcomponents/SidebarFooter.tsx
constant DISCORD_INVITE_URL (line 5) | const DISCORD_INVITE_URL = 'https://discord.gg/buxwujPNRE';
function DiscordIcon (line 7) | function DiscordIcon({ className }: { className?: string }) {
type SidebarFooterProps (line 15) | type SidebarFooterProps = {
function SidebarFooter (line 24) | function SidebarFooter({
FILE: src/components/sidebar/view/subcomponents/SidebarHeader.tsx
type SearchMode (line 7) | type SearchMode = 'projects' | 'conversations';
type SidebarHeaderProps (line 9) | type SidebarHeaderProps = {
function SidebarHeader (line 26) | function SidebarHeader({
FILE: src/components/sidebar/view/subcomponents/SidebarModals.tsx
type SidebarModalsProps (line 15) | type SidebarModalsProps = {
type TypedSettingsProps (line 38) | type TypedSettingsProps = {
function TypedSettings (line 47) | function TypedSettings(props: TypedSettingsProps) {
function SidebarModals (line 51) | function SidebarModals({
FILE: src/components/sidebar/view/subcomponents/SidebarProjectItem.tsx
type SidebarProjectItemProps (line 11) | type SidebarProjectItemProps = {
function SidebarProjectItem (line 61) | function SidebarProjectItem({
FILE: src/components/sidebar/view/subcomponents/SidebarProjectList.tsx
type SidebarProjectListProps (line 12) | type SidebarProjectListProps = {
function SidebarProjectList (line 56) | function SidebarProjectList({
FILE: src/components/sidebar/view/subcomponents/SidebarProjectSessions.tsx
type SidebarProjectSessionsProps (line 8) | type SidebarProjectSessionsProps = {
function SessionListSkeleton (line 35) | function SessionListSkeleton() {
function SidebarProjectSessions (line 53) | function SidebarProjectSessions({
FILE: src/components/sidebar/view/subcomponents/SidebarProjectsState.tsx
type SidebarProjectsStateProps (line 5) | type SidebarProjectsStateProps = {
function SidebarProjectsState (line 13) | function SidebarProjectsState({
FILE: src/components/sidebar/view/subcomponents/SidebarSessionItem.tsx
type SidebarSessionItemProps (line 11) | type SidebarSessionItemProps = {
function SidebarSessionItem (line 33) | function SidebarSessionItem({
FILE: src/components/sidebar/view/subcomponents/TaskIndicator.tsx
type TaskIndicatorStatus (line 5) | type TaskIndicatorStatus =
type TaskIndicatorSize (line 12) | type TaskIndicatorSize = 'xs' | 'sm' | 'md' | 'lg';
type TaskIndicatorProps (line 14) | type TaskIndicatorProps = {
type IndicatorConfig (line 21) | type IndicatorConfig = {
function TaskIndicator (line 84) | function TaskIndicator({
FILE: src/components/standalone-shell/view/StandaloneShell.tsx
type StandaloneShellProps (line 7) | type StandaloneShellProps = {
function StandaloneShell (line 23) | function StandaloneShell({
FILE: src/components/standalone-shell/view/subcomponents/StandaloneShellEmptyState.tsx
type StandaloneShellEmptyStateProps (line 1) | type StandaloneShellEmptyStateProps = {
function StandaloneShellEmptyState (line 5) | function StandaloneShellEmptyState({ className }: StandaloneShellEmptySt...
FILE: src/components/standalone-shell/view/subcomponents/StandaloneShellHeader.tsx
type StandaloneShellHeaderProps (line 1) | type StandaloneShellHeaderProps = {
function StandaloneShellHeader (line 7) | function StandaloneShellHeader({
FILE: src/components/task-master/context/TaskMasterContext.tsx
function createTaskMasterError (line 18) | function createTaskMasterError(context: string, error: unknown): TaskMas...
function enrichProject (line 27) | function enrichProject(project: TaskMasterProject): TaskMasterProject {
function getNextTask (line 37) | function getNextTask(tasks: TaskMasterTask[]): TaskMasterTask | null {
function isTaskMasterMessage (line 41) | function isTaskMasterMessage(
function useTaskMaster (line 51) | function useTaskMaster() {
function TaskMasterProvider (line 59) | function TaskMasterProvider({ children }: { children: React.ReactNode }) {
FILE: src/components/task-master/hooks/useProjectPrdFiles.ts
type UseProjectPrdFilesOptions (line 5) | type UseProjectPrdFilesOptions = {
type PrdResponse (line 9) | type PrdResponse = {
function normalizePrdResponse (line 14) | function normalizePrdResponse(responseData: PrdResponse): PrdFile[] {
function useProjectPrdFiles (line 26) | function useProjectPrdFiles({ projectName }: UseProjectPrdFilesOptions) {
FILE: src/components/task-master/hooks/useTaskBoardState.ts
type UseTaskBoardStateOptions (line 7) | type UseTaskBoardStateOptions = {
function matchesSearch (line 12) | function matchesSearch(task: TaskMasterTask, searchTerm: string): boolean {
function useTaskBoardState (line 27) | function useTaskBoardState({ tasks, defaultView = 'kanban' }: UseTaskBoa...
FILE: src/components/task-master/types.ts
type TaskId (line 3) | type TaskId = string | number;
type TaskStatus (line 5) | type TaskStatus =
type TaskPriority (line 15) | type TaskPriority = 'high' | 'medium' | 'low' | string;
type TaskMasterTask (line 17) | type TaskMasterTask = {
type TaskReference (line 33) | type TaskReference = {
type TaskSelection (line 39) | type TaskSelection = TaskMasterTask | TaskReference;
type PrdFile (line 41) | type PrdFile = {
type TaskMasterProjectInfo (line 52) | type TaskMasterProjectInfo = {
type TaskMasterProject (line 59) | type TaskMasterProject = Project & {
type TaskMasterProjectInput (line 67) | type TaskMasterProjectInput = TaskMasterProject | Project | null;
type TaskMasterContextError (line 69) | type TaskMasterContextError = {
type TaskMasterMcpStatus (line 75) | type TaskMasterMcpStatus = {
type TaskMasterWebSocketMessage (line 91) | type TaskMasterWebSocketMessage = {
type TaskMasterContextValue (line 97) | type TaskMasterContextValue = {
type TaskBoardView (line 115) | type TaskBoardView = 'kanban' | 'list' | 'grid';
type TaskBoardSortField (line 117) | type TaskBoardSortField = 'id' | 'title' | 'status' | 'priority' | 'upda...
type TaskBoardSortOrder (line 119) | type TaskBoardSortOrder = 'asc' | 'desc';
type TaskKanbanColumn (line 121) | type TaskKanbanColumn = {
FILE: src/components/task-master/utils/taskKanban.ts
constant KANBAN_COLUMN_CONFIG (line 4) | const KANBAN_COLUMN_CONFIG = [
constant CORE_WORKFLOW_STATUSES (line 49) | const CORE_WORKFLOW_STATUSES = new Set(['pending', 'in-progress', 'done']);
function buildKanbanColumns (line 51) | function buildKanbanColumns(tasks: TaskMasterTask[], t: TFunction<'tasks...
FILE: src/components/task-master/utils/taskSorting.ts
constant STATUS_ORDER (line 3) | const STATUS_ORDER: Record<string, number> = {
constant PRIORITY_ORDER (line 13) | const PRIORITY_ORDER: Record<string, number> = {
function toComparableIdParts (line 19) | function toComparableIdParts(taskId: string | number): number[] {
function compareTaskIds (line 26) | function compareTaskIds(leftId: string | number, rightId: string | numbe...
function getSortValue (line 42) | function getSortValue(task: TaskMasterTask, field: TaskBoardSortField): ...
function sortTasks (line 63) | function sortTasks(
function toggleSortOrder (line 90) | function toggleSortOrder(
FILE: src/components/task-master/view/NextTaskBanner.tsx
type NextTaskBannerProps (line 19) | type NextTaskBannerProps = {
function PriorityIndicator (line 25) | function PriorityIndicator({ priority }: { priority?: string }) {
function NextTaskBanner (line 49) | function NextTaskBanner({ onShowAllTasks = null, onStartTask = null, cla...
FILE: src/components/task-master/view/TaskBoard.tsx
type TaskBoardProps (line 14) | type TaskBoardProps = {
function TaskBoard (line 27) | function TaskBoard({
FILE: src/components/task-master/view/TaskBoardContent.tsx
type TaskBoardContentProps (line 7) | type TaskBoardContentProps = {
function KanbanColumns (line 16) | function KanbanColumns({
function TaskBoardContent (line 85) | function TaskBoardContent({
FILE: src/components/task-master/view/TaskBoardToolbar.tsx
type TaskBoardToolbarProps (line 19) | type TaskBoardToolbarProps = {
function TaskBoardToolbar (line 48) | function TaskBoardToolbar({
FILE: src/components/task-master/view/TaskCard.tsx
type TaskCardProps (line 17) | type TaskCardProps = {
type TaskStatusStyle (line 24) | type TaskStatusStyle = {
function getStatusStyle (line 31) | function getStatusStyle(status?: string): TaskStatusStyle {
function renderPriorityIcon (line 85) | function renderPriorityIcon(priority?: string) {
function getSubtaskProgress (line 125) | function getSubtaskProgress(task: TaskMasterTask): { completed: number; ...
function TaskCard (line 134) | function TaskCard({ task, onClick = null, showParent = false, className ...
FILE: src/components/task-master/view/TaskDetailModal.tsx
type TaskDetailModalProps (line 22) | type TaskDetailModalProps = {
constant STATUS_OPTIONS (line 32) | const STATUS_OPTIONS = [
function getStatusIcon (line 41) | function getStatusIcon(status?: string) {
function getPriorityBadgeClass (line 50) | function getPriorityBadgeClass(priority?: string): string {
function TaskDetailModal (line 57) | function TaskDetailModal({
FILE: src/components/task-master/view/TaskEmptyState.tsx
type TaskEmptyStateProps (line 6) | type TaskEmptyStateProps = {
function TaskEmptyState (line 15) | function TaskEmptyState({
FILE: src/components/task-master/view/TaskMasterPanel.tsx
type TaskMasterPanelProps (line 9) | type TaskMasterPanelProps = {
constant PRD_SAVE_MESSAGE (line 13) | const PRD_SAVE_MESSAGE = 'PRD saved successfully!';
function TaskMasterPanel (line 15) | function TaskMasterPanel({ isVisible }: TaskMasterPanelProps) {
FILE: src/components/task-master/view/modals/CreateTaskModal.tsx
type CreateTaskModalProps (line 3) | type CreateTaskModalProps = {
function CreateTaskModal (line 8) | function CreateTaskModal({ isOpen, onClose }: CreateTaskModalProps) {
FILE: src/components/task-master/view/modals/TaskHelpModal.tsx
type TaskHelpModalProps (line 4) | type TaskHelpModalProps = {
type HelpStep (line 10) | type HelpStep = {
function TaskHelpModal (line 17) | function TaskHelpModal({ isOpen, onClose, onCreatePrd }: TaskHelpModalPr...
FILE: src/components/task-master/view/modals/TaskMasterSetupModal.tsx
type TaskMasterSetupModalProps (line 8) | type TaskMasterSetupModalProps = {
function TaskMasterSetupModal (line 15) | function TaskMasterSetupModal({ isOpen, project, onClose, onAfterClose =...
FILE: src/components/task-master/view/shared/TaskFiltersPanel.tsx
type TaskFiltersPanelProps (line 4) | type TaskFiltersPanelProps = {
function TaskFiltersPanel (line 20) | function TaskFiltersPanel({
FILE: src/components/task-master/view/shared/TaskQuickSortBar.tsx
type TaskQuickSortBarProps (line 6) | type TaskQuickSortBarProps = {
function getSortIcon (line 12) | function getSortIcon(field: TaskBoardSortField, currentField: TaskBoardS...
function TaskQuickSortBar (line 20) | function TaskQuickSortBar({ sortField, sortOrder, onSortChange }: TaskQu...
FILE: src/components/version-upgrade/view/VersionUpgradeModal.tsx
type VersionUpgradeModalProps (line 8) | interface VersionUpgradeModalProps {
function VersionUpgradeModal (line 17) | function VersionUpgradeModal({
FILE: src/constants/config.ts
constant IS_PLATFORM (line 5) | const IS_PLATFORM = import.meta.env.VITE_IS_PLATFORM === 'true';
FILE: src/contexts/PluginsContext.tsx
type Plugin (line 5) | type Plugin = {
type PluginsContextValue (line 23) | type PluginsContextValue = {
function usePlugins (line 36) | function usePlugins() {
function PluginsProvider (line 44) | function PluginsProvider({ children }: { children: ReactNode }) {
FILE: src/contexts/WebSocketContext.tsx
type WebSocketContextType (line 5) | type WebSocketContextType = {
FILE: src/hooks/useDeviceSettings.ts
type UseDeviceSettingsOptions (line 3) | type UseDeviceSettingsOptions = {
function useDeviceSettings (line 31) | function useDeviceSettings(options: UseDeviceSettingsOptions = {}) {
FILE: src/hooks/useLocalStorage.jsx
function useLocalStorage (line 10) | function useLocalStorage(key, initialValue) {
FILE: src/hooks/useProjectsState.ts
type UseProjectsStateArgs (line 13) | type UseProjectsStateArgs = {
type FetchProjectsOptions (line 21) | type FetchProjectsOptions = {
constant VALID_TABS (line 111) | const VALID_TABS: Set<string> = new Set(['chat', 'files', 'shell', 'git'...
function useProjectsState (line 129) | function useProjectsState({
FILE: src/hooks/useSessionProtection.ts
function useSessionProtection (line 3) | function useSessionProtection() {
FILE: src/hooks/useUiPreferences.ts
type UiPreferences (line 3) | type UiPreferences = {
type UiPreferenceKey (line 12) | type UiPreferenceKey = keyof UiPreferences;
type SetPreferenceAction (line 14) | type SetPreferenceAction = {
type SetManyPreferencesAction (line 20) | type SetManyPreferencesAction = {
type ResetPreferencesAction (line 25) | type ResetPreferencesAction = {
type UiPreferencesAction (line 30) | type UiPreferencesAction =
constant DEFAULTS (line 35) | const DEFAULTS: UiPreferences = {
constant PREFERENCE_KEYS (line 44) | const PREFERENCE_KEYS = Object.keys(DEFAULTS) as UiPreferenceKey[];
constant VALID_KEYS (line 45) | const VALID_KEYS = new Set<UiPreferenceKey>(PREFERENCE_KEYS);
constant SYNC_EVENT (line 46) | const SYNC_EVENT = 'ui-preferences:sync';
type SyncEventDetail (line 48) | type SyncEventDetail = {
function reducer (line 109) | function reducer(state: UiPreferences, action: UiPreferencesAction): UiP...
function useUiPreferences (line 149) | function useUiPreferences(storageKey = 'uiPreferences') {
FILE: src/hooks/useVersionCheck.ts
type InstallMode (line 24) | type InstallMode = 'git' | 'npm';
FILE: src/hooks/useWebPush.ts
type WebPushState (line 4) | type WebPushState = {
function urlBase64ToUint8Array (line 12) | function urlBase64ToUint8Array(base64String: string): Uint8Array {
function useWebPush (line 23) | function useWebPush(): WebPushState {
FILE: src/lib/utils.js
function cn (line 4) | function cn(...inputs) {
function safeJsonParse (line 8) | function safeJsonParse(value) {
FILE: src/shared/view/ui/Badge.tsx
type BadgeProps (line 23) | type BadgeProps = React.HTMLAttributes<HTMLDivElement> & VariantProps<ty...
function Badge (line 25) | function Badge({ className, variant, ...props }: BadgeProps) {
FILE: src/shared/view/ui/Button.tsx
type ButtonProps (line 34) | type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> &
FILE: src/shared/view/ui/DarkModeToggle.tsx
type DarkModeToggleProps (line 5) | type DarkModeToggleProps = {
function DarkModeToggle (line 11) | function DarkModeToggle({
FILE: src/shared/view/ui/Input.tsx
type InputProps (line 4) | type InputProps = React.InputHTMLAttributes<HTMLInputElement>;
FILE: src/shared/view/ui/LanguageSelector.tsx
type LanguageSelectorProps (line 7) | type LanguageSelectorProps = {
function LanguageSelector (line 20) | function LanguageSelector({ compact = false }: LanguageSelectorProps) {
FILE: src/shared/view/ui/PillBar.tsx
type PillBarProps (line 5) | type PillBarProps = {
function PillBar (line 10) | function PillBar({ children, className }: PillBarProps) {
type PillProps (line 19) | type PillProps = {
function Pill (line 26) | function Pill({ isActive, onClick, children, className }: PillProps) {
FILE: src/shared/view/ui/ScrollArea.tsx
type ScrollAreaProps (line 4) | type ScrollAreaProps = React.HTMLAttributes<HTMLDivElement>;
FILE: src/shared/view/ui/Tooltip.tsx
type TooltipPosition (line 4) | type TooltipPosition = 'top' | 'bottom' | 'left' | 'right';
type TooltipProps (line 6) | type TooltipProps = {
function getPositionClasses (line 14) | function getPositionClasses(position: TooltipPosition): string {
function getArrowClasses (line 29) | function getArrowClasses(position: TooltipPosition): string {
function Tooltip (line 44) | function Tooltip({
FILE: src/stores/useSessionStore.ts
type MessageKind (line 16) | type MessageKind =
type NormalizedMessage (line 32) | interface NormalizedMessage {
type SessionStatus (line 70) | type SessionStatus = 'idle' | 'loading' | 'streaming' | 'error';
type SessionSlot (line 72) | interface SessionSlot {
constant EMPTY (line 87) | const EMPTY: NormalizedMessage[] = [];
function createEmptySlot (line 89) | function createEmptySlot(): SessionSlot {
function computeMerged (line 110) | function computeMerged(server: NormalizedMessage[], realtime: Normalized...
function recomputeMergedIfNeeded (line 123) | function recomputeMergedIfNeeded(slot: SessionSlot): boolean {
constant STALE_THRESHOLD_MS (line 135) | const STALE_THRESHOLD_MS = 30_000;
constant MAX_REALTIME_MESSAGES (line 137) | const MAX_REALTIME_MESSAGES = 500;
function useSessionStore (line 141) | function useSessionStore() {
type SessionStore (line 455) | type SessionStore = ReturnType<typeof useSessionStore>;
FILE: src/types/app.ts
type SessionProvider (line 1) | type SessionProvider = 'claude' | 'cursor' | 'codex' | 'gemini';
type AppTab (line 3) | type AppTab = 'chat' | 'files' | 'shell' | 'git' | 'tasks' | 'preview' |...
type ProjectSession (line 5) | interface ProjectSession {
type ProjectSessionMeta (line 20) | interface ProjectSessionMeta {
type ProjectTaskmasterInfo (line 26) | interface ProjectTaskmasterInfo {
type Project (line 33) | interface Project {
type LoadingProgress (line 47) | interface LoadingProgress {
type ProjectsUpdatedMessage (line 56) | interface ProjectsUpdatedMessage {
type LoadingProgressMessage (line 63) | interface LoadingProgressMessage extends LoadingProgress {
type AppSocketMessage (line 67) | type AppSocketMessage =
FILE: src/types/global.d.ts
type Window (line 4) | interface Window {
type EventSourceEventMap (line 10) | interface EventSourceEventMap {
FILE: src/types/sharedTypes.ts
type ReleaseInfo (line 1) | type ReleaseInfo = {
FILE: src/utils/clipboard.ts
function fallbackCopyToClipboard (line 1) | function fallbackCopyToClipboard(text: string): boolean {
function copyTextToClipboard (line 29) | async function copyTextToClipboard(text: string): Promise<boolean> {
Condensed preview — 448 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,680K chars).
[
{
"path": ".github/ISSUE_TEMPLATE/bug_report.md",
"chars": 917,
"preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\ntype: Bug\n\n---\n\n**Desc"
},
{
"path": ".github/ISSUE_TEMPLATE/feature_request.md",
"chars": 482,
"preview": "---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"[Feature]\"\nlabels: ''\nassignees: ''\ntype: Feat"
},
{
"path": ".github/workflows/discord-release.yml",
"chars": 663,
"preview": "name: Discord Release Notification\n\non:\n release:\n types: [published]\n\njobs:\n github-releases-to-discord:\n runs-"
},
{
"path": ".gitignore",
"chars": 1591,
"preview": "# Dependencies\nnode_modules/\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\n# Build ou"
},
{
"path": ".gitmodules",
"chars": 120,
"preview": "[submodule \"plugins/starter\"]\n\tpath = plugins/starter\n\turl = https://github.com/cloudcli-ai/cloudcli-plugin-starter.git\n"
},
{
"path": ".husky/commit-msg",
"chars": 25,
"preview": "npx commitlint --edit $1\n"
},
{
"path": ".husky/pre-commit",
"chars": 16,
"preview": "npx lint-staged\n"
},
{
"path": ".npmignore",
"chars": 556,
"preview": "\n*.md\n!README.md\n.env*\n.gitignore\n.nvmrc\n.release-it.json\nrelease.sh\npostcss.config.js\nvite.config.js\ntailwind.config.js"
},
{
"path": ".nvmrc",
"chars": 3,
"preview": "v22"
},
{
"path": ".release-it.json",
"chars": 1243,
"preview": "{\n \"git\": {\n \"commitMessage\": \"chore(release): v${version}\",\n \"tagName\": \"v${version}\",\n \"requireBranch\": \"mai"
},
{
"path": "CHANGELOG.md",
"chars": 13780,
"preview": "# Changelog\n\nAll notable changes to CloudCLI UI will be documented in this file.\n\n\n## [1.26.0](https://github.com/sitebo"
},
{
"path": "CONTRIBUTING.md",
"chars": 4907,
"preview": "# Contributing to CloudCLI UI\n\nThanks for your interest in contributing to CloudCLI UI! Before you start, please take a "
},
{
"path": "LICENSE",
"chars": 34892,
"preview": "# GNU GENERAL PUBLIC LICENSE\n\nVersion 3, 29 June 2007\n\nCopyright (C) 2007 Free Software Foundation, Inc.\n<https://fsf.or"
},
{
"path": "README.de.md",
"chars": 12136,
"preview": "<div align=\"center\">\n <img src=\"public/logo.svg\" alt=\"CloudCLI UI\" width=\"64\" height=\"64\">\n <h1>Cloud CLI (auch bekann"
},
{
"path": "README.ja.md",
"chars": 8108,
"preview": "<div align=\"center\">\n <img src=\"public/logo.svg\" alt=\"CloudCLI UI\" width=\"64\" height=\"64\">\n <h1>Cloud CLI(別名 Claude Co"
},
{
"path": "README.ko.md",
"chars": 7745,
"preview": "<div align=\"center\">\n <img src=\"public/logo.svg\" alt=\"CloudCLI UI\" width=\"64\" height=\"64\">\n <h1>Cloud CLI (일명 Claude C"
},
{
"path": "README.md",
"chars": 11438,
"preview": "<div align=\"center\">\n <img src=\"public/logo.svg\" alt=\"CloudCLI UI\" width=\"64\" height=\"64\">\n <h1>Cloud CLI (aka Claude "
},
{
"path": "README.ru.md",
"chars": 12006,
"preview": "<div align=\"center\">\n <img src=\"public/logo.svg\" alt=\"CloudCLI UI\" width=\"64\" height=\"64\">\n <h1>Cloud CLI (aka Claude "
},
{
"path": "README.zh-CN.md",
"chars": 6857,
"preview": "<div align=\"center\">\n <img src=\"public/logo.svg\" alt=\"CloudCLI UI\" width=\"64\" height=\"64\">\n <h1>Cloud CLI(又名 Claude Co"
},
{
"path": "commitlint.config.js",
"chars": 68,
"preview": "export default {\n extends: [\"@commitlint/config-conventional\"],\n};\n"
},
{
"path": "eslint.config.js",
"chars": 3180,
"preview": "import js from \"@eslint/js\";\nimport tseslint from \"typescript-eslint\";\nimport react from \"eslint-plugin-react\";\nimport r"
},
{
"path": "index.html",
"chars": 1785,
"preview": "<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
},
{
"path": "package.json",
"chars": 4207,
"preview": "{\n \"name\": \"@siteboon/claude-code-ui\",\n \"version\": \"1.26.0\",\n \"description\": \"A web-based UI for Claude Code CLI\",\n "
},
{
"path": "postcss.config.js",
"chars": 79,
"preview": "export default {\n plugins: {\n tailwindcss: {},\n autoprefixer: {},\n },\n}"
},
{
"path": "public/api-docs.html",
"chars": 32146,
"preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width"
},
{
"path": "public/clear-cache.html",
"chars": 2919,
"preview": "<!DOCTYPE html>\n<html>\n<head>\n <title>Clear Cache - Claude Code UI</title>\n <style>\n body {\n fon"
},
{
"path": "public/convert-icons.md",
"chars": 1642,
"preview": "# Convert SVG Icons to PNG\n\nI've created SVG versions of the app icons that match the MessageSquare design from the side"
},
{
"path": "public/generate-icons.js",
"chars": 2073,
"preview": "const fs = require('fs');\nconst path = require('path');\n\n// Icon sizes needed\nconst sizes = [72, 96, 128, 144, 152, 192,"
},
{
"path": "public/manifest.json",
"chars": 1380,
"preview": "{\n \"name\": \"CloudCLI UI\",\n \"short_name\": \"CloudCLI UI\",\n \"description\": \"CloudCLI UI web application\",\n \"start_url\":"
},
{
"path": "public/sw.js",
"chars": 2542,
"preview": "// Service Worker for Claude Code UI PWA\nconst CACHE_NAME = 'claude-ui-v1';\nconst urlsToCache = [\n '/',\n '/index.html'"
},
{
"path": "release.sh",
"chars": 136,
"preview": "#!/bin/bash\n# Load environment variables from .env\nexport $(grep -v '^#' .env | grep '^GITHUB_TOKEN=' | xargs)\nexec npx "
},
{
"path": "scripts/fix-node-pty.js",
"chars": 2060,
"preview": "#!/usr/bin/env node\n/**\n * Fix node-pty spawn-helper permissions on macOS\n *\n * This script fixes a known issue with nod"
},
{
"path": "server/claude-sdk.js",
"chars": 26246,
"preview": "/**\n * Claude SDK Integration\n *\n * This module provides SDK-based integration with Claude using the @anthropic-ai/claud"
},
{
"path": "server/cli.js",
"chars": 11815,
"preview": "#!/usr/bin/env node\n/**\n * Claude Code UI CLI\n *\n * Provides command-line utilities for managing Claude Code UI\n *\n * Co"
},
{
"path": "server/constants/config.js",
"chars": 195,
"preview": "/**\n * Environment Flag: Is Platform\n * Indicates if the app is running in Platform mode (hosted) or OSS mode (self-host"
},
{
"path": "server/cursor-cli.js",
"chars": 11693,
"preview": "import { spawn } from 'child_process';\nimport crossSpawn from 'cross-spawn';\nimport { notifyRunFailed, notifyRunStopped "
},
{
"path": "server/database/db.js",
"chars": 19519,
"preview": "import Database from 'better-sqlite3';\nimport path from 'path';\nimport fs from 'fs';\nimport crypto from 'crypto';\nimport"
},
{
"path": "server/database/init.sql",
"chars": 3639,
"preview": "-- Initialize authentication database\nPRAGMA foreign_keys = ON;\n\n-- Users table (single user system)\nCREATE TABLE IF NOT"
},
{
"path": "server/gemini-cli.js",
"chars": 18631,
"preview": "import { spawn } from 'child_process';\nimport crossSpawn from 'cross-spawn';\n\n// Use cross-spawn on Windows for correct "
},
{
"path": "server/gemini-response-handler.js",
"chars": 2095,
"preview": "// Gemini Response Handler - JSON Stream processing\nimport { geminiAdapter } from './providers/gemini/adapter.js';\n\nclas"
},
{
"path": "server/index.js",
"chars": 103895,
"preview": "#!/usr/bin/env node\n// Load environment variables before other imports execute\nimport './load-env.js';\nimport fs from 'f"
},
{
"path": "server/load-env.js",
"chars": 938,
"preview": "// Load environment variables from .env before other imports execute.\nimport fs from 'fs';\nimport os from 'os';\nimport p"
},
{
"path": "server/middleware/auth.js",
"chars": 3638,
"preview": "import jwt from 'jsonwebtoken';\nimport { userDb, appConfigDb } from '../database/db.js';\nimport { IS_PLATFORM } from '.."
},
{
"path": "server/openai-codex.js",
"chars": 11427,
"preview": "/**\n * OpenAI Codex SDK Integration\n * =============================\n *\n * This module provides integration with the Ope"
},
{
"path": "server/projects.js",
"chars": 85406,
"preview": "/**\n * PROJECT DISCOVERY AND MANAGEMENT SYSTEM\n * ========================================\n * \n * This module manages pr"
},
{
"path": "server/providers/claude/adapter.js",
"chars": 8826,
"preview": "/**\n * Claude provider adapter.\n *\n * Normalizes Claude SDK session history into NormalizedMessage format.\n * @module ad"
},
{
"path": "server/providers/codex/adapter.js",
"chars": 7985,
"preview": "/**\n * Codex (OpenAI) provider adapter.\n *\n * Normalizes Codex SDK session history into NormalizedMessage format.\n * @mo"
},
{
"path": "server/providers/cursor/adapter.js",
"chars": 11970,
"preview": "/**\n * Cursor provider adapter.\n *\n * Normalizes Cursor CLI session history into NormalizedMessage format.\n * @module ad"
},
{
"path": "server/providers/gemini/adapter.js",
"chars": 6290,
"preview": "/**\n * Gemini provider adapter.\n *\n * Normalizes Gemini CLI session history into NormalizedMessage format.\n * @module ad"
},
{
"path": "server/providers/registry.js",
"chars": 1235,
"preview": "/**\n * Provider Registry\n *\n * Centralizes provider adapter lookup. All code that needs a provider adapter\n * should go "
},
{
"path": "server/providers/types.js",
"chars": 4583,
"preview": "/**\n * Provider Types & Interface\n *\n * Defines the normalized message format and the provider adapter interface.\n * All"
},
{
"path": "server/providers/utils.js",
"chars": 716,
"preview": "/**\n * Shared provider utilities.\n *\n * @module providers/utils\n */\n\n/**\n * Prefixes that indicate internal/system conte"
},
{
"path": "server/routes/agent.js",
"chars": 44914,
"preview": "import express from 'express';\nimport { spawn } from 'child_process';\nimport path from 'path';\nimport os from 'os';\nimpo"
},
{
"path": "server/routes/auth.js",
"chars": 3972,
"preview": "import express from 'express';\nimport bcrypt from 'bcrypt';\nimport { userDb, db } from '../database/db.js';\nimport { gen"
},
{
"path": "server/routes/cli-auth.js",
"chars": 11889,
"preview": "import express from 'express';\nimport { spawn } from 'child_process';\nimport fs from 'fs/promises';\nimport path from 'pa"
},
{
"path": "server/routes/codex.js",
"chars": 9872,
"preview": "import express from 'express';\nimport { spawn } from 'child_process';\nimport { promises as fs } from 'fs';\nimport path f"
},
{
"path": "server/routes/commands.js",
"chars": 17081,
"preview": "import express from 'express';\nimport { promises as fs } from 'fs';\nimport path from 'path';\nimport { fileURLToPath } fr"
},
{
"path": "server/routes/cursor.js",
"chars": 25047,
"preview": "import express from 'express';\nimport { promises as fs } from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport"
},
{
"path": "server/routes/gemini.js",
"chars": 859,
"preview": "import express from 'express';\nimport sessionManager from '../sessionManager.js';\nimport { sessionNamesDb } from '../dat"
},
{
"path": "server/routes/git.js",
"chars": 50453,
"preview": "import express from 'express';\nimport { spawn } from 'child_process';\nimport path from 'path';\nimport { promises as fs }"
},
{
"path": "server/routes/mcp-utils.js",
"chars": 1296,
"preview": "/**\n * MCP UTILITIES API ROUTES\n * ========================\n * \n * API endpoints for MCP server detection and configurat"
},
{
"path": "server/routes/mcp.js",
"chars": 18183,
"preview": "import express from 'express';\nimport { promises as fs } from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport"
},
{
"path": "server/routes/messages.js",
"chars": 1981,
"preview": "/**\n * Unified messages endpoint.\n *\n * GET /api/sessions/:sessionId/messages?provider=claude&projectName=foo&limit=50&o"
},
{
"path": "server/routes/plugins.js",
"chars": 9734,
"preview": "import express from 'express';\nimport path from 'path';\nimport http from 'http';\nimport mime from 'mime-types';\nimport f"
},
{
"path": "server/routes/projects.js",
"chars": 17151,
"preview": "import express from 'express';\nimport { promises as fs } from 'fs';\nimport path from 'path';\nimport { spawn } from 'chil"
},
{
"path": "server/routes/settings.js",
"chars": 8786,
"preview": "import express from 'express';\nimport { apiKeysDb, credentialsDb, notificationPreferencesDb, pushSubscriptionsDb } from "
},
{
"path": "server/routes/taskmaster.js",
"chars": 63993,
"preview": "/**\n * TASKMASTER API ROUTES\n * ====================\n * \n * This module provides API endpoints for TaskMaster integratio"
},
{
"path": "server/routes/user.js",
"chars": 4016,
"preview": "import express from 'express';\nimport { userDb } from '../database/db.js';\nimport { authenticateToken } from '../middlew"
},
{
"path": "server/services/notification-orchestrator.js",
"chars": 5864,
"preview": "import webPush from 'web-push';\nimport { notificationPreferencesDb, pushSubscriptionsDb, sessionNamesDb } from '../datab"
},
{
"path": "server/services/vapid-keys.js",
"chars": 954,
"preview": "import webPush from 'web-push';\nimport { db } from '../database/db.js';\n\nlet cachedKeys = null;\n\nfunction ensureVapidKey"
},
{
"path": "server/sessionManager.js",
"chars": 5972,
"preview": "import { promises as fs } from 'fs';\nimport path from 'path';\nimport os from 'os';\n\nclass SessionManager {\n constructor"
},
{
"path": "server/utils/commandParser.js",
"chars": 8838,
"preview": "import { promises as fs } from 'fs';\nimport path from 'path';\nimport { execFile } from 'child_process';\nimport { promisi"
},
{
"path": "server/utils/frontmatter.js",
"chars": 486,
"preview": "import matter from 'gray-matter';\n\nconst disabledFrontmatterEngine = () => ({});\n\nconst frontmatterOptions = {\n languag"
},
{
"path": "server/utils/gitConfig.js",
"chars": 1128,
"preview": "import { spawn } from 'child_process';\n\nfunction spawnAsync(command, args) {\n return new Promise((resolve, reject) => {"
},
{
"path": "server/utils/mcp-detector.js",
"chars": 7084,
"preview": "/**\n * MCP SERVER DETECTION UTILITY\n * ============================\n * \n * Centralized utility for detecting MCP server "
},
{
"path": "server/utils/plugin-loader.js",
"chars": 14887,
"preview": "import fs from 'fs';\nimport path from 'path';\nimport os from 'os';\nimport { spawn } from 'child_process';\n\nconst PLUGINS"
},
{
"path": "server/utils/plugin-process-manager.js",
"chars": 4993,
"preview": "import { spawn } from 'child_process';\nimport path from 'path';\nimport { scanPlugins, getPluginsConfig, getPluginDir } f"
},
{
"path": "server/utils/taskmaster-websocket.js",
"chars": 3856,
"preview": "/**\n * TASKMASTER WEBSOCKET UTILITIES\n * ==============================\n * \n * Utilities for broadcasting TaskMaster sta"
},
{
"path": "shared/modelConstants.js",
"chars": 3060,
"preview": "/**\n * Centralized Model Definitions\n * Single source of truth for all supported AI models\n */\n\n/**\n * Claude (Anthropic"
},
{
"path": "shared/networkHosts.js",
"chars": 613,
"preview": "export function isWildcardHost(host) {\n return host === '0.0.0.0' || host === '::';\n}\n\nexport function isLoopbackHost(h"
},
{
"path": "src/App.tsx",
"chars": 1453,
"preview": "import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';\nimport { I18nextProvider } from 'react-i18nex"
},
{
"path": "src/components/app/AppContent.tsx",
"chars": 6397,
"preview": "import { useEffect, useRef } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport { useTransl"
},
{
"path": "src/components/app/MobileNav.tsx",
"chars": 7223,
"preview": "import { useState, useRef, useEffect, type Dispatch, type SetStateAction } from 'react';\nimport { useTranslation } from "
},
{
"path": "src/components/auth/constants.ts",
"chars": 296,
"preview": "export const AUTH_TOKEN_STORAGE_KEY = 'auth-token';\n\nexport const AUTH_ERROR_MESSAGES = {\n authStatusCheckFailed: 'Fail"
},
{
"path": "src/components/auth/context/AuthContext.tsx",
"chars": 6574,
"preview": "import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';\nimport { IS_PLATFORM } fro"
},
{
"path": "src/components/auth/index.ts",
"chars": 130,
"preview": "export { AuthProvider, useAuth } from './context/AuthContext';\nexport { default as ProtectedRoute } from './view/Protect"
},
{
"path": "src/components/auth/types.ts",
"chars": 1087,
"preview": "import type { ReactNode } from 'react';\n\nexport type AuthUser = {\n id?: number | string;\n username: string;\n [key: st"
},
{
"path": "src/components/auth/utils.ts",
"chars": 417,
"preview": "import type { ApiErrorPayload } from './types';\n\nexport async function parseJsonSafely<T>(response: Response): Promise<T"
},
{
"path": "src/components/auth/view/AuthErrorAlert.tsx",
"chars": 399,
"preview": "type AuthErrorAlertProps = {\n errorMessage: string;\n};\n\nexport default function AuthErrorAlert({ errorMessage }: AuthEr"
},
{
"path": "src/components/auth/view/AuthInputField.tsx",
"chars": 1273,
"preview": "type AuthInputFieldProps = {\n id: string;\n label: string;\n value: string;\n onChange: (nextValue: string) => void;\n "
},
{
"path": "src/components/auth/view/AuthLoadingScreen.tsx",
"chars": 1060,
"preview": "import { MessageSquare } from 'lucide-react';\n\nconst loadingDotAnimationDelays = ['0s', '0.1s', '0.2s'];\n\nexport default"
},
{
"path": "src/components/auth/view/AuthScreenLayout.tsx",
"chars": 1316,
"preview": "import type { ReactNode } from 'react';\nimport { MessageSquare } from 'lucide-react';\n\ntype AuthScreenLayoutProps = {\n "
},
{
"path": "src/components/auth/view/LoginForm.tsx",
"chars": 3129,
"preview": "import { useCallback, useState } from 'react';\nimport type { FormEvent } from 'react';\nimport { useTranslation } from 'r"
},
{
"path": "src/components/auth/view/ProtectedRoute.tsx",
"chars": 1016,
"preview": "import type { ReactNode } from 'react';\nimport { IS_PLATFORM } from '../../../constants/config';\nimport { useAuth } from"
},
{
"path": "src/components/auth/view/SetupForm.tsx",
"chars": 4225,
"preview": "import { useCallback, useState } from 'react';\nimport type { FormEvent } from 'react';\nimport { useAuth } from '../conte"
},
{
"path": "src/components/chat/constants/thinkingModes.ts",
"chars": 921,
"preview": "import { Brain, Zap, Sparkles, Atom } from 'lucide-react';\n\nexport const thinkingModes = [\n {\n id: 'none',\n name:"
},
{
"path": "src/components/chat/hooks/useChatComposerState.ts",
"chars": 30527,
"preview": "import { useCallback, useEffect, useRef, useState } from 'react';\nimport type {\n ChangeEvent,\n ClipboardEvent,\n Dispa"
},
{
"path": "src/components/chat/hooks/useChatMessages.ts",
"chars": 5911,
"preview": "/**\n * Message normalization utilities.\n * Converts NormalizedMessage[] from the session store into ChatMessage[] for th"
},
{
"path": "src/components/chat/hooks/useChatProviderState.ts",
"chars": 3943,
"preview": "import { useCallback, useEffect, useRef, useState } from 'react';\nimport { authenticatedFetch } from '../../../utils/api"
},
{
"path": "src/components/chat/hooks/useChatRealtimeHandlers.ts",
"chars": 12110,
"preview": "import { useEffect, useRef } from 'react';\nimport type { Dispatch, MutableRefObject, SetStateAction } from 'react';\nimpo"
},
{
"path": "src/components/chat/hooks/useChatSessionState.ts",
"chars": 28064,
"preview": "import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';\nimport type { MutableRefObje"
},
{
"path": "src/components/chat/hooks/useFileMentions.tsx",
"chars": 7898,
"preview": "import { useCallback, useEffect, useMemo, useState } from 'react';\nimport type { Dispatch, KeyboardEvent, RefObject, Set"
},
{
"path": "src/components/chat/hooks/useSlashCommands.ts",
"chars": 10788,
"preview": "import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport type { Dispatch, KeyboardEvent, RefObj"
},
{
"path": "src/components/chat/tools/README.md",
"chars": 8013,
"preview": "# Tool Rendering System\n\n## Overview\n\nConfig-driven architecture for rendering tool executions in chat. All tool display"
},
{
"path": "src/components/chat/tools/ToolRenderer.tsx",
"chars": 7299,
"preview": "import React, { memo, useMemo, useCallback } from 'react';\nimport type { Project } from '../../../types/app';\nimport typ"
},
{
"path": "src/components/chat/tools/components/CollapsibleDisplay.tsx",
"chars": 2697,
"preview": "import React from 'react';\nimport { CollapsibleSection } from './CollapsibleSection';\n\ninterface CollapsibleDisplayProps"
},
{
"path": "src/components/chat/tools/components/CollapsibleSection.tsx",
"chars": 2120,
"preview": "import React from 'react';\n\ninterface CollapsibleSectionProps {\n title: string;\n toolName?: string;\n open?: boolean;\n"
},
{
"path": "src/components/chat/tools/components/ContentRenderers/FileListContent.tsx",
"chars": 1620,
"preview": "import React from 'react';\n\ninterface FileListItem {\n path: string;\n onClick?: () => void;\n}\n\ninterface FileListConten"
},
{
"path": "src/components/chat/tools/components/ContentRenderers/MarkdownContent.tsx",
"chars": 516,
"preview": "import React from 'react';\nimport { Markdown } from '../../../view/subcomponents/Markdown';\n\ninterface MarkdownContentPr"
},
{
"path": "src/components/chat/tools/components/ContentRenderers/QuestionAnswerContent.tsx",
"chars": 8711,
"preview": "import React, { useState } from 'react';\nimport type { Question } from '../../../types/types';\n\ninterface QuestionAnswer"
},
{
"path": "src/components/chat/tools/components/ContentRenderers/TaskListContent.tsx",
"chars": 4471,
"preview": "import React from 'react';\n\ninterface TaskItem {\n id: string;\n subject: string;\n status: 'pending' | 'in_progress' | "
},
{
"path": "src/components/chat/tools/components/ContentRenderers/TextContent.tsx",
"chars": 1348,
"preview": "import React from 'react';\n\ninterface TextContentProps {\n content: string;\n format?: 'plain' | 'json' | 'code';\n clas"
},
{
"path": "src/components/chat/tools/components/ContentRenderers/TodoList.tsx",
"chars": 4802,
"preview": "import { memo, useMemo } from 'react';\nimport { CheckCircle2, Circle, Clock, type LucideIcon } from 'lucide-react';\nimpo"
},
{
"path": "src/components/chat/tools/components/ContentRenderers/TodoListContent.tsx",
"chars": 937,
"preview": "import { memo, useMemo } from 'react';\nimport TodoList, { type TodoItem } from './TodoList';\n\nconst isTodoItem = (value:"
},
{
"path": "src/components/chat/tools/components/ContentRenderers/index.ts",
"chars": 322,
"preview": "export { MarkdownContent } from './MarkdownContent';\nexport { FileListContent } from './FileListContent';\nexport { TodoL"
},
{
"path": "src/components/chat/tools/components/InteractiveRenderers/AskUserQuestionPanel.tsx",
"chars": 17437,
"preview": "import React, { useState, useCallback, useRef, useEffect } from 'react';\nimport type { PermissionPanelProps } from '../."
},
{
"path": "src/components/chat/tools/components/InteractiveRenderers/index.ts",
"chars": 63,
"preview": "export { AskUserQuestionPanel } from './AskUserQuestionPanel';\n"
},
{
"path": "src/components/chat/tools/components/OneLineDisplay.tsx",
"chars": 6767,
"preview": "import React, { useState } from 'react';\nimport { copyTextToClipboard } from '../../../../utils/clipboard';\n\ntype Action"
},
{
"path": "src/components/chat/tools/components/SubagentContainer.tsx",
"chars": 7313,
"preview": "import React from 'react';\nimport type { SubagentChildTool } from '../../types/types';\nimport { CollapsibleSection } fro"
},
{
"path": "src/components/chat/tools/components/ToolDiffViewer.tsx",
"chars": 2829,
"preview": "import React, { useMemo } from 'react';\n\ntype DiffLine = {\n type: string;\n content: string;\n lineNum: number;\n};\n\nint"
},
{
"path": "src/components/chat/tools/components/index.ts",
"chars": 353,
"preview": "export { CollapsibleSection } from './CollapsibleSection';\nexport { ToolDiffViewer } from './ToolDiffViewer';\nexport { O"
},
{
"path": "src/components/chat/tools/configs/permissionPanelRegistry.ts",
"chars": 709,
"preview": "import type { ComponentType } from 'react';\nimport type { PendingPermissionRequest } from '../../types/types';\n\nexport i"
},
{
"path": "src/components/chat/tools/configs/toolConfigs.ts",
"chars": 17197,
"preview": "/**\n * Centralized tool configuration registry\n * Defines display behavior for all tool types \n */\n\nexport interface Too"
},
{
"path": "src/components/chat/tools/index.ts",
"chars": 154,
"preview": "export { ToolRenderer } from './ToolRenderer';\nexport { getToolConfig, shouldHideToolResult } from './configs/toolConfig"
},
{
"path": "src/components/chat/types/types.ts",
"chars": 2982,
"preview": "import type { Project, ProjectSession, SessionProvider } from '../../../types/app';\n\nexport type Provider = SessionProvi"
},
{
"path": "src/components/chat/utils/chatFormatting.ts",
"chars": 2880,
"preview": "export function decodeHtmlEntities(text: string) {\n if (!text) return text;\n return text\n .replace(/</g, '<')\n "
},
{
"path": "src/components/chat/utils/chatPermissions.ts",
"chars": 2328,
"preview": "import { safeJsonParse } from '../../../lib/utils.js';\nimport type { ChatMessage, ClaudePermissionSuggestion, Permission"
},
{
"path": "src/components/chat/utils/chatStorage.ts",
"chars": 2049,
"preview": "import type { ClaudeSettings } from '../types/types';\n\nexport const CLAUDE_SETTINGS_KEY = 'claude-settings';\n\nexport con"
},
{
"path": "src/components/chat/utils/messageKeys.ts",
"chars": 1102,
"preview": "import type { ChatMessage } from '../types/types';\n\nconst toMessageKeyPart = (value: unknown): string | null => {\n if ("
},
{
"path": "src/components/chat/utils/messageTransforms.ts",
"chars": 2560,
"preview": "export interface DiffLine {\n type: 'added' | 'removed';\n content: string;\n lineNum: number;\n}\n\nexport type DiffCalcul"
},
{
"path": "src/components/chat/view/ChatInterface.tsx",
"chars": 12862,
"preview": "import React, { useCallback, useEffect, useRef } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { "
},
{
"path": "src/components/chat/view/subcomponents/AssistantThinkingIndicator.tsx",
"chars": 1516,
"preview": "import { SessionProvider } from '../../../../types/app';\nimport SessionProviderLogo from '../../../llm-logo-provider/Ses"
},
{
"path": "src/components/chat/view/subcomponents/ChatComposer.tsx",
"chars": 13905,
"preview": "import { useTranslation } from 'react-i18next';\nimport type {\n ChangeEvent,\n ClipboardEvent,\n Dispatch,\n FormEvent,\n"
},
{
"path": "src/components/chat/view/subcomponents/ChatInputControls.tsx",
"chars": 5600,
"preview": "import React from 'react';\nimport { useTranslation } from 'react-i18next';\nimport type { PermissionMode, Provider } from"
},
{
"path": "src/components/chat/view/subcomponents/ChatMessagesPane.tsx",
"chars": 10591,
"preview": "import { useTranslation } from 'react-i18next';\nimport { useCallback, useRef } from 'react';\nimport type { Dispatch, Ref"
},
{
"path": "src/components/chat/view/subcomponents/ClaudeStatus.tsx",
"chars": 7923,
"preview": "import { useEffect, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { cn } from '../../."
},
{
"path": "src/components/chat/view/subcomponents/CommandMenu.tsx",
"chars": 8142,
"preview": "import { useEffect, useRef } from 'react';\nimport type { CSSProperties } from 'react';\n\ntype CommandMenuCommand = {\n na"
},
{
"path": "src/components/chat/view/subcomponents/ImageAttachment.tsx",
"chars": 1775,
"preview": "import { useEffect, useState } from 'react';\n\ninterface ImageAttachmentProps {\n file: File;\n onRemove: () => void;\n u"
},
{
"path": "src/components/chat/view/subcomponents/Markdown.tsx",
"chars": 5959,
"preview": "import React, { useMemo, useState } from 'react';\nimport ReactMarkdown from 'react-markdown';\nimport remarkGfm from 'rem"
},
{
"path": "src/components/chat/view/subcomponents/MessageComponent.tsx",
"chars": 24168,
"preview": "import { memo, useEffect, useMemo, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimpo"
},
{
"path": "src/components/chat/view/subcomponents/MessageCopyControl.tsx",
"chars": 7808,
"preview": "import { useEffect, useMemo, useRef, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport { c"
},
{
"path": "src/components/chat/view/subcomponents/PermissionRequestsBanner.tsx",
"chars": 5664,
"preview": "import React from 'react';\nimport type { PendingPermissionRequest } from '../../types/types';\nimport { buildClaudeToolPe"
},
{
"path": "src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx",
"chars": 9821,
"preview": "import React from \"react\";\nimport { Check, ChevronDown } from \"lucide-react\";\nimport { useTranslation } from \"react-i18n"
},
{
"path": "src/components/chat/view/subcomponents/ThinkingModeSelector.tsx",
"chars": 5861,
"preview": "import { useState, useRef, useEffect } from 'react';\nimport { Brain, X } from 'lucide-react';\nimport { useTranslation } "
},
{
"path": "src/components/chat/view/subcomponents/TokenUsagePie.tsx",
"chars": 1633,
"preview": "type TokenUsagePieProps = {\n used: number;\n total: number;\n};\n\nexport default function TokenUsagePie({ used, total }: "
},
{
"path": "src/components/code-editor/constants/settings.ts",
"chars": 467,
"preview": "export const CODE_EDITOR_STORAGE_KEYS = {\n theme: 'codeEditorTheme',\n wordWrap: 'codeEditorWordWrap',\n showMinimap: '"
},
{
"path": "src/components/code-editor/hooks/useCodeEditorDocument.ts",
"chars": 4155,
"preview": "import { useCallback, useEffect, useState } from 'react';\nimport { api } from '../../../utils/api';\nimport type { CodeEd"
},
{
"path": "src/components/code-editor/hooks/useCodeEditorSettings.ts",
"chars": 2706,
"preview": "import { useEffect, useState } from 'react';\nimport {\n CODE_EDITOR_DEFAULTS,\n CODE_EDITOR_SETTINGS_CHANGED_EVENT,\n CO"
},
{
"path": "src/components/code-editor/hooks/useEditorKeyboardShortcuts.ts",
"chars": 825,
"preview": "import { useEffect } from 'react';\n\ntype UseEditorKeyboardShortcutsParams = {\n onSave: () => void;\n onClose: () => voi"
},
{
"path": "src/components/code-editor/hooks/useEditorSidebar.ts",
"chars": 3477,
"preview": "import { useCallback, useEffect, useRef, useState } from 'react';\nimport type { MouseEvent as ReactMouseEvent } from 're"
},
{
"path": "src/components/code-editor/types/types.ts",
"chars": 430,
"preview": "export type CodeEditorDiffInfo = {\n old_string?: string;\n new_string?: string;\n [key: string]: unknown;\n};\n\nexport ty"
},
{
"path": "src/components/code-editor/utils/binaryFile.ts",
"chars": 754,
"preview": "// Binary file extensions (images are handled by ImageViewer, not here)\nconst BINARY_EXTENSIONS = [\n // Archives\n 'zip"
},
{
"path": "src/components/code-editor/utils/editorExtensions.ts",
"chars": 3907,
"preview": "import { css } from '@codemirror/lang-css';\nimport { html } from '@codemirror/lang-html';\nimport { javascript } from '@c"
},
{
"path": "src/components/code-editor/utils/editorStyles.ts",
"chars": 2435,
"preview": "export const getEditorLoadingStyles = (isDarkMode: boolean) => {\n return `\n .code-editor-loading {\n background-"
},
{
"path": "src/components/code-editor/utils/editorToolbarPanel.ts",
"chars": 7879,
"preview": "import { getChunks } from '@codemirror/merge';\nimport { EditorView, showPanel } from '@codemirror/view';\nimport type { C"
},
{
"path": "src/components/code-editor/view/CodeEditor.tsx",
"chars": 7892,
"preview": "import { EditorView } from '@codemirror/view';\nimport { unifiedMergeView } from '@codemirror/merge';\nimport type { Exten"
},
{
"path": "src/components/code-editor/view/EditorSidebar.tsx",
"chars": 4306,
"preview": "import { useState, useEffect, useRef } from 'react';\nimport type { MouseEvent, MutableRefObject } from 'react';\nimport t"
},
{
"path": "src/components/code-editor/view/subcomponents/CodeEditorBinaryFile.tsx",
"chars": 5140,
"preview": "import type { CodeEditorFile } from '../../types/types';\n\ntype CodeEditorBinaryFileProps = {\n file: CodeEditorFile;\n i"
},
{
"path": "src/components/code-editor/view/subcomponents/CodeEditorFooter.tsx",
"chars": 754,
"preview": "type CodeEditorFooterProps = {\n content: string;\n linesLabel: string;\n charactersLabel: string;\n shortcutsLabel: str"
},
{
"path": "src/components/code-editor/view/subcomponents/CodeEditorHeader.tsx",
"chars": 5317,
"preview": "import { Code2, Download, Eye, Maximize2, Minimize2, Save, Settings as SettingsIcon, X } from 'lucide-react';\nimport typ"
},
{
"path": "src/components/code-editor/view/subcomponents/CodeEditorLoadingState.tsx",
"chars": 1302,
"preview": "import { getEditorLoadingStyles } from '../../utils/editorStyles';\n\ntype CodeEditorLoadingStateProps = {\n isDarkMode: b"
},
{
"path": "src/components/code-editor/view/subcomponents/CodeEditorSurface.tsx",
"chars": 1722,
"preview": "import CodeMirror from '@uiw/react-codemirror';\nimport { oneDark } from '@codemirror/theme-one-dark';\nimport type { Exte"
},
{
"path": "src/components/code-editor/view/subcomponents/markdown/MarkdownCodeBlock.tsx",
"chars": 2354,
"preview": "import { useState } from 'react';\nimport type { ComponentProps } from 'react';\nimport { Prism as SyntaxHighlighter } fro"
},
{
"path": "src/components/code-editor/view/subcomponents/markdown/MarkdownPreview.tsx",
"chars": 1803,
"preview": "import { useMemo } from 'react';\nimport type { Components } from 'react-markdown';\nimport ReactMarkdown from 'react-mark"
},
{
"path": "src/components/file-tree/constants/constants.ts",
"chars": 419,
"preview": "import type { FileTreeViewMode } from '../types/types';\n\nexport const FILE_TREE_VIEW_MODE_STORAGE_KEY = 'file-tree-view-"
},
{
"path": "src/components/file-tree/constants/fileIcons.ts",
"chars": 9739,
"preview": "import {\n Archive,\n Binary,\n Blocks,\n BookOpen,\n Box,\n Braces,\n Code2,\n Cog,\n Coffee,\n Cpu,\n Database,\n File"
},
{
"path": "src/components/file-tree/hooks/useExpandedDirectories.ts",
"chars": 1123,
"preview": "import { useCallback, useState } from 'react';\n\ntype UseExpandedDirectoriesResult = {\n expandedDirs: Set<string>;\n tog"
},
{
"path": "src/components/file-tree/hooks/useFileTreeData.ts",
"chars": 2309,
"preview": "import { useCallback, useEffect, useRef, useState } from 'react';\nimport { api } from '../../../utils/api';\nimport type "
},
{
"path": "src/components/file-tree/hooks/useFileTreeOperations.ts",
"chars": 11173,
"preview": "import { useCallback, useState } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport JSZip from 'jszip'"
},
{
"path": "src/components/file-tree/hooks/useFileTreeSearch.ts",
"chars": 1196,
"preview": "import { useEffect, useState } from 'react';\nimport { collectExpandedDirectoryPaths, filterFileTree } from '../utils/fil"
},
{
"path": "src/components/file-tree/hooks/useFileTreeUpload.ts",
"chars": 6360,
"preview": "import { useCallback, useState, useRef } from 'react';\nimport type { Project } from '../../../types/app';\nimport { api }"
},
{
"path": "src/components/file-tree/hooks/useFileTreeViewMode.ts",
"chars": 1200,
"preview": "import { useCallback, useEffect, useState } from 'react';\nimport {\n FILE_TREE_DEFAULT_VIEW_MODE,\n FILE_TREE_VIEW_MODES"
},
{
"path": "src/components/file-tree/types/types.ts",
"chars": 635,
"preview": "import type { LucideIcon } from 'lucide-react';\n\nexport type FileTreeViewMode = 'simple' | 'compact' | 'detailed';\n\nexpo"
},
{
"path": "src/components/file-tree/utils/fileTreeUtils.ts",
"chars": 2391,
"preview": "import type { TFunction } from 'i18next';\nimport { IMAGE_FILE_EXTENSIONS } from '../constants/constants';\nimport type { "
},
{
"path": "src/components/file-tree/view/FileContextMenu.tsx",
"chars": 10001,
"preview": "import { Fragment, useCallback, useEffect, useMemo, useRef, useState, type MouseEvent as ReactMouseEvent, type ReactNode"
},
{
"path": "src/components/file-tree/view/FileTree.tsx",
"chars": 11176,
"preview": "import { useCallback, useState, useEffect, useRef } from 'react';\nimport { useTranslation } from 'react-i18next';\nimport"
},
{
"path": "src/components/file-tree/view/FileTreeBody.tsx",
"chars": 3021,
"preview": "import type { ReactNode, RefObject } from 'react';\nimport { Folder, Search } from 'lucide-react';\nimport { useTranslatio"
},
{
"path": "src/components/file-tree/view/FileTreeDetailedColumns.tsx",
"chars": 633,
"preview": "import { useTranslation } from 'react-i18next';\n\nexport default function FileTreeDetailedColumns() {\n const { t } = use"
},
{
"path": "src/components/file-tree/view/FileTreeEmptyState.tsx",
"chars": 630,
"preview": "import type { LucideIcon } from 'lucide-react';\n\ntype FileTreeEmptyStateProps = {\n icon: LucideIcon;\n title: string;\n "
},
{
"path": "src/components/file-tree/view/FileTreeHeader.tsx",
"chars": 5252,
"preview": "import { ChevronDown, Eye, FileText, FolderPlus, List, RefreshCw, Search, TableProperties, X } from 'lucide-react';\nimpo"
},
{
"path": "src/components/file-tree/view/FileTreeList.tsx",
"chars": 2440,
"preview": "import type { ReactNode, RefObject } from 'react';\nimport type { FileTreeNode as FileTreeNodeType, FileTreeViewMode } fr"
},
{
"path": "src/components/file-tree/view/FileTreeLoadingState.tsx",
"chars": 308,
"preview": "import { useTranslation } from 'react-i18next';\n\nexport default function FileTreeLoadingState() {\n const { t } = useTra"
},
{
"path": "src/components/file-tree/view/FileTreeNode.tsx",
"chars": 8346,
"preview": "import type { ReactNode, RefObject } from 'react';\nimport { ChevronRight, Folder, FolderOpen } from 'lucide-react';\nimpo"
},
{
"path": "src/components/file-tree/view/ImageViewer.tsx",
"chars": 3279,
"preview": "import { useEffect, useState } from 'react';\nimport { X } from 'lucide-react';\nimport { Button } from '../../../shared/v"
},
{
"path": "src/components/git-panel/constants/constants.ts",
"chars": 2986,
"preview": "import type { ConfirmActionType, FileStatusCode, GitStatusGroupEntry } from '../types/types';\n\nexport const DEFAULT_BRAN"
},
{
"path": "src/components/git-panel/hooks/useGitPanelController.ts",
"chars": 21055,
"preview": "import { useCallback, useEffect, useRef, useState } from 'react';\nimport { authenticatedFetch } from '../../../utils/api"
},
{
"path": "src/components/git-panel/hooks/useRevertLocalCommit.ts",
"chars": 1417,
"preview": "import { useCallback, useState } from 'react';\nimport { authenticatedFetch } from '../../../utils/api';\nimport type { Gi"
},
{
"path": "src/components/git-panel/hooks/useSelectedProvider.ts",
"chars": 640,
"preview": "import { useEffect, useState } from 'react';\n\nexport function useSelectedProvider() {\n const [provider, setProvider] = "
},
{
"path": "src/components/git-panel/types/types.ts",
"chars": 3741,
"preview": "import type { Project } from '../../../types/app';\n\nexport type GitPanelView = 'changes' | 'history' | 'branches';\nexpor"
},
{
"path": "src/components/git-panel/utils/gitPanelUtils.ts",
"chars": 3052,
"preview": "import { FILE_STATUS_BADGE_CLASSES, FILE_STATUS_GROUPS, FILE_STATUS_LABELS } from '../constants/constants';\nimport type "
},
{
"path": "src/components/git-panel/view/GitPanel.tsx",
"chars": 5861,
"preview": "import { useCallback, useState } from 'react';\nimport { useGitPanelController } from '../hooks/useGitPanelController';\ni"
},
{
"path": "src/components/git-panel/view/GitPanelHeader.tsx",
"chars": 11447,
"preview": "import { AlertCircle, Check, ChevronDown, Download, GitBranch, Plus, RefreshCw, RotateCcw, Upload, X } from 'lucide-reac"
},
{
"path": "src/components/git-panel/view/GitRepositoryErrorState.tsx",
"chars": 1103,
"preview": "import { GitBranch } from 'lucide-react';\n\ntype GitRepositoryErrorStateProps = {\n error: string;\n details?: string;\n};"
},
{
"path": "src/components/git-panel/view/GitViewTabs.tsx",
"chars": 1654,
"preview": "import { FileText, GitBranch, History } from 'lucide-react';\nimport type { GitPanelView } from '../types/types';\n\ntype G"
},
{
"path": "src/components/git-panel/view/branches/BranchesView.tsx",
"chars": 8735,
"preview": "import { Check, GitBranch, Globe, Plus, RefreshCw, Trash2 } from 'lucide-react';\nimport { useState } from 'react';\nimpor"
},
{
"path": "src/components/git-panel/view/changes/ChangesView.tsx",
"chars": 9681,
"preview": "import { GitBranch, GitCommit, RefreshCw } from 'lucide-react';\nimport { useCallback, useEffect, useMemo, useState } fro"
},
{
"path": "src/components/git-panel/view/changes/CommitComposer.tsx",
"chars": 6441,
"preview": "import { Check, ChevronDown, GitCommit, RefreshCw, Sparkles } from 'lucide-react';\nimport { useState } from 'react';\nimp"
},
{
"path": "src/components/git-panel/view/changes/FileChangeItem.tsx",
"chars": 4766,
"preview": "import { ChevronRight, Trash2 } from 'lucide-react';\nimport type { FileStatusCode } from '../../types/types';\nimport { g"
},
{
"path": "src/components/git-panel/view/changes/FileChangeList.tsx",
"chars": 1758,
"preview": "import { FILE_STATUS_GROUPS } from '../../constants/constants';\nimport type { FileStatusCode, GitDiffMap, GitStatusRespo"
},
{
"path": "src/components/git-panel/view/changes/FileSelectionControls.tsx",
"chars": 1359,
"preview": "type FileSelectionControlsProps = {\n isMobile: boolean;\n selectedCount: number;\n totalCount: number;\n isHidden: bool"
}
]
// ... and 248 more files (download for full content)
About this extraction
This page contains the full source code of the siteboon/claudecodeui GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 448 files (2.4 MB), approximately 659.0k tokens, and a symbol index with 1297 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.