diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 00000000..0d3578ce
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,152 @@
+# CLAUDE.md
+
+This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
+
+## Architecture Overview
+
+Claudable is a full-stack web application that connects AI coding agents (Claude Code, Cursor CLI, etc.) with a web-based interface for building and deploying Next.js applications. The architecture consists of:
+
+### Frontend (apps/web)
+- **Next.js 14** application with TypeScript
+- **App Router** architecture with pages in `app/` directory
+- **Tailwind CSS** for styling with dark mode support
+- **Framer Motion** for animations
+- **React Context** for global state management (Auth, GlobalSettings)
+- **Component Architecture**: Shared components for project management, modals, and UI elements
+
+### Backend (apps/api)
+- **FastAPI** Python web framework
+- **SQLAlchemy** for database operations with SQLite (local) / PostgreSQL (production)
+- **WebSocket** support for real-time communication
+- **Claude Code SDK** integration for AI agent communication
+- **Modular structure**:
+ - `api/` - REST endpoints
+ - `core/` - Configuration and utilities
+ - `models/` - Database models
+ - `services/` - Business logic
+ - `db/` - Database connections
+
+### Key Integrations
+- **Multiple AI Agents**: Claude Code (primary), Cursor CLI, Codex CLI, Gemini CLI, Qwen Code
+- **Deployment**: Vercel integration for hosting
+- **Version Control**: GitHub integration for repositories
+- **Database**: Supabase for production PostgreSQL
+- **File Management**: Local SQLite database with project files stored in `data/`
+
+## Development Commands
+
+### Primary Development
+```bash
+# Start full development environment (both frontend and backend)
+npm run dev
+
+# Frontend only (Next.js dev server)
+npm run dev:web
+
+# Backend only (FastAPI with uvicorn)
+npm run dev:api
+```
+
+### Database Operations
+```bash
+# Reset database to initial state (WARNING: Deletes all data)
+npm run db:reset
+
+# Create backup of SQLite database
+npm run db:backup
+
+# Restore from backup (manual operation)
+```
+
+### Environment Management
+```bash
+# Setup environment files and Python venv
+npm run setup
+
+# Clean all dependencies and environments
+npm run clean
+
+# Ensure environment is properly configured
+npm run ensure:env
+npm run ensure:venv
+```
+
+### Testing and Quality
+The project uses:
+- Next.js built-in TypeScript checking
+- FastAPI automatic OpenAPI documentation at `http://localhost:8080/docs`
+- Manual testing through web interface
+
+## Project Structure
+
+```
+Claudable/
+├── apps/
+│ ├── web/ # Next.js frontend
+│ │ ├── app/ # App Router pages
+│ │ ├── components/# Reusable React components
+│ │ ├── contexts/ # React Context providers
+│ │ └── types/ # TypeScript type definitions
+│ └── api/ # FastAPI backend
+│ └── app/
+│ ├── api/ # REST endpoints
+│ ├── core/ # Configuration & utilities
+│ ├── models/# Database models
+│ └── services/ # Business logic
+├── scripts/ # Build and development scripts
+├── data/ # SQLite database and project files
+└── assets/ # Static assets and documentation images
+```
+
+## Configuration
+
+### Environment Setup
+Copy `.env.example` to `.env` and configure:
+
+**Required:**
+- `ANTHROPIC_API_KEY` - For Claude Code SDK integration
+
+**Optional:**
+- `API_PORT` - Backend server port (default: 8080)
+- `WEB_PORT` - Frontend server port (default: 3000)
+- `DATABASE_URL` - PostgreSQL connection (for production)
+- Service integrations: GitHub, Vercel, Supabase tokens
+
+### Port Configuration
+The application automatically detects available ports:
+- Frontend: http://localhost:3000 (or next available)
+- Backend: http://localhost:8080 (or next available)
+- API Documentation: http://localhost:8080/docs
+
+## Development Workflow
+
+1. **Project Creation**: Users describe their app idea through the web interface
+2. **AI Agent Selection**: Choose from Claude Code, Cursor CLI, Codex CLI, Gemini CLI, or Qwen Code
+3. **Code Generation**: Selected AI agent generates Next.js application code
+4. **Live Preview**: Real-time preview with hot-reload during development
+5. **Deployment**: One-click deployment to Vercel with GitHub integration
+
+## Key Features Implementation
+
+### Multi-Agent Support
+- Agent detection and installation checking via `scripts/` utilities
+- Per-project agent preference stored in database
+- Model selection per agent (GPT-5, Claude Sonnet 4, etc.)
+
+### Real-time Communication
+- WebSocket connection between frontend and backend
+- Live code generation updates
+- Preview server management
+
+### Project Management
+- SQLite database for local development
+- Project files stored in `data/projects/`
+- Preview servers run on dynamic port allocation
+- Automatic cleanup and resource management
+
+## Development Tips
+
+- Backend API documentation is auto-generated at `/docs` endpoint
+- Frontend builds use Next.js with Turbo for faster compilation
+- Database schema changes require manual migration planning
+- All AI agent communication goes through the Claude Code SDK wrapper
\ No newline at end of file
diff --git a/README.md b/README.md
index a631f4fe..5e1f7201 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
# Claudable
-
+
Powered by OPACTOR
+
- **Powerful Agent Performance**: Leverage the full power of Claude Code and Cursor CLI Agent capabilities with native MCP support
- **Natural Language to Code**: Simply describe what you want to build, and Claudable generates production-ready Next.js code
@@ -33,23 +39,81 @@ How to start? Simply login to Claude Code (or Cursor CLI), start Claudable, and
- **Supabase Database**: Connect production PostgreSQL with authentication ready to use
- **Automated Error Detection**: Detect errors in your app and fix them automatically
-## Technology Stack
-**AI Cooding Agent:**
-- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code/setup)**: Advanced AI coding agent. We strongly recommend you to use Claude Code for the best experience.
+## Demo Examples
+
+### Codex CLI Example
+
+
+### Qwen Code Example
+
+
+## Supported AI Coding Agents
+
+Claudable supports multiple AI coding agents, giving you the flexibility to choose the best tool for your needs:
+
+- **Claude Code** - Anthropic's advanced AI coding agent
+- **Codex CLI** - OpenAI's lightweight coding agent
+- **Cursor CLI** - Powerful multi-model AI agent
+- **Gemini CLI** - Google's open-source AI agent
+- **Qwen Code** - Alibaba's open-source coding CLI
+
+### Claude Code (Recommended)
+**[Claude Code](https://docs.anthropic.com/en/docs/claude-code/setup)** - Anthropic's advanced AI coding agent with Claude Opus 4.1
+- **Features**: Deep codebase awareness, MCP support, Unix philosophy, direct terminal integration
+- **Context**: Native 256K tokens
+- **Pricing**: Included with ChatGPT Plus/Pro/Team/Edu/Enterprise plans
+- **Installation**:
```bash
- # Install
npm install -g @anthropic-ai/claude-code
- # Login
claude # then > /login
```
-- **[Cursor CLI](https://docs.cursor.com/en/cli/overview)**: Intelligent coding agent for complex coding tasks. It's little bit slower than Claude Code, but it's more powerful.
+
+### Codex CLI
+**[Codex CLI](https://github.com/openai/codex)** - OpenAI's lightweight coding agent with GPT-5 support
+- **Features**: High reasoning capabilities, local execution, multiple operating modes (interactive, auto-edit, full-auto)
+- **Context**: Varies by model
+- **Pricing**: Included with ChatGPT Plus/Pro/Business/Edu/Enterprise plans
+- **Installation**:
+ ```bash
+ npm install -g @openai/codex
+ codex # login with ChatGPT account
+ ```
+
+### Cursor CLI
+**[Cursor CLI](https://cursor.com/en/cli)** - Powerful AI agent with access to cutting-edge models
+- **Features**: Multi-model support (Anthropic, OpenAI, Gemini), MCP integration, AGENTS.md support
+- **Context**: Model dependent
+- **Pricing**: Free tier available, Pro plans for advanced features
+- **Installation**:
```bash
- # Install
curl https://cursor.com/install -fsS | bash
- # Login
cursor-agent login
```
+### Gemini CLI
+**[Gemini CLI](https://developers.google.com/gemini-code-assist/docs/gemini-cli)** - Google's open-source AI agent with Gemini 2.5 Pro
+- **Features**: 1M token context window, Google Search grounding, MCP support, extensible architecture
+- **Context**: 1M tokens (with free tier: 60 req/min, 1000 req/day)
+- **Pricing**: Free with Google account, paid tiers for higher limits
+- **Installation**:
+ ```bash
+ npm install -g @google/gemini-cli
+ gemini # follow authentication flow
+ ```
+
+### Qwen Code
+**[Qwen Code](https://github.com/QwenLM/qwen-code)** - Alibaba's open-source CLI for Qwen3-Coder models
+- **Features**: 256K-1M token context, multiple model sizes (0.5B to 480B), Apache 2.0 license
+- **Context**: 256K native, 1M with extrapolation
+- **Pricing**: Completely free and open-source
+- **Installation**:
+ ```bash
+ npm install -g @qwen-code/qwen-code@latest
+ qwen --version
+ ```
+
+## Technology Stack
+
**Database & Deployment:**
- **[Supabase](https://supabase.com/)**: Connect production-ready PostgreSQL database directly to your project.
- **[Vercel](https://vercel.com/)**: Publish your work immediately with one-click deployment
@@ -208,20 +272,22 @@ If you encounter the error: `Error output dangerously skip permissions cannot be
- Anon Key: Public key for client-side
- Service Role Key: Secret key for server-side
-## Design Comparison
-*Same prompt, different results*
-
-### Claudable
-
+## License
-[View Claudable Live Demo →](https://claudable-preview.vercel.app/)
+MIT License.
-### Lovable
-
+## Upcoming Features
+These features are in development and will be opened soon.
+- **New CLI Agents** - Trust us, you're going to LOVE this!
+- **Checkpoints for Chat** - Save and restore conversation/codebase states
+- **Advanced MCP Integration** - Native integration with MCP
+- **Enhanced Agent System** - Subagents, AGENTS.md integration
+- **Website Cloning** - You can start a project from a reference URL.
+- Various bug fixes and community PR merges
-[View Lovable Live Demo →](https://preview--goal-track-studio.lovable.app/)
+We're working hard to deliver the features you've been asking for. Stay tuned!
-## License
+## Star History
-MIT License.
\ No newline at end of file
+[](https://www.star-history.com/#opactorai/Claudable&Date)
diff --git a/apps/api/app/api/assets.py b/apps/api/app/api/assets.py
index ebf14305..a4c07005 100644
--- a/apps/api/app/api/assets.py
+++ b/apps/api/app/api/assets.py
@@ -28,6 +28,27 @@ async def upload_logo(project_id: str, body: LogoRequest, db: Session = Depends(
return {"path": f"assets/logo.png"}
+@router.get("/{project_id}/{filename}")
+async def get_image(project_id: str, filename: str, db: Session = Depends(get_db)):
+ """Get an image file from project assets directory"""
+ from fastapi.responses import FileResponse
+
+ # Verify project exists
+ row = db.get(ProjectModel, project_id)
+ if not row:
+ raise HTTPException(status_code=404, detail="Project not found")
+
+ # Build file path
+ file_path = os.path.join(settings.projects_root, project_id, "assets", filename)
+
+ # Check if file exists
+ if not os.path.exists(file_path):
+ raise HTTPException(status_code=404, detail="Image not found")
+
+ # Return the image file
+ return FileResponse(file_path)
+
+
@router.post("/{project_id}/upload")
async def upload_image(project_id: str, file: UploadFile = File(...), db: Session = Depends(get_db)):
"""Upload an image file to project assets directory"""
diff --git a/apps/api/app/api/chat/act.py b/apps/api/app/api/chat/act.py
index 7ea61cb9..300160a9 100644
--- a/apps/api/app/api/chat/act.py
+++ b/apps/api/app/api/chat/act.py
@@ -16,7 +16,8 @@
from app.models.sessions import Session as ChatSession
from app.models.commits import Commit
from app.models.user_requests import UserRequest
-from app.services.cli.unified_manager import UnifiedCLIManager, CLIType
+from app.services.cli.unified_manager import UnifiedCLIManager
+from app.services.cli.base import CLIType
from app.services.git_ops import commit_all
from app.core.websocket.manager import manager
from app.core.terminal_ui import ui
@@ -27,7 +28,9 @@
class ImageAttachment(BaseModel):
name: str
- base64_data: str
+ # Either base64_data or path must be provided
+ base64_data: Optional[str] = None
+ path: Optional[str] = None # Absolute path to image file
mime_type: str = "image/jpeg"
@@ -156,11 +159,14 @@ async def execute_chat_task(
db=db
)
+ # Qwen Coder does not support images yet; drop them to prevent errors
+ safe_images = [] if cli_preference == CLIType.QWEN else images
+
result = await cli_manager.execute_instruction(
instruction=instruction,
cli_type=cli_preference,
fallback_enabled=project_fallback_enabled,
- images=images,
+ images=safe_images,
model=project_selected_model,
is_initial_prompt=is_initial_prompt
)
@@ -318,11 +324,14 @@ async def execute_act_task(
db=db
)
+ # Qwen Coder does not support images yet; drop them to prevent errors
+ safe_images = [] if cli_preference == CLIType.QWEN else images
+
result = await cli_manager.execute_instruction(
instruction=instruction,
cli_type=cli_preference,
fallback_enabled=project_fallback_enabled,
- images=images,
+ images=safe_images,
model=project_selected_model,
is_initial_prompt=is_initial_prompt
)
@@ -516,18 +525,79 @@ async def run_act(
fallback_enabled = body.fallback_enabled if body.fallback_enabled is not None else project.fallback_enabled
conversation_id = body.conversation_id or str(uuid.uuid4())
- # Save user instruction as message
+ # 🔍 DEBUG: Log incoming request data
+ print(f"📥 ACT Request - Project: {project_id}")
+ print(f"📥 Instruction: {body.instruction[:100]}...")
+ print(f"📥 Images count: {len(body.images)}")
+ print(f"📥 Images data: {body.images}")
+ for i, img in enumerate(body.images):
+ print(f"📥 Image {i+1}: {img}")
+ if hasattr(img, '__dict__'):
+ print(f"📥 Image {i+1} dict: {img.__dict__}")
+
+ # Extract image paths and build attachments for metadata/WS
+ image_paths = []
+ attachments = []
+ import os as _os
+
+ print(f"🔍 Processing {len(body.images)} images...")
+ for i, img in enumerate(body.images):
+ print(f"🔍 Processing image {i+1}: {img}")
+
+ img_dict = img if isinstance(img, dict) else img.__dict__ if hasattr(img, '__dict__') else {}
+ print(f"🔍 Image {i+1} converted to dict: {img_dict}")
+
+ p = img_dict.get('path')
+ n = img_dict.get('name')
+ print(f"🔍 Image {i+1} - path: {p}, name: {n}")
+
+ if p:
+ print(f"🔍 Adding path to image_paths: {p}")
+ image_paths.append(p)
+ try:
+ fname = _os.path.basename(p)
+ print(f"🔍 Processing path: {p}")
+ print(f"🔍 Extracted filename: {fname}")
+ if fname and fname.strip():
+ attachment = {
+ "name": n or fname,
+ "url": f"/api/assets/{project_id}/{fname}"
+ }
+ print(f"🔍 Created attachment: {attachment}")
+ attachments.append(attachment)
+ else:
+ print(f"❌ Failed to extract filename from: {p}")
+ except Exception as e:
+ print(f"❌ Exception processing path {p}: {e}")
+ pass
+ elif n:
+ print(f"🔍 Adding name to image_paths: {n}")
+ image_paths.append(n)
+ else:
+ print(f"❌ Image {i+1} has neither path nor name!")
+
+ print(f"🔍 Final image_paths: {image_paths}")
+ print(f"🔍 Final attachments: {attachments}")
+
+ # Save user instruction as message (with image paths in content for display)
+ message_content = body.instruction
+ if image_paths:
+ image_refs = [f"Image #{i+1} path: {path}" for i, path in enumerate(image_paths)]
+ message_content = f"{body.instruction}\n\n{chr(10).join(image_refs)}"
+
user_message = Message(
id=str(uuid.uuid4()),
project_id=project_id,
role="user",
message_type="chat",
- content=body.instruction,
+ content=message_content,
metadata_json={
"type": "act_instruction",
"cli_preference": cli_preference.value,
"fallback_enabled": fallback_enabled,
- "has_images": len(body.images) > 0
+ "has_images": len(body.images) > 0,
+ "image_paths": image_paths,
+ "attachments": attachments
},
conversation_id=conversation_id,
created_at=datetime.utcnow()
@@ -572,7 +642,7 @@ async def run_act(
"id": user_message.id,
"role": "user",
"message_type": "chat",
- "content": body.instruction,
+ "content": message_content,
"metadata_json": user_message.metadata_json,
"parent_message_id": None,
"session_id": session.id,
@@ -636,18 +706,54 @@ async def run_chat(
fallback_enabled = body.fallback_enabled if body.fallback_enabled is not None else project.fallback_enabled
conversation_id = body.conversation_id or str(uuid.uuid4())
- # Save user instruction as message
+ # Extract image paths and build attachments for metadata/WS
+ image_paths = []
+ attachments = []
+ import os as _os2
+ for img in body.images:
+ img_dict = img if isinstance(img, dict) else img.__dict__ if hasattr(img, '__dict__') else {}
+ p = img_dict.get('path')
+ n = img_dict.get('name')
+ if p:
+ image_paths.append(p)
+ try:
+ fname = _os2.path.basename(p)
+ print(f"🔍 [CHAT] Processing path: {p}")
+ print(f"🔍 [CHAT] Extracted filename: {fname}")
+ if fname and fname.strip():
+ attachment = {
+ "name": n or fname,
+ "url": f"/api/assets/{project_id}/{fname}"
+ }
+ print(f"🔍 [CHAT] Created attachment: {attachment}")
+ attachments.append(attachment)
+ else:
+ print(f"❌ [CHAT] Failed to extract filename from: {p}")
+ except Exception as e:
+ print(f"❌ [CHAT] Exception processing path {p}: {e}")
+ pass
+ elif n:
+ image_paths.append(n)
+
+ # Save user instruction as message (with image paths in content for display)
+ message_content = body.instruction
+ if image_paths:
+ image_refs = [f"Image #{i+1} path: {path}" for i, path in enumerate(image_paths)]
+ message_content = f"{body.instruction}\n\n{chr(10).join(image_refs)}"
+
user_message = Message(
id=str(uuid.uuid4()),
project_id=project_id,
role="user",
message_type="chat",
- content=body.instruction,
+ content=message_content,
metadata_json={
"type": "chat_instruction",
"cli_preference": cli_preference.value,
"fallback_enabled": fallback_enabled,
- "has_images": len(body.images) > 0
+ "has_images": len(body.images) > 0,
+ "image_paths": image_paths,
+ "attachments": attachments
},
conversation_id=conversation_id,
created_at=datetime.utcnow()
@@ -679,7 +785,7 @@ async def run_chat(
"id": user_message.id,
"role": "user",
"message_type": "chat",
- "content": body.instruction,
+ "content": message_content,
"metadata_json": user_message.metadata_json,
"parent_message_id": None,
"session_id": session.id,
@@ -719,4 +825,4 @@ async def run_chat(
conversation_id=conversation_id,
status="running",
message="Chat execution started"
- )
\ No newline at end of file
+ )
diff --git a/apps/api/app/api/chat/cli_preferences.py b/apps/api/app/api/chat/cli_preferences.py
index 2d160d32..6a3ff4b5 100644
--- a/apps/api/app/api/chat/cli_preferences.py
+++ b/apps/api/app/api/chat/cli_preferences.py
@@ -9,7 +9,8 @@
from app.api.deps import get_db
from app.models.projects import Project
-from app.services.cli import UnifiedCLIManager, CLIType
+from app.services.cli import UnifiedCLIManager
+from app.services.cli.base import CLIType
router = APIRouter()
@@ -36,6 +37,9 @@ class CLIStatusResponse(BaseModel):
class AllCLIStatusResponse(BaseModel):
claude: CLIStatusResponse
cursor: CLIStatusResponse
+ codex: CLIStatusResponse
+ qwen: CLIStatusResponse
+ gemini: CLIStatusResponse
preferred_cli: str
@@ -164,28 +168,37 @@ async def get_all_cli_status(project_id: str, db: Session = Depends(get_db)):
if not project:
raise HTTPException(status_code=404, detail="Project not found")
- # For now, return mock status data to avoid CLI manager issues
preferred_cli = getattr(project, 'preferred_cli', 'claude')
-
- # Create mock status responses
- claude_status = CLIStatusResponse(
- cli_type="claude",
- available=True,
- configured=True,
- error=None,
- models=["claude-3.5-sonnet", "claude-3-opus"]
- )
-
- cursor_status = CLIStatusResponse(
- cli_type="cursor",
- available=False,
- configured=False,
- error="Not configured",
- models=[]
+
+ # Build real status for each CLI using UnifiedCLIManager
+ manager = UnifiedCLIManager(
+ project_id=project.id,
+ project_path=project.repo_path,
+ session_id="status_check",
+ conversation_id="status_check",
+ db=db,
)
-
+
+ def to_resp(cli_key: str, status: Dict[str, Any]) -> CLIStatusResponse:
+ return CLIStatusResponse(
+ cli_type=cli_key,
+ available=status.get("available", False),
+ configured=status.get("configured", False),
+ error=status.get("error"),
+ models=status.get("models"),
+ )
+
+ claude_status = await manager.check_cli_status(CLIType.CLAUDE)
+ cursor_status = await manager.check_cli_status(CLIType.CURSOR)
+ codex_status = await manager.check_cli_status(CLIType.CODEX)
+ qwen_status = await manager.check_cli_status(CLIType.QWEN)
+ gemini_status = await manager.check_cli_status(CLIType.GEMINI)
+
return AllCLIStatusResponse(
- claude=claude_status,
- cursor=cursor_status,
- preferred_cli=preferred_cli
- )
\ No newline at end of file
+ claude=to_resp("claude", claude_status),
+ cursor=to_resp("cursor", cursor_status),
+ codex=to_resp("codex", codex_status),
+ qwen=to_resp("qwen", qwen_status),
+ gemini=to_resp("gemini", gemini_status),
+ preferred_cli=preferred_cli,
+ )
diff --git a/apps/api/app/api/github.py b/apps/api/app/api/github.py
index 8c70a81b..129c2491 100644
--- a/apps/api/app/api/github.py
+++ b/apps/api/app/api/github.py
@@ -327,8 +327,9 @@ async def push_github_repository(project_id: str, db: Session = Depends(get_db))
if not repo_path or not os.path.exists(repo_path):
raise HTTPException(status_code=500, detail="Local repository path not found")
- # Branch
- default_branch = connection.service_data.get("default_branch", "main")
+ # Branch: GitHub may return null for default_branch on empty repos.
+ # Normalize to 'main' and persist after first successful push.
+ default_branch = connection.service_data.get("default_branch") or "main"
# Commit any pending changes (optional harmless)
commit_all(repo_path, "Publish from Lovable UI")
@@ -348,6 +349,9 @@ async def push_github_repository(project_id: str, db: Session = Depends(get_db))
"last_push_at": datetime.utcnow().isoformat() + "Z",
"last_pushed_branch": default_branch,
})
+ # Ensure default_branch is set after first push
+ if not data.get("default_branch"):
+ data["default_branch"] = default_branch
svc.service_data = data
db.commit()
except Exception as e:
@@ -370,4 +374,4 @@ async def push_github_repository(project_id: str, db: Session = Depends(get_db))
logger = logging.getLogger(__name__)
logger.warning(f"Failed updating Vercel connection after push: {e}")
- return GitPushResponse(success=True, message="Pushed to GitHub", branch=default_branch)
\ No newline at end of file
+ return GitPushResponse(success=True, message="Pushed to GitHub", branch=default_branch)
diff --git a/apps/api/app/api/projects/crud.py b/apps/api/app/api/projects/crud.py
index 78e70708..2878a09a 100644
--- a/apps/api/app/api/projects/crud.py
+++ b/apps/api/app/api/projects/crud.py
@@ -152,29 +152,29 @@ async def init_project_task():
async def install_dependencies_background(project_id: str, project_path: str):
- """Install dependencies in background"""
+ """Install dependencies in background (npm)"""
try:
import subprocess
import os
-
- # Check if package.json exists
+
package_json_path = os.path.join(project_path, "package.json")
if os.path.exists(package_json_path):
print(f"Installing dependencies for project {project_id}...")
-
- # Run npm install in background
+
process = await asyncio.create_subprocess_exec(
"npm", "install",
cwd=project_path,
stdout=asyncio.subprocess.PIPE,
- stderr=asyncio.subprocess.PIPE
+ stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await process.communicate()
-
+
if process.returncode == 0:
print(f"Dependencies installed successfully for project {project_id}")
else:
- print(f"Failed to install dependencies for project {project_id}: {stderr.decode()}")
+ print(
+ f"Failed to install dependencies for project {project_id}: {stderr.decode()}"
+ )
except Exception as e:
print(f"Error installing dependencies: {e}")
@@ -303,7 +303,9 @@ async def get_project(project_id: str, db: Session = Depends(get_db)) -> Project
features=ai_info.get('features'),
tech_stack=ai_info.get('tech_stack'),
ai_generated=ai_info.get('ai_generated', False),
- initial_prompt=project.initial_prompt
+ initial_prompt=project.initial_prompt,
+ preferred_cli=project.preferred_cli,
+ selected_model=project.selected_model
)
except HTTPException:
raise
@@ -484,4 +486,4 @@ async def delete_project(project_id: str, db: Session = Depends(get_db)):
print(f"❌ Error cleaning up project files for {project_id}: {e}")
# Don't fail the whole operation if file cleanup fails
- return {"message": f"Project {project_id} deleted successfully"}
\ No newline at end of file
+ return {"message": f"Project {project_id} deleted successfully"}
diff --git a/apps/api/app/api/settings.py b/apps/api/app/api/settings.py
index 248b0eed..25d8e1fd 100644
--- a/apps/api/app/api/settings.py
+++ b/apps/api/app/api/settings.py
@@ -4,7 +4,8 @@
from typing import Dict, Any
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
-from app.services.cli.unified_manager import CLIType, CursorAgentCLI
+from app.services.cli.unified_manager import CursorAgentCLI
+from app.services.cli.base import CLIType
router = APIRouter(prefix="/api/settings", tags=["settings"])
@@ -83,17 +84,23 @@ async def get_cli_status() -> Dict[str, Any]:
results = {}
# 새로운 UnifiedCLIManager의 CLI 인스턴스 사용
- from app.services.cli.unified_manager import ClaudeCodeCLI, CursorAgentCLI
+ from app.services.cli.unified_manager import ClaudeCodeCLI, CursorAgentCLI, CodexCLI, QwenCLI, GeminiCLI
cli_instances = {
"claude": ClaudeCodeCLI(),
- "cursor": CursorAgentCLI()
+ "cursor": CursorAgentCLI(),
+ "codex": CodexCLI(),
+ "qwen": QwenCLI(),
+ "gemini": GeminiCLI()
}
# 모든 CLI를 병렬로 확인
tasks = []
for cli_id, cli_instance in cli_instances.items():
+ print(f"[DEBUG] Setting up check for CLI: {cli_id}")
async def check_cli(cli_id, cli_instance):
+ print(f"[DEBUG] Checking CLI: {cli_id}")
status = await cli_instance.check_availability()
+ print(f"[DEBUG] CLI {cli_id} status: {status}")
return cli_id, status
tasks.append(check_cli(cli_id, cli_instance))
@@ -143,4 +150,4 @@ async def update_global_settings(settings: GlobalSettingsModel) -> Dict[str, Any
"cli_settings": settings.cli_settings
})
- return {"success": True, "settings": GLOBAL_SETTINGS}
\ No newline at end of file
+ return {"success": True, "settings": GLOBAL_SETTINGS}
diff --git a/apps/api/app/api/vercel.py b/apps/api/app/api/vercel.py
index c2e12ad5..ba16c17f 100644
--- a/apps/api/app/api/vercel.py
+++ b/apps/api/app/api/vercel.py
@@ -271,11 +271,19 @@ async def deploy_to_vercel(
# Initialize Vercel service
vercel_service = VercelService(vercel_token)
+ # Resolve branch: prefer GitHub connection's default/last pushed branch
+ preferred_branch = (
+ github_connection.service_data.get("last_pushed_branch")
+ or github_connection.service_data.get("default_branch")
+ or request.branch
+ or "main"
+ )
+
# Create deployment
deployment_result = await vercel_service.create_deployment(
project_name=vercel_data.get("project_name"),
github_repo_id=github_repo_id,
- branch=request.branch,
+ branch=preferred_branch,
framework=vercel_data.get("framework", "nextjs")
)
@@ -467,4 +475,4 @@ async def get_active_monitoring():
return {"active_projects": active_projects}
except Exception as e:
logger.error(f"Failed to get active monitoring: {e}")
- raise HTTPException(status_code=500, detail=str(e))
\ No newline at end of file
+ raise HTTPException(status_code=500, detail=str(e))
diff --git a/apps/api/app/db/migrations.py b/apps/api/app/db/migrations.py
new file mode 100644
index 00000000..cfe1574e
--- /dev/null
+++ b/apps/api/app/db/migrations.py
@@ -0,0 +1,24 @@
+"""Database migrations module for SQLite."""
+
+import logging
+from pathlib import Path
+from typing import Optional
+
+logger = logging.getLogger(__name__)
+
+
+def run_sqlite_migrations(db_path: Optional[str] = None) -> None:
+ """
+ Run SQLite database migrations.
+
+ Args:
+ db_path: Path to the SQLite database file
+ """
+ if db_path:
+ logger.info(f"Running migrations for SQLite database at: {db_path}")
+ else:
+ logger.info("Running migrations for in-memory SQLite database")
+
+ # Add migration logic here as needed
+ # For now, this is a placeholder that ensures the module exists
+ pass
\ No newline at end of file
diff --git a/apps/api/app/main.py b/apps/api/app/main.py
index 4f7d22fe..3c182a7a 100644
--- a/apps/api/app/main.py
+++ b/apps/api/app/main.py
@@ -18,6 +18,7 @@
from app.db.base import Base
import app.models # noqa: F401 ensures models are imported for metadata
from app.db.session import engine
+from app.db.migrations import run_sqlite_migrations
import os
configure_logging()
@@ -79,6 +80,8 @@ def on_startup() -> None:
inspector = inspect(engine)
Base.metadata.create_all(bind=engine)
ui.success("Database initialization complete")
+ # Run lightweight SQLite migrations for additive changes
+ run_sqlite_migrations(engine)
# Show available endpoints
ui.info("API server ready")
diff --git a/apps/api/app/prompt/system-prompt.md b/apps/api/app/prompt/system-prompt.md
index 4469bb9e..0a48c930 100644
--- a/apps/api/app/prompt/system-prompt.md
+++ b/apps/api/app/prompt/system-prompt.md
@@ -1,4 +1,4 @@
-You are CLovable, an advanced AI coding assistant specialized in building modern fullstack web applications. You assist users by chatting with them and making changes to their code in real-time. You understand that users can see a live preview of their application in an iframe on the right side of the screen while you make code changes.
+You are Claudable, an advanced AI coding assistant specialized in building modern fullstack web applications. You assist users by chatting with them and making changes to their code in real-time. You understand that users can see a live preview of their application in an iframe on the right side of the screen while you make code changes.
## Core Identity
@@ -12,10 +12,31 @@ You are an expert fullstack developer with deep knowledge of the modern web deve
Not every interaction requires code changes - you're happy to discuss architecture, explain concepts, debug issues, or provide guidance without modifying the codebase. When code changes are needed, you make efficient and effective updates while following modern fullstack best practices for maintainability, security, and performance.
+When starting a new task:
+1. Run ONE command: `ls -la`
+2. IMMEDIATELY start working with the correct paths
+CRITICAL: File paths in Next.js projects:
+- If you see `app/` directory: use `app/page.tsx` (no leading slash)
+- If you see `src/` directory: use `src/app/page.tsx` (no leading slash)
+- NEVER use `/app/page.tsx` or `./app/page.tsx` - these are wrong!
+
+For the FIRST interaction on a new project:
+- Take time to understand what the user wants to build
+- Consider what existing beautiful designs you can draw inspiration from
+- List the features you'll implement in the first version (don't do too much, but make it look good)
+- List possible colors, gradients, animations, fonts and styles you'll use
+- When the user asks for a specific design, follow it to the letter
+- Consider editing tailwind.config.ts and index.css first if custom styles are needed
+- Focus on creating a beautiful, working first impression - go above and beyond
+- The MOST IMPORTANT thing is that the app is beautiful and works without build errors
+- Take your time to wow the user with a really beautiful and well-coded app
+
## Product Principles (MVP approach)
- Implement only the specific functionality the user explicitly requests
- Avoid adding extra features, optimizations, or enhancements unless specifically asked
- Keep implementations simple and focused on the core requirement
+- Avoid unnecessary abstraction - write code in the same file when it makes sense
+- Don't over-componentize - larger single-file components are often more maintainable
## Technical Stack Guidelines
@@ -26,6 +47,15 @@ Not every interaction requires code changes - you're happy to discuss architectu
- Use "use client" directive only when client-side interactivity is required
- Implement proper metadata API for SEO optimization
- Follow Next.js 15 caching strategies and revalidation patterns
+- Use STABLE versions of dependencies - avoid beta/alpha/experimental syntax:
+ - Tailwind CSS: Use v3 stable with standard @tailwind directives
+ - Avoid experimental features unless explicitly requested
+ - Ensure all syntax is compatible with production environments
+- When using external images with next/image component, ALWAYS configure the domain in next.config.mjs:
+ - Add image domains to `images.remotePatterns` with protocol, hostname, port, and pathname
+ - For placeholder images (via.placeholder.com, picsum.photos, etc.), configure them properly
+ - Use standard