Welcome to Your ${style === 'modern' ? 'Modern' : style === 'playful' ? 'Playful' : style === 'professional' ? 'Professional' : 'Artistic'} Website
+Reimagined from ${url}
+ Get Started +Fast
+Lightning-fast performance optimized for modern web standards.
+Responsive
+Looks great on all devices, from mobile to desktop.
+Beautiful
+Stunning design that captures attention and drives engagement.
+
+
+## Setup
+
+1. **Clone & Install**
+```bash
+git clone https://github.com/firecrawl/open-lovable.git
+cd open-lovable
+pnpm install # or npm install / yarn install
+```
+
+2. **Add `.env.local`**
+
+```env
+# =================================================================
+# REQUIRED
+# =================================================================
+FIRECRAWL_API_KEY=your_firecrawl_api_key # https://firecrawl.dev
+
+# =================================================================
+# AI PROVIDER - Choose your LLM
+# =================================================================
+GEMINI_API_KEY=your_gemini_api_key # https://aistudio.google.com/app/apikey
+ANTHROPIC_API_KEY=your_anthropic_api_key # https://console.anthropic.com
+OPENAI_API_KEY=your_openai_api_key # https://platform.openai.com
+GROQ_API_KEY=your_groq_api_key # https://console.groq.com
+
+# =================================================================
+# FAST APPLY (Optional - for faster edits)
+# =================================================================
+MORPH_API_KEY=your_morphllm_api_key # https://morphllm.com/dashboard
+
+# =================================================================
+# SANDBOX PROVIDER - Choose ONE: Vercel (default) or E2B
+# =================================================================
+SANDBOX_PROVIDER=vercel # or 'e2b'
+
+# Option 1: Vercel Sandbox (default)
+# Choose one authentication method:
+
+# Method A: OIDC Token (recommended for development)
+# Run `vercel link` then `vercel env pull` to get VERCEL_OIDC_TOKEN automatically
+VERCEL_OIDC_TOKEN=auto_generated_by_vercel_env_pull
+
+# Method B: Personal Access Token (for production or when OIDC unavailable)
+# VERCEL_TEAM_ID=team_xxxxxxxxx # Your Vercel team ID
+# VERCEL_PROJECT_ID=prj_xxxxxxxxx # Your Vercel project ID
+# VERCEL_TOKEN=vercel_xxxxxxxxxxxx # Personal access token from Vercel dashboard
+
+# Option 2: E2B Sandbox
+# E2B_API_KEY=your_e2b_api_key # https://e2b.dev
+```
+
+3. **Run**
+```bash
+pnpm dev # or npm run dev / yarn dev
+```
+
+Open [http://localhost:3000](http://localhost:3000)
+
+## License
+
+MIT
\ No newline at end of file
diff --git a/README.md b/README.md
index 4ea4ce7faabc8f10e661ded579103aa881f26d6e..222582cc1ff3646fc6ec6acd2473a7bd794db934 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,10 @@
---
-title: OpenOperator
-emoji: 🤖
-colorFrom: blue
-colorTo: purple
+# Trigger rebuild
+title: Open Lovable
+emoji: ❤️
+colorFrom: pink
+colorTo: blue
sdk: docker
-app_port: 7860
+app_port: 3000
pinned: false
-short_description: Agent-Zero powered operator with REST API
---
-
-Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
diff --git a/api_app.py b/api_app.py
deleted file mode 100644
index a9dd1790eae0113af5c28b06c5851451c2b0f47e..0000000000000000000000000000000000000000
--- a/api_app.py
+++ /dev/null
@@ -1,123 +0,0 @@
-import sys
-import os
-import secrets
-import hmac
-import hashlib
-import time
-from fastapi import FastAPI, HTTPException, Request
-from fastapi.responses import HTMLResponse, Response
-from fastapi.staticfiles import StaticFiles
-from pydantic import BaseModel
-from typing import Optional, List
-
-sys.path.append(os.path.dirname(os.path.abspath(__file__)))
-
-from agent import AgentContext, AgentContextType, UserMessage
-import initialize
-from python.helpers import runtime, dotenv, files, git
-from python.helpers.print_style import PrintStyle
-
-app = FastAPI(title="Skilled-Agent API")
-
-# CSRF Logic from run_ui.py
-CSRF_SECRET = secrets.token_bytes(32)
-TOKEN_TTL = 3600
-
-def generate_csrf_token():
- nonce = secrets.token_hex(16)
- timestamp = str(int(time.time()))
- data = f"{nonce}:{timestamp}"
- sig = hmac.new(CSRF_SECRET, data.encode(), hashlib.sha256).hexdigest()
- return f"{data}.{sig}"
-
-class ChatRequest(BaseModel):
- message: str
- chat_id: Optional[str] = None
- attachments: Optional[List[str]] = None
-
-class ChatResponse(BaseModel):
- response: str
- chat_id: str
-
-@app.on_event("startup")
-async def startup_event():
- PrintStyle().print("Initializing Skilled-Agent API...")
- runtime.initialize()
- dotenv.load_dotenv()
-
- # Run migrations if necessary
- if hasattr(initialize, "initialize_migration"):
- initialize.initialize_migration()
-
- # Initialize chats
- init_chats = initialize.initialize_chats()
- init_chats.result_sync()
-
- # Initialize MCP
- initialize.initialize_mcp()
-
- # Start job loop
- initialize.initialize_job_loop()
-
- # Preload
- initialize.initialize_preload()
-
- PrintStyle().print("Skilled-Agent API started.")
-
-@app.get("/", response_class=HTMLResponse)
-async def serve_index():
- PrintStyle().print("Serving index.html")
- gitinfo = None
- try:
- gitinfo = git.get_git_info()
- except Exception as e:
- gitinfo = {"version": "unknown", "commit_time": "unknown"}
-
- index_content = files.read_file("webui/index.html")
- index_content = files.replace_placeholders_text(
- _content=index_content,
- version_no=gitinfo["version"],
- version_time=gitinfo["commit_time"]
- )
-
- csrf_token = generate_csrf_token()
- runtime_id = runtime.get_runtime_id()
- meta_tags = f'''
- '''
- index_content = index_content.replace("", f"{meta_tags}")
- return index_content
-
-@app.post("/chat", response_model=ChatResponse)
-async def chat(request: ChatRequest):
- context = None
- if request.chat_id:
- context = AgentContext.get(request.chat_id)
- if not context:
- raise HTTPException(status_code=404, detail=f"Chat session {request.chat_id} not found")
- else:
- config = initialize.initialize_agent()
- context = AgentContext(config=config, type=AgentContextType.BACKGROUND)
-
- if not request.message:
- raise HTTPException(status_code=400, detail="Message is required")
-
- try:
- PrintStyle().print(f"Processing message for chat {context.id}...")
- task = context.communicate(
- UserMessage(
- message=request.message,
- attachments=request.attachments or []
- )
- )
- result = await task.result()
- return ChatResponse(response=result, chat_id=context.id)
- except Exception as e:
- PrintStyle().error(f"Error in chat: {e}")
- raise HTTPException(status_code=500, detail=str(e))
-
-@app.get("/health")
-async def health():
- return {"status": "healthy"}
-
-# Mount static files
-app.mount("/", StaticFiles(directory="webui"), name="static")
diff --git a/app/api/analyze-edit-intent/route.ts b/app/api/analyze-edit-intent/route.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b7abd69271272b5c765db79d9ac2582d8ce217e5
--- /dev/null
+++ b/app/api/analyze-edit-intent/route.ts
@@ -0,0 +1,135 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { generateObject } from 'ai';
+import { z } from 'zod';
+import getProviderForModel from '@/lib/ai/provider-manager';
+
+// Schema for the AI's search plan - not file selection!
+const searchPlanSchema = z.object({
+ editType: z.enum([
+ 'UPDATE_COMPONENT',
+ 'ADD_FEATURE',
+ 'FIX_ISSUE',
+ 'UPDATE_STYLE',
+ 'REFACTOR',
+ 'ADD_DEPENDENCY',
+ 'REMOVE_ELEMENT'
+ ]).describe('The type of edit being requested'),
+ reasoning: z.string().describe('Explanation of the search strategy'),
+ searchTerms: z.array(z.string()).describe('Specific text to search for (case-insensitive). Be VERY specific - exact button text, class names, etc.'),
+ regexPatterns: z.array(z.string()).optional().describe('Regex patterns for finding code structures (e.g., "className=[\"\\\'].*header.*[\"\\]")'),
+ fileTypesToSearch: z.array(z.string()).default(['.jsx', '.tsx', '.js', '.ts']).describe('File extensions to search'),
+ expectedMatches: z.number().min(1).max(10).default(1).describe('Expected number of matches (helps validate search worked)'),
+ fallbackSearch: z.object({
+ terms: z.array(z.string()),
+ patterns: z.array(z.string()).optional()
+ }).optional().describe('Backup search if primary fails')
+});
+
+export async function POST(request: NextRequest) {
+ try {
+ const { prompt, manifest } = await request.json();
+
+ console.log('[analyze-edit-intent] Request received');
+ console.log('[analyze-edit-intent] Prompt:', prompt);
+ console.log('[analyze-edit-intent] Manifest files count:', manifest?.files ? Object.keys(manifest.files).length : 0);
+
+ if (!prompt || !manifest) {
+ return NextResponse.json({
+ error: 'prompt and manifest are required'
+ }, { status: 400 });
+ }
+
+ const validFiles = Object.entries(manifest.files as Record
+