diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..bb68b14a --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,31 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/reference/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#jobs-overview & https://circleci.com/docs/reference/configuration-reference/#jobs +jobs: + say-hello: + # Specify the execution environment. You can specify an image from Docker Hub or use one of our convenience images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/guides/execution-managed/executor-intro/ & https://circleci.com/docs/reference/configuration-reference/#executor-job + docker: + # Specify the version you desire here + # See: https://circleci.com/developer/images/image/cimg/base + - image: cimg/base:current + + # Add steps to the job + # See: https://circleci.com/docs/guides/orchestrate/jobs-steps/#steps-overview & https://circleci.com/docs/reference/configuration-reference/#steps + steps: + # Checkout the code as the first step. + - checkout + - run: + name: "Say hello" + command: "echo Hello, World!" + +# Orchestrate jobs using workflows +# See: https://circleci.com/docs/guides/orchestrate/workflows/ & https://circleci.com/docs/reference/configuration-reference/#workflows +workflows: + say-hello-workflow: # This is the name of the workflow, feel free to change it to better match your workflow. + # Inside the workflow, you define the jobs you want to run. + jobs: + - say-hello \ No newline at end of file diff --git a/.env.codegen b/.env.codegen new file mode 100644 index 00000000..fe158722 --- /dev/null +++ b/.env.codegen @@ -0,0 +1,78 @@ +# Claude Code UI Environment Configuration for Codegen +# Optimized settings for Codegen integration + +# ============================================================================= +# SERVER CONFIGURATION +# ============================================================================= + +# Backend server port (Express API + WebSocket server) +# Using 3001 for API server (compatible with sandbox) +PORT=3001 + +# Frontend port (Vite dev server) +# Using 5173 for frontend (Vite default) +VITE_PORT=5173 + +# ============================================================================= +# CODEGEN SPECIFIC CONFIGURATION +# ============================================================================= + +# Codegen API settings +CODEGEN_API_URL=https://api.codegen.com +CODEGEN_WS_URL=wss://ws.codegen.com + +# Codegen CLI path (will be auto-detected if not specified) +CODEGEN_CLI_PATH=codegen + +# Session management +CODEGEN_SESSION_TIMEOUT=3600000 +CODEGEN_MAX_SESSIONS=10 + +# Performance optimization for sandbox environment +CODEGEN_MEMORY_LIMIT=512 +CODEGEN_CPU_LIMIT=2 + +# ============================================================================= +# SECURITY CONFIGURATION +# ============================================================================= + +# JWT settings for authentication +JWT_SECRET=your-jwt-secret-key-here +JWT_EXPIRES_IN=24h + +# CORS settings +CORS_ORIGIN=http://localhost:5173,http://127.0.0.1:5173 + +# ============================================================================= +# DATABASE CONFIGURATION +# ============================================================================= + +# SQLite database path +DATABASE_PATH=./data/codegen.db + +# Database connection pool settings +DB_MAX_CONNECTIONS=10 +DB_IDLE_TIMEOUT=30000 + +# ============================================================================= +# LOGGING AND MONITORING +# ============================================================================= + +# Log level (error, warn, info, debug) +LOG_LEVEL=info + +# Enable performance monitoring +ENABLE_MONITORING=true + +# ============================================================================= +# DEVELOPMENT SETTINGS +# ============================================================================= + +# Enable hot reload for development +HOT_RELOAD=true + +# Enable debug mode +DEBUG_MODE=false + +# Enable verbose logging +VERBOSE_LOGGING=false diff --git a/.github/workflows/summary.yml b/.github/workflows/summary.yml new file mode 100644 index 00000000..9b07bb8f --- /dev/null +++ b/.github/workflows/summary.yml @@ -0,0 +1,34 @@ +name: Summarize new issues + +on: + issues: + types: [opened] + +jobs: + summary: + runs-on: ubuntu-latest + permissions: + issues: write + models: read + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Run AI inference + id: inference + uses: actions/ai-inference@v1 + with: + prompt: | + Summarize the following GitHub issue in one paragraph: + Title: ${{ github.event.issue.title }} + Body: ${{ github.event.issue.body }} + + - name: Comment with AI summary + run: | + gh issue comment $ISSUE_NUMBER --body '${{ steps.inference.outputs.response }}' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_NUMBER: ${{ github.event.issue.number }} + RESPONSE: ${{ steps.inference.outputs.response }} diff --git a/CODEGEN_SETUP.md b/CODEGEN_SETUP.md new file mode 100644 index 00000000..49f06177 --- /dev/null +++ b/CODEGEN_SETUP.md @@ -0,0 +1,279 @@ +# Настройка Codegen в Claude Code UI + +## Обзор + +Claude Code UI теперь поддерживает интеграцию с Codegen - мощным AI-агентом для разработки программного обеспечения. Эта интеграция позволяет использовать возможности Codegen прямо из веб-интерфейса. + +## Архитектура интеграции + +### Backend компоненты + +1. **server/codegen-cli.js** - Модуль для управления процессами Codegen + - Функция `spawnCodegen()` - запуск новых сессий + - Функция `abortCodegenSession()` - завершение сессий + - Управление WebSocket соединениями + +2. **server/routes/codegen.js** - API маршруты для Codegen + - `POST /api/codegen/command` - отправка команд + - `POST /api/codegen/abort` - прерывание сессий + - Аутентификация через JWT токены + +3. **server/index.js** - Основной сервер + - Импорт Codegen модулей + - Подключение защищенных маршрутов + - WebSocket обработка + +### Frontend компоненты + +1. **src/components/CodegenLogo.jsx** - SVG логотип Codegen + - Зеленая цветовая схема (#10B981 to #047857) + - Анимированные элементы + - Адаптивный дизайн + +2. **src/components/ChatInterface.jsx** - Основной интерфейс чата + - Выбор провайдера (Claude/Cursor/Codegen) + - Обработка сообщений Codegen + - Отображение логотипов и статусов + +3. **src/components/MainContent.jsx** и **Sidebar.jsx** + - Поддержка выбора Codegen провайдера + - Обновленная навигация + +## Установка и настройка + +### Предварительные требования + +1. Node.js (версия 16 или выше) +2. npm или yarn +3. Установленный Codegen CLI + +### Шаги установки + +1. **Клонирование репозитория** + ```bash + git clone https://github.com/evgenygurin/claudecodeui.git + cd claudecodeui + ``` + +2. **Установка зависимостей** + ```bash + npm install + ``` + +3. **Установка дополнительных зависимостей для сборки** + ```bash + npm install --save-dev terser + ``` + +4. **Сборка проекта** + ```bash + npm run build + ``` + +5. **Запуск сервера** + ```bash + npm start + ``` + +## Использование + +### Выбор провайдера + +1. Откройте веб-интерфейс Claude Code UI +2. В интерфейсе чата найдите секцию выбора провайдера +3. Выберите "Codegen" из доступных опций +4. Интерфейс автоматически переключится на зеленую тему Codegen + +### Отправка команд + +1. После выбора Codegen введите команду в текстовое поле +2. Нажмите Enter или кнопку отправки +3. Команда будет отправлена через WebSocket на backend +4. Ответ от Codegen отобразится в интерфейсе чата + +### Управление сессиями + +- Каждая сессия Codegen имеет уникальный ID +- Сессии автоматически возобновляются при переключении между проектами +- Возможность прерывания длительных операций + +## API Endpoints + +### POST /api/codegen/command +Отправка команды в Codegen + +**Параметры:** +```json +{ + "type": "codegen-command", + "command": "string", + "sessionId": "string", + "options": { + "cwd": "string", + "projectPath": "string", + "sessionId": "string", + "resume": boolean, + "toolsSettings": object + } +} +``` + +### POST /api/codegen/abort +Прерывание сессии Codegen + +**Параметры:** +```json +{ + "sessionId": "string" +} +``` + +## Конфигурация + +### Настройки провайдера + +Выбор провайдера сохраняется в localStorage: +```javascript +localStorage.setItem('selected-provider', 'codegen'); +``` + +### WebSocket сообщения + +Формат сообщений для Codegen: +```javascript +{ + type: 'codegen-command', + command: input, + sessionId: effectiveSessionId, + options: { + cwd: selectedProject.fullPath || selectedProject.path, + projectPath: selectedProject.fullPath || selectedProject.path, + sessionId: effectiveSessionId, + resume: !!effectiveSessionId, + toolsSettings: toolsSettings + } +} +``` + +## Стилизация + +### Цветовая схема Codegen + +- Основной цвет: `#10B981` (зеленый) +- Темный оттенок: `#047857` +- Тень: `ring-2 ring-green-500/20` +- Границы: `border-green-500` + +### CSS классы + +```css +.codegen-theme { + border-green-500 shadow-lg ring-2 ring-green-500/20 +} +``` + +## Отладка + +### Логи сервера + +Сервер выводит подробные логи для отладки: +- Запуск/остановка процессов Codegen +- WebSocket соединения +- Ошибки аутентификации + +### Логи браузера + +В консоли браузера отображаются: +- Сообщения WebSocket +- Ошибки JavaScript +- Состояние провайдера + +## Устранение неполадок + +### Проблемы со сборкой + +1. **Ошибка "terser not found"** + ```bash + npm install --save-dev terser + ``` + +2. **CSS предупреждения** + - Предупреждения CSS не критичны и не влияют на функциональность + +### Проблемы с WebSocket + +1. Проверьте подключение к серверу +2. Убедитесь в корректности JWT токена +3. Проверьте настройки CORS + +### Проблемы с Codegen + +1. Убедитесь, что Codegen CLI установлен +2. Проверьте права доступа к файлам проекта +3. Убедитесь в корректности путей к проекту + +## Разработка + +### Структура файлов + +``` +claudecodeui/ +├── server/ +│ ├── codegen-cli.js # Управление процессами Codegen +│ ├── routes/codegen.js # API маршруты +│ └── index.js # Основной сервер +├── src/ +│ └── components/ +│ ├── CodegenLogo.jsx # Логотип Codegen +│ ├── ChatInterface.jsx # Основной интерфейс +│ ├── MainContent.jsx # Контент +│ └── Sidebar.jsx # Боковая панель +└── CODEGEN_SETUP.md # Эта документация +``` + +### Добавление новых функций + +1. Backend изменения в `server/routes/codegen.js` +2. Frontend изменения в соответствующих компонентах +3. Обновление WebSocket обработчиков +4. Тестирование интеграции + +## Безопасность + +### Аутентификация + +- Все API маршруты защищены JWT токенами +- Middleware `authenticateToken` проверяет валидность токенов +- Сессии изолированы по пользователям + +### Изоляция процессов + +- Каждая сессия Codegen запускается в отдельном процессе +- Процессы имеют ограниченные права доступа +- Автоматическое завершение неактивных сессий + +## Производительность + +### Оптимизация сборки + +- Минификация CSS и JavaScript +- Разделение кода на чанки +- Сжатие gzip + +### Управление памятью + +- Автоматическая очистка завершенных процессов +- Ограничение количества одновременных сессий +- Мониторинг использования ресурсов + +## Поддержка + +Для получения поддержки: +1. Проверьте логи сервера и браузера +2. Убедитесь в корректности конфигурации +3. Создайте issue в репозитории GitHub + +## Лицензия + +Проект распространяется под лицензией MIT. + diff --git a/QUICK_START_CODEGEN.md b/QUICK_START_CODEGEN.md new file mode 100644 index 00000000..45922d50 --- /dev/null +++ b/QUICK_START_CODEGEN.md @@ -0,0 +1,57 @@ +# Быстрый старт с Codegen + +## 🚀 Быстрая настройка + +### 1. Установка зависимостей +```bash +npm install +npm install --save-dev terser +``` + +### 2. Сборка и запуск +```bash +npm run build +npm start +``` + +### 3. Использование +1. Откройте веб-интерфейс +2. Выберите провайдер "Codegen" +3. Начните вводить команды + +## 🎯 Ключевые особенности + +- ✅ Полная интеграция с Codegen CLI +- ✅ WebSocket соединения в реальном времени +- ✅ Управление сессиями и проектами +- ✅ Зеленая тема интерфейса +- ✅ JWT аутентификация + +## 🔧 Основные файлы + +| Файл | Назначение | +|------|------------| +| `server/codegen-cli.js` | Управление процессами Codegen | +| `server/routes/codegen.js` | API маршруты | +| `src/components/CodegenLogo.jsx` | Логотип компонент | +| `src/components/ChatInterface.jsx` | Основной интерфейс | + +## 🐛 Решение проблем + +**Ошибка сборки:** +```bash +npm install --save-dev terser +``` + +**WebSocket не работает:** +- Проверьте JWT токен +- Убедитесь что сервер запущен + +**Codegen не отвечает:** +- Проверьте установку Codegen CLI +- Убедитесь в правах доступа к проекту + +## 📚 Подробная документация + +См. [CODEGEN_SETUP.md](./CODEGEN_SETUP.md) для полной документации. + diff --git a/README.md b/README.md index 759fa186..3468063c 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ -A desktop and mobile UI for [Claude Code](https://docs.anthropic.com/en/docs/claude-code), and [Cursor CLI](https://docs.cursor.com/en/cli/overview). You can use it locally or remotely to view your active projects and sessions in Claude Code or Cursor and make changes to them from everywhere (mobile or desktop). This gives you a proper interface that works everywhere. Supports models including **Claude Sonnet 4**, **Opus 4.1**, and **GPT-5** +A desktop and mobile UI for [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor CLI](https://docs.cursor.com/en/cli/overview), and **[Codegen](https://codegen.com)**. You can use it locally or remotely to view your active projects and sessions in Claude Code, Cursor, or Codegen and make changes to them from everywhere (mobile or desktop). This gives you a proper interface that works everywhere. Supports models including **Claude Sonnet 4**, **Opus 4.1**, and **GPT-5** ## Screenshots @@ -42,8 +42,9 @@ A desktop and mobile UI for [Claude Code](https://docs.anthropic.com/en/docs/cla ## Features - **Responsive Design** - Works seamlessly across desktop, tablet, and mobile so you can also use Claude Code from mobile -- **Interactive Chat Interface** - Built-in chat interface for seamless communication with Claude Code or Cursor -- **Integrated Shell Terminal** - Direct access to Claude Code or Cursor CLI through built-in shell functionality +- **Interactive Chat Interface** - Built-in chat interface for seamless communication with Claude Code, Cursor, or Codegen +- **Integrated Shell Terminal** - Direct access to Claude Code, Cursor CLI, or Codegen through built-in shell functionality +- **Multi-Provider Support** - Switch between Claude Code, Cursor, and Codegen providers with a single click - **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 @@ -57,7 +58,8 @@ A desktop and mobile UI for [Claude Code](https://docs.anthropic.com/en/docs/cla - [Node.js](https://nodejs.org/) v20 or higher - [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and configured, and/or -- [Cursor CLI](https://docs.cursor.com/en/cli/overview) installed and configured +- [Cursor CLI](https://docs.cursor.com/en/cli/overview) installed and configured, and/or +- [Codegen](https://codegen.com) installed and configured ### Installation @@ -242,6 +244,11 @@ d - Review server console logs for detailed error messages - Ensure you're not trying to access system directories outside project scope +## Codegen Integration + +For detailed information about Codegen integration, see: +- [Codegen Setup Guide](./CODEGEN_SETUP.md) - Complete setup and configuration documentation +- [Quick Start with Codegen](./QUICK_START_CODEGEN.md) - Fast setup guide ## License diff --git a/package-lock.json b/package-lock.json index 769aeb5d..743f02f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "claude-code-ui", - "version": "1.7.0", + "version": "1.8.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "claude-code-ui", - "version": "1.7.0", + "version": "1.8.0", "license": "MIT", "dependencies": { "@codemirror/lang-css": "^6.3.1", @@ -56,6 +56,7 @@ "postcss": "^8.4.32", "sharp": "^0.34.2", "tailwindcss": "^3.4.0", + "terser": "^5.44.0", "vite": "^7.0.4" } }, @@ -1268,6 +1269,17 @@ "node": ">=6.0.0" } }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.4.tgz", @@ -2100,6 +2112,19 @@ "node": ">= 0.6" } }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -7257,6 +7282,16 @@ "node": ">= 14" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -7266,6 +7301,17 @@ "node": ">=0.10.0" } }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/space-separated-tokens": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", @@ -8092,6 +8138,32 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "license": "ISC" }, + "node_modules/terser": { + "version": "5.44.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.0.tgz", + "integrity": "sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.15.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true, + "license": "MIT" + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", diff --git a/package.json b/package.json index f88599ae..5804320e 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "postcss": "^8.4.32", "sharp": "^0.34.2", "tailwindcss": "^3.4.0", + "terser": "^5.44.0", "vite": "^7.0.4" } } diff --git a/server/codegen-cli.js b/server/codegen-cli.js new file mode 100644 index 00000000..1eaed3c2 --- /dev/null +++ b/server/codegen-cli.js @@ -0,0 +1,281 @@ +import { spawn } from 'child_process'; +import crossSpawn from 'cross-spawn'; +import { promises as fs } from 'fs'; +import path from 'path'; +import os from 'os'; + +// Use cross-spawn on Windows for better command execution +const spawnFunction = process.platform === 'win32' ? crossSpawn : spawn; + +let activeCodegenProcesses = new Map(); // Track active processes by session ID + +async function spawnCodegen(command, options = {}, ws) { + return new Promise(async (resolve, reject) => { + const { sessionId, projectPath, cwd, resume, toolsSettings, permissionMode, images } = options; + let capturedSessionId = sessionId; // Track session ID throughout the process + let sessionCreatedSent = false; // Track if we've already sent session-created event + + // Use tools settings passed from frontend, or defaults + const settings = toolsSettings || { + allowedTools: [], + disallowedTools: [], + skipPermissions: false + }; + + // Build Codegen CLI command + const args = []; + + // Add command if we have one + if (command && command.trim()) { + args.push('--message'); + args.push(command); + } + + // Use cwd (actual project directory) instead of projectPath + const workingDir = cwd || process.cwd(); + + // Handle images by saving them to temporary files and passing paths to Codegen + const tempImagePaths = []; + let tempDir = null; + if (images && images.length > 0) { + try { + // Create temp directory in the project directory so Codegen can access it + 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()) { + const imageBuffer = Buffer.from(image.data, 'base64'); + const extension = image.type.split('/')[1] || 'png'; + const filename = `image_${index}.${extension}`; + const imagePath = path.join(tempDir, filename); + + await fs.writeFile(imagePath, imageBuffer); + tempImagePaths.push(imagePath); + } + + // Add image paths to Codegen command + if (tempImagePaths.length > 0) { + args.push('--images'); + args.push(tempImagePaths.join(',')); + } + } catch (error) { + console.error('Error handling images:', error); + ws?.send(JSON.stringify({ + type: 'error', + data: `Error processing images: ${error.message}` + })); + } + } + + // Add resume flag if resuming + if (resume && sessionId) { + args.push('--resume'); + args.push(sessionId); + } + + // Add project path if specified + if (projectPath) { + args.push('--project'); + args.push(projectPath); + } + + // Add tools configuration + if (settings.allowedTools && settings.allowedTools.length > 0) { + args.push('--allow-tools'); + args.push(settings.allowedTools.join(',')); + } + + if (settings.disallowedTools && settings.disallowedTools.length > 0) { + args.push('--deny-tools'); + args.push(settings.disallowedTools.join(',')); + } + + if (settings.skipPermissions) { + args.push('--auto-approve'); + } + + console.log('Spawning Codegen with args:', args); + console.log('Working directory:', workingDir); + + // Get Codegen CLI path from environment or use default + const codegenPath = process.env.CODEGEN_CLI_PATH || 'codegen'; + + // Spawn the Codegen process + const child = spawnFunction(codegenPath, args, { + cwd: workingDir, + stdio: ['pipe', 'pipe', 'pipe'], + env: { + ...process.env, + FORCE_COLOR: '1', + TERM: 'xterm-256color' + } + }); + + // Store the process for potential abortion + if (capturedSessionId) { + activeCodegenProcesses.set(capturedSessionId, child); + } + + let outputBuffer = ''; + let errorBuffer = ''; + let isSessionActive = false; + + // Handle stdout + child.stdout.on('data', (data) => { + const output = data.toString(); + outputBuffer += output; + + // Send real-time output to WebSocket + if (ws && ws.readyState === ws.OPEN) { + ws.send(JSON.stringify({ + type: 'codegen-output', + data: output, + sessionId: capturedSessionId + })); + } + + // Parse Codegen output for session information + const lines = output.split('\n'); + for (const line of lines) { + // Look for session ID in output + const sessionMatch = line.match(/Session ID: ([a-zA-Z0-9-]+)/); + if (sessionMatch && !capturedSessionId) { + capturedSessionId = sessionMatch[1]; + activeCodegenProcesses.set(capturedSessionId, child); + + if (ws && ws.readyState === ws.OPEN && !sessionCreatedSent) { + ws.send(JSON.stringify({ + type: 'session-created', + sessionId: capturedSessionId, + projectPath: projectPath || workingDir + })); + sessionCreatedSent = true; + } + } + + // Check if session is active + if (line.includes('Codegen is ready') || line.includes('Session started')) { + isSessionActive = true; + } + } + }); + + // Handle stderr + child.stderr.on('data', (data) => { + const error = data.toString(); + errorBuffer += error; + + // Send error output to WebSocket + if (ws && ws.readyState === ws.OPEN) { + ws.send(JSON.stringify({ + type: 'codegen-error', + data: error, + sessionId: capturedSessionId + })); + } + }); + + // Handle process exit + child.on('close', async (code, signal) => { + console.log(`Codegen process exited with code ${code}, signal ${signal}`); + + // Clean up temporary images + if (tempDir) { + try { + await fs.rm(tempDir, { recursive: true, force: true }); + } catch (error) { + console.error('Error cleaning up temp images:', error); + } + } + + // Remove from active processes + if (capturedSessionId) { + activeCodegenProcesses.delete(capturedSessionId); + } + + // Send completion message to WebSocket + if (ws && ws.readyState === ws.OPEN) { + ws.send(JSON.stringify({ + type: 'codegen-complete', + code, + signal, + sessionId: capturedSessionId, + output: outputBuffer, + error: errorBuffer + })); + } + + if (code === 0) { + resolve({ + success: true, + output: outputBuffer, + sessionId: capturedSessionId, + isSessionActive + }); + } else { + reject(new Error(`Codegen process failed with code ${code}: ${errorBuffer}`)); + } + }); + + // Handle process error + child.on('error', (error) => { + console.error('Codegen process error:', error); + + // Remove from active processes + if (capturedSessionId) { + activeCodegenProcesses.delete(capturedSessionId); + } + + // Send error to WebSocket + if (ws && ws.readyState === ws.OPEN) { + ws.send(JSON.stringify({ + type: 'codegen-error', + data: error.message, + sessionId: capturedSessionId + })); + } + + reject(error); + }); + + // Send initial input if we have a command + if (command && command.trim()) { + child.stdin.write(command + '\n'); + } + }); +} + +// Function to abort a Codegen session +function abortCodegenSession(sessionId) { + const process = activeCodegenProcesses.get(sessionId); + if (process) { + console.log(`Aborting Codegen session: ${sessionId}`); + process.kill('SIGTERM'); + activeCodegenProcesses.delete(sessionId); + return true; + } + return false; +} + +// Function to get active Codegen sessions +function getActiveCodegenSessions() { + return Array.from(activeCodegenProcesses.keys()); +} + +// Function to send input to an active Codegen session +function sendInputToCodegenSession(sessionId, input) { + const process = activeCodegenProcesses.get(sessionId); + if (process && process.stdin && process.stdin.writable) { + process.stdin.write(input + '\n'); + return true; + } + return false; +} + +export { + spawnCodegen, + abortCodegenSession, + getActiveCodegenSessions, + sendInputToCodegenSession +}; diff --git a/server/index.js b/server/index.js index f074c578..2a7d058d 100755 --- a/server/index.js +++ b/server/index.js @@ -39,10 +39,12 @@ import mime from 'mime-types'; import { getProjects, getSessions, getSessionMessages, renameProject, deleteSession, deleteProject, addProjectManually, extractProjectDirectory, clearProjectDirectoryCache } from './projects.js'; import { spawnClaude, abortClaudeSession } from './claude-cli.js'; import { spawnCursor, abortCursorSession } from './cursor-cli.js'; +import { spawnCodegen, abortCodegenSession } from './codegen-cli.js'; import gitRoutes from './routes/git.js'; import authRoutes from './routes/auth.js'; import mcpRoutes from './routes/mcp.js'; import cursorRoutes from './routes/cursor.js'; +import codegenRoutes from './routes/codegen.js'; import taskmasterRoutes from './routes/taskmaster.js'; import mcpUtilsRoutes from './routes/mcp-utils.js'; import { initializeDatabase } from './database/db.js'; @@ -185,6 +187,9 @@ app.use('/api/mcp', authenticateToken, mcpRoutes); // Cursor API Routes (protected) app.use('/api/cursor', authenticateToken, cursorRoutes); +// Codegen API Routes (protected) +app.use('/api/codegen', authenticateToken, codegenRoutes); + // TaskMaster API Routes (protected) app.use('/api/taskmaster', authenticateToken, taskmasterRoutes); diff --git a/server/routes/codegen.js b/server/routes/codegen.js new file mode 100644 index 00000000..14a0d169 --- /dev/null +++ b/server/routes/codegen.js @@ -0,0 +1,226 @@ +import express from 'express'; +import { spawnCodegen, abortCodegenSession, getActiveCodegenSessions, sendInputToCodegenSession } from '../codegen-cli.js'; +import { authenticateToken } from '../middleware/auth.js'; + +const router = express.Router(); + +// Start a new Codegen session +router.post('/start', authenticateToken, async (req, res) => { + try { + const { command, projectPath, cwd, toolsSettings, permissionMode, images } = req.body; + + console.log('Starting Codegen session with:', { + command: command?.substring(0, 100) + '...', + projectPath, + cwd, + toolsSettings, + permissionMode, + imageCount: images?.length || 0 + }); + + // Start Codegen process + const result = await spawnCodegen(command, { + projectPath, + cwd, + toolsSettings, + permissionMode, + images + }); + + res.json({ + success: true, + sessionId: result.sessionId, + output: result.output, + isSessionActive: result.isSessionActive + }); + } catch (error) { + console.error('Error starting Codegen session:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Resume an existing Codegen session +router.post('/resume', authenticateToken, async (req, res) => { + try { + const { sessionId, command, projectPath, cwd, toolsSettings } = req.body; + + console.log('Resuming Codegen session:', sessionId); + + const result = await spawnCodegen(command, { + sessionId, + projectPath, + cwd, + resume: true, + toolsSettings + }); + + res.json({ + success: true, + sessionId: result.sessionId, + output: result.output, + isSessionActive: result.isSessionActive + }); + } catch (error) { + console.error('Error resuming Codegen session:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Send input to an active Codegen session +router.post('/input', authenticateToken, async (req, res) => { + try { + const { sessionId, input } = req.body; + + if (!sessionId || !input) { + return res.status(400).json({ + success: false, + error: 'Session ID and input are required' + }); + } + + const success = sendInputToCodegenSession(sessionId, input); + + res.json({ + success, + message: success ? 'Input sent successfully' : 'Session not found or not active' + }); + } catch (error) { + console.error('Error sending input to Codegen session:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Abort a Codegen session +router.post('/abort', authenticateToken, async (req, res) => { + try { + const { sessionId } = req.body; + + if (!sessionId) { + return res.status(400).json({ + success: false, + error: 'Session ID is required' + }); + } + + const success = abortCodegenSession(sessionId); + + res.json({ + success, + message: success ? 'Session aborted successfully' : 'Session not found' + }); + } catch (error) { + console.error('Error aborting Codegen session:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get active Codegen sessions +router.get('/sessions', authenticateToken, async (req, res) => { + try { + const activeSessions = getActiveCodegenSessions(); + + res.json({ + success: true, + sessions: activeSessions + }); + } catch (error) { + console.error('Error getting active Codegen sessions:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Check Codegen CLI availability +router.get('/status', authenticateToken, async (req, res) => { + try { + const { spawn } = await import('child_process'); + const codegenPath = process.env.CODEGEN_CLI_PATH || 'codegen'; + + // Try to run codegen --version to check if it's available + const child = spawn(codegenPath, ['--version'], { + stdio: ['pipe', 'pipe', 'pipe'] + }); + + let output = ''; + let error = ''; + + child.stdout.on('data', (data) => { + output += data.toString(); + }); + + child.stderr.on('data', (data) => { + error += data.toString(); + }); + + child.on('close', (code) => { + if (code === 0) { + res.json({ + success: true, + available: true, + version: output.trim(), + path: codegenPath + }); + } else { + res.json({ + success: true, + available: false, + error: error.trim() || 'Codegen CLI not found', + path: codegenPath + }); + } + }); + + child.on('error', (err) => { + res.json({ + success: true, + available: false, + error: err.message, + path: codegenPath + }); + }); + } catch (error) { + console.error('Error checking Codegen status:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +// Get Codegen configuration +router.get('/config', authenticateToken, async (req, res) => { + try { + res.json({ + success: true, + config: { + cliPath: process.env.CODEGEN_CLI_PATH || 'codegen', + sessionTimeout: parseInt(process.env.CODEGEN_SESSION_TIMEOUT) || 3600000, + maxSessions: parseInt(process.env.CODEGEN_MAX_SESSIONS) || 10, + memoryLimit: parseInt(process.env.MEMORY_LIMIT) || 512, + cpuLimit: parseInt(process.env.CPU_LIMIT) || 2 + } + }); + } catch (error) { + console.error('Error getting Codegen config:', error); + res.status(500).json({ + success: false, + error: error.message + }); + } +}); + +export default router; diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx index e38efcd5..cdacf420 100644 --- a/src/components/ChatInterface.jsx +++ b/src/components/ChatInterface.jsx @@ -22,6 +22,7 @@ import { useDropzone } from 'react-dropzone'; import TodoList from './TodoList'; import ClaudeLogo from './ClaudeLogo.jsx'; import CursorLogo from './CursorLogo.jsx'; +import CodegenLogo from './CodegenLogo.jsx'; import NextTaskBanner from './NextTaskBanner.jsx'; import { useTasksSettings } from '../contexts/TasksSettingsContext'; @@ -248,7 +249,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile )}
- {message.type === 'error' ? 'Error' : message.type === 'tool' ? 'Tool' : ((localStorage.getItem('selected-provider') || 'claude') === 'cursor' ? 'Cursor' : 'Claude')} + {message.type === 'error' ? 'Error' : message.type === 'tool' ? 'Tool' : ((localStorage.getItem('selected-provider') || 'claude') === 'cursor' ? 'Cursor' : (localStorage.getItem('selected-provider') || 'claude') === 'codegen' ? 'Codegen' : 'Claude')}
)} @@ -2778,6 +2779,20 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess toolsSettings: toolsSettings } }); + } else if (provider === 'codegen') { + // Send Codegen command + sendMessage({ + type: 'codegen-command', + command: input, + sessionId: effectiveSessionId, + options: { + cwd: selectedProject.fullPath || selectedProject.path, + projectPath: selectedProject.fullPath || selectedProject.path, + sessionId: effectiveSessionId, + resume: !!effectiveSessionId, + toolsSettings: toolsSettings + } + }); } else { // Send Claude command (existing code) sendMessage({ @@ -3000,7 +3015,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess Select a provider to start a new conversation

-
+
{/* Claude Button */}
)} + + {/* Codegen Button */} +
{/* Model Selection for Cursor - Always reserve space to prevent jumping */} @@ -3092,7 +3139,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess ? 'Ready to use Claude AI. Start typing your message below.' : provider === 'cursor' ? `Ready to use Cursor with ${cursorModel}. Start typing your message below.` - : 'Select a provider above to begin' + : provider === 'codegen' ? 'Ready to use Codegen AI. Start typing your message below.' : 'Select a provider above to begin' }

@@ -3194,7 +3241,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess )} -
{(localStorage.getItem('selected-provider') || 'claude') === 'cursor' ? 'Cursor' : 'Claude'}
+
{(localStorage.getItem('selected-provider') || 'claude') === 'cursor' ? 'Cursor' : (localStorage.getItem('selected-provider') || 'claude') === 'codegen' ? 'Codegen' : 'Claude'}
{/* Abort button removed - functionality not yet implemented at backend */}
diff --git a/src/components/CodegenLogo.jsx b/src/components/CodegenLogo.jsx new file mode 100644 index 00000000..8f0e122e --- /dev/null +++ b/src/components/CodegenLogo.jsx @@ -0,0 +1,78 @@ +import React from 'react'; + +const CodegenLogo = ({ className = "w-8 h-8" }) => { + return ( + + {/* Codegen logo - stylized code brackets with AI elements */} + + + + + + + + + {/* Left bracket */} + + + {/* Right bracket */} + + + {/* AI brain/neural network pattern in center */} + + + + + + {/* Connection lines */} + + + ); +}; + +export default CodegenLogo; diff --git a/src/components/MainContent.jsx b/src/components/MainContent.jsx index 5bbbc7a3..8db7954b 100644 --- a/src/components/MainContent.jsx +++ b/src/components/MainContent.jsx @@ -20,6 +20,7 @@ import GitPanel from './GitPanel'; import ErrorBoundary from './ErrorBoundary'; import ClaudeLogo from './ClaudeLogo'; import CursorLogo from './CursorLogo'; +import CodegenLogo from './CodegenLogo'; import TaskList from './TaskList'; import TaskDetail from './TaskDetail'; import PRDEditor from './PRDEditor'; diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index 4d1d4e20..d2161ac5 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -8,6 +8,7 @@ import { FolderOpen, Folder, Plus, MessageSquare, Clock, ChevronDown, ChevronRig import { cn } from '../lib/utils'; import ClaudeLogo from './ClaudeLogo'; import CursorLogo from './CursorLogo.jsx'; +import CodegenLogo from './CodegenLogo.jsx'; import TaskIndicator from './TaskIndicator'; import { api } from '../utils/api'; import { useTaskMaster } from '../contexts/TaskMasterContext'; diff --git a/vite.config.js b/vite.config.js index 3d166b8e..526aec43 100755 --- a/vite.config.js +++ b/vite.config.js @@ -5,25 +5,52 @@ export default defineConfig(({ command, mode }) => { // Load env file based on `mode` in the current working directory. const env = loadEnv(mode, process.cwd(), '') - return { plugins: [react()], server: { port: parseInt(env.VITE_PORT) || 5173, + host: '0.0.0.0', // Allow external connections for sandbox proxy: { '/api': `http://localhost:${env.PORT || 3001}`, '/ws': { target: `ws://localhost:${env.PORT || 3001}`, - ws: true + ws: true, + changeOrigin: true }, '/shell': { target: `ws://localhost:${env.PORT || 3002}`, - ws: true + ws: true, + changeOrigin: true + }, + // Codegen specific proxy routes + '/codegen': { + target: `http://localhost:${env.PORT || 3001}`, + changeOrigin: true + }, + '/codegen-ws': { + target: `ws://localhost:${env.PORT || 3001}`, + ws: true, + changeOrigin: true } } }, build: { - outDir: 'dist' + outDir: 'dist', + // Optimize for production + minify: 'terser', + rollupOptions: { + output: { + manualChunks: { + vendor: ['react', 'react-dom'], + codemirror: ['@uiw/react-codemirror'], + terminal: ['xterm', 'xterm-addon-fit'] + } + } + } + }, + // Optimize for development + optimizeDeps: { + include: ['react', 'react-dom', '@uiw/react-codemirror'] } } -}) \ No newline at end of file +})