Add v2 sandbox implementation with new API routes and sandbox library
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Command } from 'commander';
|
||||
import inquirer from 'inquirer';
|
||||
import chalk from 'chalk';
|
||||
import ora from 'ora';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { installer } from './lib/installer.js';
|
||||
import { getPrompts } from './lib/prompts.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('create-open-lovable')
|
||||
.description('Create a new Open Lovable project with your choice of sandbox provider')
|
||||
.version('1.0.0')
|
||||
.option('-s, --sandbox <provider>', 'Sandbox provider (e2b or vercel)')
|
||||
.option('-n, --name <name>', 'Project name')
|
||||
.option('-p, --path <path>', 'Installation path (defaults to current directory)')
|
||||
.option('--skip-install', 'Skip npm install')
|
||||
.option('--dry-run', 'Run without making changes')
|
||||
.parse(process.argv);
|
||||
|
||||
const options = program.opts();
|
||||
|
||||
async function main() {
|
||||
console.log(chalk.cyan('\n🚀 Welcome to Open Lovable Setup!\n'));
|
||||
|
||||
let config = {
|
||||
sandbox: options.sandbox,
|
||||
name: options.name || 'my-open-lovable',
|
||||
path: options.path || process.cwd(),
|
||||
skipInstall: options.skipInstall || false,
|
||||
dryRun: options.dryRun || false
|
||||
};
|
||||
|
||||
// Interactive mode if sandbox not specified
|
||||
if (!config.sandbox) {
|
||||
const prompts = getPrompts(config);
|
||||
const answers = await inquirer.prompt(prompts);
|
||||
config = { ...config, ...answers };
|
||||
}
|
||||
|
||||
// Validate sandbox provider
|
||||
if (!['e2b', 'vercel'].includes(config.sandbox)) {
|
||||
console.error(chalk.red(`\n❌ Invalid sandbox provider: ${config.sandbox}`));
|
||||
console.log(chalk.yellow('Valid options: e2b, vercel\n'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(chalk.gray('\nConfiguration:'));
|
||||
console.log(chalk.gray(` Project: ${config.name}`));
|
||||
console.log(chalk.gray(` Sandbox: ${config.sandbox}`));
|
||||
console.log(chalk.gray(` Path: ${path.resolve(config.path, config.name)}\n`));
|
||||
|
||||
if (config.dryRun) {
|
||||
console.log(chalk.yellow('🔍 Dry run mode - no files will be created\n'));
|
||||
}
|
||||
|
||||
const spinner = ora('Creating project...').start();
|
||||
|
||||
try {
|
||||
await installer({
|
||||
...config,
|
||||
templatesDir: path.join(__dirname, 'templates')
|
||||
});
|
||||
|
||||
spinner.succeed('Project created successfully!');
|
||||
|
||||
console.log(chalk.green('\n✅ Setup complete!\n'));
|
||||
console.log(chalk.white('Next steps:'));
|
||||
console.log(chalk.gray(` 1. cd ${config.name}`));
|
||||
console.log(chalk.gray(` 2. Copy .env.example to .env and add your API keys`));
|
||||
console.log(chalk.gray(` 3. npm run dev`));
|
||||
console.log(chalk.gray('\nHappy coding! 🎉\n'));
|
||||
|
||||
} catch (error) {
|
||||
spinner.fail('Setup failed');
|
||||
console.error(chalk.red('\n❌ Error:'), error.message);
|
||||
if (error.stack && process.env.DEBUG) {
|
||||
console.error(chalk.gray(error.stack));
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(error => {
|
||||
console.error(chalk.red('Unexpected error:'), error);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,261 @@
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
import chalk from 'chalk';
|
||||
import inquirer from 'inquirer';
|
||||
import { getEnvPrompts } from './prompts.js';
|
||||
|
||||
export async function installer(config) {
|
||||
const { name, sandbox, path: installPath, skipInstall, dryRun, templatesDir } = config;
|
||||
const projectPath = path.join(installPath, name);
|
||||
|
||||
if (dryRun) {
|
||||
console.log(chalk.blue('\n📋 Dry run - would perform these actions:'));
|
||||
console.log(chalk.gray(` - Create directory: ${projectPath}`));
|
||||
console.log(chalk.gray(` - Copy base template files`));
|
||||
console.log(chalk.gray(` - Copy ${sandbox}-specific files`));
|
||||
console.log(chalk.gray(` - Create .env file`));
|
||||
if (!skipInstall) {
|
||||
console.log(chalk.gray(` - Run npm install`));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if directory exists
|
||||
if (await fs.pathExists(projectPath)) {
|
||||
const { overwrite } = await inquirer.prompt([{
|
||||
type: 'confirm',
|
||||
name: 'overwrite',
|
||||
message: `Directory ${name} already exists. Overwrite?`,
|
||||
default: false
|
||||
}]);
|
||||
|
||||
if (!overwrite) {
|
||||
throw new Error('Installation cancelled');
|
||||
}
|
||||
await fs.remove(projectPath);
|
||||
}
|
||||
|
||||
// Create project directory
|
||||
await fs.ensureDir(projectPath);
|
||||
|
||||
// Copy base template (shared files)
|
||||
const baseTemplatePath = path.join(templatesDir, 'base');
|
||||
if (await fs.pathExists(baseTemplatePath)) {
|
||||
await copyTemplate(baseTemplatePath, projectPath);
|
||||
} else {
|
||||
// If no base template exists yet, copy from the main project
|
||||
await copyMainProject(path.dirname(templatesDir), projectPath, sandbox);
|
||||
}
|
||||
|
||||
// Copy provider-specific template
|
||||
const providerTemplatePath = path.join(templatesDir, sandbox);
|
||||
if (await fs.pathExists(providerTemplatePath)) {
|
||||
await copyTemplate(providerTemplatePath, projectPath);
|
||||
}
|
||||
|
||||
// Configure environment variables
|
||||
if (config.configureEnv) {
|
||||
const envAnswers = await inquirer.prompt(getEnvPrompts(sandbox));
|
||||
await createEnvFile(projectPath, sandbox, envAnswers);
|
||||
} else {
|
||||
// Create .env.example copy
|
||||
await createEnvExample(projectPath, sandbox);
|
||||
}
|
||||
|
||||
// Update package.json with project name
|
||||
await updatePackageJson(projectPath, name);
|
||||
|
||||
// Update configuration to use the selected sandbox provider
|
||||
await updateAppConfig(projectPath, sandbox);
|
||||
|
||||
// Install dependencies
|
||||
if (!skipInstall) {
|
||||
console.log(chalk.cyan('\n📦 Installing dependencies...'));
|
||||
execSync('npm install', {
|
||||
cwd: projectPath,
|
||||
stdio: 'inherit'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function copyTemplate(src, dest) {
|
||||
const files = await fs.readdir(src);
|
||||
|
||||
for (const file of files) {
|
||||
const srcPath = path.join(src, file);
|
||||
const destPath = path.join(dest, file);
|
||||
|
||||
const stat = await fs.stat(srcPath);
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
await fs.ensureDir(destPath);
|
||||
await copyTemplate(srcPath, destPath);
|
||||
} else {
|
||||
await fs.copy(srcPath, destPath, { overwrite: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function copyMainProject(mainProjectPath, projectPath, sandbox) {
|
||||
// Copy essential directories and files from the main project
|
||||
const itemsToCopy = [
|
||||
'app',
|
||||
'components',
|
||||
'config',
|
||||
'lib',
|
||||
'types',
|
||||
'public',
|
||||
'styles',
|
||||
'.eslintrc.json',
|
||||
'.gitignore',
|
||||
'next.config.js',
|
||||
'package.json',
|
||||
'tailwind.config.ts',
|
||||
'tsconfig.json',
|
||||
'postcss.config.mjs'
|
||||
];
|
||||
|
||||
for (const item of itemsToCopy) {
|
||||
const srcPath = path.join(mainProjectPath, '..', item);
|
||||
const destPath = path.join(projectPath, item);
|
||||
|
||||
if (await fs.pathExists(srcPath)) {
|
||||
await fs.copy(srcPath, destPath, {
|
||||
overwrite: true,
|
||||
filter: (src) => {
|
||||
// Skip node_modules and .next
|
||||
if (src.includes('node_modules') || src.includes('.next')) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function createEnvFile(projectPath, sandbox, answers) {
|
||||
let envContent = '# Open Lovable Configuration\n\n';
|
||||
|
||||
// Sandbox provider
|
||||
envContent += `# Sandbox Provider\n`;
|
||||
envContent += `SANDBOX_PROVIDER=${sandbox}\n\n`;
|
||||
|
||||
// Required keys
|
||||
envContent += `# REQUIRED - Web scraping for cloning websites\n`;
|
||||
envContent += `FIRECRAWL_API_KEY=${answers.firecrawlApiKey || 'your_firecrawl_api_key_here'}\n\n`;
|
||||
|
||||
if (sandbox === 'e2b') {
|
||||
envContent += `# REQUIRED - E2B Sandboxes\n`;
|
||||
envContent += `E2B_API_KEY=${answers.e2bApiKey || 'your_e2b_api_key_here'}\n\n`;
|
||||
} else if (sandbox === 'vercel') {
|
||||
envContent += `# REQUIRED - Vercel Sandboxes\n`;
|
||||
if (answers.vercelAuthMethod === 'oidc') {
|
||||
envContent += `# Using OIDC authentication (automatic in Vercel environment)\n`;
|
||||
} else {
|
||||
envContent += `VERCEL_TEAM_ID=${answers.vercelTeamId || 'your_team_id'}\n`;
|
||||
envContent += `VERCEL_PROJECT_ID=${answers.vercelProjectId || 'your_project_id'}\n`;
|
||||
envContent += `VERCEL_TOKEN=${answers.vercelToken || 'your_access_token'}\n`;
|
||||
}
|
||||
envContent += '\n';
|
||||
}
|
||||
|
||||
// Optional AI provider keys
|
||||
envContent += `# OPTIONAL - AI Providers\n`;
|
||||
|
||||
if (answers.anthropicApiKey) {
|
||||
envContent += `ANTHROPIC_API_KEY=${answers.anthropicApiKey}\n`;
|
||||
} else {
|
||||
envContent += `# ANTHROPIC_API_KEY=your_anthropic_api_key_here\n`;
|
||||
}
|
||||
|
||||
if (answers.openaiApiKey) {
|
||||
envContent += `OPENAI_API_KEY=${answers.openaiApiKey}\n`;
|
||||
} else {
|
||||
envContent += `# OPENAI_API_KEY=your_openai_api_key_here\n`;
|
||||
}
|
||||
|
||||
if (answers.geminiApiKey) {
|
||||
envContent += `GEMINI_API_KEY=${answers.geminiApiKey}\n`;
|
||||
} else {
|
||||
envContent += `# GEMINI_API_KEY=your_gemini_api_key_here\n`;
|
||||
}
|
||||
|
||||
if (answers.groqApiKey) {
|
||||
envContent += `GROQ_API_KEY=${answers.groqApiKey}\n`;
|
||||
} else {
|
||||
envContent += `# GROQ_API_KEY=your_groq_api_key_here\n`;
|
||||
}
|
||||
|
||||
await fs.writeFile(path.join(projectPath, '.env'), envContent);
|
||||
await fs.writeFile(path.join(projectPath, '.env.example'), envContent.replace(/=.+/g, '=your_key_here'));
|
||||
}
|
||||
|
||||
async function createEnvExample(projectPath, sandbox) {
|
||||
let envContent = '# Open Lovable Configuration\n\n';
|
||||
|
||||
envContent += `# Sandbox Provider\n`;
|
||||
envContent += `SANDBOX_PROVIDER=${sandbox}\n\n`;
|
||||
|
||||
envContent += `# REQUIRED - Web scraping for cloning websites\n`;
|
||||
envContent += `# Get yours at https://firecrawl.dev\n`;
|
||||
envContent += `FIRECRAWL_API_KEY=your_firecrawl_api_key_here\n\n`;
|
||||
|
||||
if (sandbox === 'e2b') {
|
||||
envContent += `# REQUIRED - Sandboxes for code execution\n`;
|
||||
envContent += `# Get yours at https://e2b.dev\n`;
|
||||
envContent += `E2B_API_KEY=your_e2b_api_key_here\n\n`;
|
||||
} else if (sandbox === 'vercel') {
|
||||
envContent += `# REQUIRED - Vercel Sandboxes\n`;
|
||||
envContent += `# Option 1: OIDC (automatic in Vercel environment)\n`;
|
||||
envContent += `# Option 2: Personal Access Token\n`;
|
||||
envContent += `VERCEL_TEAM_ID=your_team_id\n`;
|
||||
envContent += `VERCEL_PROJECT_ID=your_project_id\n`;
|
||||
envContent += `VERCEL_TOKEN=your_access_token\n\n`;
|
||||
}
|
||||
|
||||
envContent += `# OPTIONAL - AI Providers (need at least one)\n`;
|
||||
envContent += `# Get yours at https://console.anthropic.com\n`;
|
||||
envContent += `ANTHROPIC_API_KEY=your_anthropic_api_key_here\n\n`;
|
||||
envContent += `# Get yours at https://platform.openai.com\n`;
|
||||
envContent += `OPENAI_API_KEY=your_openai_api_key_here\n\n`;
|
||||
envContent += `# Get yours at https://aistudio.google.com/app/apikey\n`;
|
||||
envContent += `GEMINI_API_KEY=your_gemini_api_key_here\n\n`;
|
||||
envContent += `# Get yours at https://console.groq.com\n`;
|
||||
envContent += `GROQ_API_KEY=your_groq_api_key_here\n`;
|
||||
|
||||
await fs.writeFile(path.join(projectPath, '.env.example'), envContent);
|
||||
}
|
||||
|
||||
async function updatePackageJson(projectPath, name) {
|
||||
const packageJsonPath = path.join(projectPath, 'package.json');
|
||||
|
||||
if (await fs.pathExists(packageJsonPath)) {
|
||||
const packageJson = await fs.readJson(packageJsonPath);
|
||||
packageJson.name = name;
|
||||
await fs.writeJson(packageJsonPath, packageJson, { spaces: 2 });
|
||||
}
|
||||
}
|
||||
|
||||
async function updateAppConfig(projectPath, sandbox) {
|
||||
const configPath = path.join(projectPath, 'config', 'app.config.ts');
|
||||
|
||||
if (await fs.pathExists(configPath)) {
|
||||
let content = await fs.readFile(configPath, 'utf-8');
|
||||
|
||||
// Add sandbox provider configuration
|
||||
const sandboxConfig = `
|
||||
// Sandbox Provider Configuration
|
||||
sandboxProvider: process.env.SANDBOX_PROVIDER || '${sandbox}',
|
||||
`;
|
||||
|
||||
// Insert after the opening of appConfig
|
||||
content = content.replace(
|
||||
'export const appConfig = {',
|
||||
`export const appConfig = {${sandboxConfig}`
|
||||
);
|
||||
|
||||
await fs.writeFile(configPath, content);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
export function getPrompts(config) {
|
||||
const prompts = [];
|
||||
|
||||
if (!config.name) {
|
||||
prompts.push({
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: 'Project name:',
|
||||
default: 'my-open-lovable',
|
||||
validate: (input) => {
|
||||
if (!input || input.trim() === '') {
|
||||
return 'Project name is required';
|
||||
}
|
||||
if (!/^[a-z0-9-_]+$/i.test(input)) {
|
||||
return 'Project name can only contain letters, numbers, hyphens, and underscores';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!config.sandbox) {
|
||||
prompts.push({
|
||||
type: 'list',
|
||||
name: 'sandbox',
|
||||
message: 'Choose your sandbox provider:',
|
||||
choices: [
|
||||
{
|
||||
name: 'E2B - Full-featured development sandboxes',
|
||||
value: 'e2b',
|
||||
short: 'E2B'
|
||||
},
|
||||
{
|
||||
name: 'Vercel - Lightweight ephemeral VMs',
|
||||
value: 'vercel',
|
||||
short: 'Vercel'
|
||||
}
|
||||
],
|
||||
default: 'e2b'
|
||||
});
|
||||
}
|
||||
|
||||
prompts.push({
|
||||
type: 'confirm',
|
||||
name: 'configureEnv',
|
||||
message: 'Would you like to configure API keys now?',
|
||||
default: true
|
||||
});
|
||||
|
||||
return prompts;
|
||||
}
|
||||
|
||||
export function getEnvPrompts(provider) {
|
||||
const prompts = [];
|
||||
|
||||
// Always include Firecrawl API key
|
||||
prompts.push({
|
||||
type: 'input',
|
||||
name: 'firecrawlApiKey',
|
||||
message: 'Firecrawl API key (for web scraping):',
|
||||
validate: (input) => {
|
||||
if (!input || input.trim() === '') {
|
||||
return 'Firecrawl API key is required for web scraping functionality';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (provider === 'e2b') {
|
||||
prompts.push({
|
||||
type: 'input',
|
||||
name: 'e2bApiKey',
|
||||
message: 'E2B API key:',
|
||||
validate: (input) => {
|
||||
if (!input || input.trim() === '') {
|
||||
return 'E2B API key is required';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
} else if (provider === 'vercel') {
|
||||
prompts.push({
|
||||
type: 'list',
|
||||
name: 'vercelAuthMethod',
|
||||
message: 'Vercel authentication method:',
|
||||
choices: [
|
||||
{
|
||||
name: 'OIDC Token (automatic in Vercel environment)',
|
||||
value: 'oidc',
|
||||
short: 'OIDC'
|
||||
},
|
||||
{
|
||||
name: 'Personal Access Token',
|
||||
value: 'pat',
|
||||
short: 'PAT'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
prompts.push({
|
||||
type: 'input',
|
||||
name: 'vercelTeamId',
|
||||
message: 'Vercel Team ID:',
|
||||
when: (answers) => answers.vercelAuthMethod === 'pat',
|
||||
validate: (input) => {
|
||||
if (!input || input.trim() === '') {
|
||||
return 'Team ID is required for PAT authentication';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
prompts.push({
|
||||
type: 'input',
|
||||
name: 'vercelProjectId',
|
||||
message: 'Vercel Project ID:',
|
||||
when: (answers) => answers.vercelAuthMethod === 'pat',
|
||||
validate: (input) => {
|
||||
if (!input || input.trim() === '') {
|
||||
return 'Project ID is required for PAT authentication';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
prompts.push({
|
||||
type: 'input',
|
||||
name: 'vercelToken',
|
||||
message: 'Vercel Access Token:',
|
||||
when: (answers) => answers.vercelAuthMethod === 'pat',
|
||||
validate: (input) => {
|
||||
if (!input || input.trim() === '') {
|
||||
return 'Access token is required for PAT authentication';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Optional AI provider keys
|
||||
prompts.push({
|
||||
type: 'confirm',
|
||||
name: 'addAiKeys',
|
||||
message: 'Would you like to add AI provider API keys?',
|
||||
default: true
|
||||
});
|
||||
|
||||
prompts.push({
|
||||
type: 'checkbox',
|
||||
name: 'aiProviders',
|
||||
message: 'Select AI providers to configure:',
|
||||
when: (answers) => answers.addAiKeys,
|
||||
choices: [
|
||||
{ name: 'Anthropic (Claude)', value: 'anthropic' },
|
||||
{ name: 'OpenAI (GPT)', value: 'openai' },
|
||||
{ name: 'Google (Gemini)', value: 'gemini' },
|
||||
{ name: 'Groq', value: 'groq' }
|
||||
]
|
||||
});
|
||||
|
||||
prompts.push({
|
||||
type: 'input',
|
||||
name: 'anthropicApiKey',
|
||||
message: 'Anthropic API key:',
|
||||
when: (answers) => answers.aiProviders && answers.aiProviders.includes('anthropic')
|
||||
});
|
||||
|
||||
prompts.push({
|
||||
type: 'input',
|
||||
name: 'openaiApiKey',
|
||||
message: 'OpenAI API key:',
|
||||
when: (answers) => answers.aiProviders && answers.aiProviders.includes('openai')
|
||||
});
|
||||
|
||||
prompts.push({
|
||||
type: 'input',
|
||||
name: 'geminiApiKey',
|
||||
message: 'Gemini API key:',
|
||||
when: (answers) => answers.aiProviders && answers.aiProviders.includes('gemini')
|
||||
});
|
||||
|
||||
prompts.push({
|
||||
type: 'input',
|
||||
name: 'groqApiKey',
|
||||
message: 'Groq API key:',
|
||||
when: (answers) => answers.aiProviders && answers.aiProviders.includes('groq')
|
||||
});
|
||||
|
||||
return prompts;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "create-open-lovable",
|
||||
"version": "1.0.0",
|
||||
"description": "CLI to bootstrap Open Lovable with your choice of sandbox provider",
|
||||
"type": "module",
|
||||
"main": "index.js",
|
||||
"bin": {
|
||||
"create-open-lovable": "./index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node index.js --dry-run"
|
||||
},
|
||||
"keywords": [
|
||||
"lovable",
|
||||
"sandbox",
|
||||
"e2b",
|
||||
"vercel",
|
||||
"ai",
|
||||
"code-generation"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^11.1.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"inquirer": "^9.2.12",
|
||||
"ora": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Open Lovable Configuration - E2B Provider
|
||||
|
||||
# Sandbox Provider
|
||||
SANDBOX_PROVIDER=e2b
|
||||
|
||||
# REQUIRED - Sandboxes for code execution
|
||||
# Get yours at https://e2b.dev
|
||||
E2B_API_KEY=your_e2b_api_key_here
|
||||
|
||||
# REQUIRED - Web scraping for cloning websites
|
||||
# Get yours at https://firecrawl.dev
|
||||
FIRECRAWL_API_KEY=your_firecrawl_api_key_here
|
||||
|
||||
# OPTIONAL - AI Providers (need at least one)
|
||||
# Get yours at https://console.anthropic.com
|
||||
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||
|
||||
# Get yours at https://platform.openai.com
|
||||
OPENAI_API_KEY=your_openai_api_key_here
|
||||
|
||||
# Get yours at https://aistudio.google.com/app/apikey
|
||||
GEMINI_API_KEY=your_gemini_api_key_here
|
||||
|
||||
# Get yours at https://console.groq.com
|
||||
GROQ_API_KEY=your_groq_api_key_here
|
||||
@@ -0,0 +1,38 @@
|
||||
# Open Lovable - E2B Sandbox
|
||||
|
||||
This project is configured to use E2B sandboxes for code execution.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Get your E2B API key from [https://e2b.dev](https://e2b.dev)
|
||||
2. Get your Firecrawl API key from [https://firecrawl.dev](https://firecrawl.dev)
|
||||
3. Copy `.env.example` to `.env` and add your API keys
|
||||
4. Run `npm install` to install dependencies
|
||||
5. Run `npm run dev` to start the development server
|
||||
|
||||
## E2B Features
|
||||
|
||||
- Full-featured development sandboxes
|
||||
- 15-minute default timeout (configurable)
|
||||
- Persistent file system during session
|
||||
- Support for complex package installations
|
||||
- Built-in Python runtime for code execution
|
||||
|
||||
## Configuration
|
||||
|
||||
You can adjust E2B settings in `config/app.config.ts`:
|
||||
|
||||
- `timeoutMinutes`: Sandbox session timeout (default: 15)
|
||||
- `vitePort`: Development server port (default: 5173)
|
||||
- `viteStartupDelay`: Time to wait for Vite to start (default: 7000ms)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. Verify your E2B API key is valid
|
||||
2. Check the console for detailed error messages
|
||||
3. Ensure you have a stable internet connection
|
||||
4. Try refreshing the page and creating a new sandbox
|
||||
|
||||
For more help, visit the [E2B documentation](https://docs.e2b.dev).
|
||||
@@ -0,0 +1,28 @@
|
||||
# Open Lovable Configuration - Vercel Provider
|
||||
|
||||
# Sandbox Provider
|
||||
SANDBOX_PROVIDER=vercel
|
||||
|
||||
# REQUIRED - Vercel Sandboxes
|
||||
# Option 1: OIDC Token (automatic in Vercel environment)
|
||||
# Option 2: Personal Access Token (configure below)
|
||||
VERCEL_TEAM_ID=your_team_id
|
||||
VERCEL_PROJECT_ID=your_project_id
|
||||
VERCEL_TOKEN=your_access_token
|
||||
|
||||
# REQUIRED - Web scraping for cloning websites
|
||||
# Get yours at https://firecrawl.dev
|
||||
FIRECRAWL_API_KEY=your_firecrawl_api_key_here
|
||||
|
||||
# OPTIONAL - AI Providers (need at least one)
|
||||
# Get yours at https://console.anthropic.com
|
||||
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||
|
||||
# Get yours at https://platform.openai.com
|
||||
OPENAI_API_KEY=your_openai_api_key_here
|
||||
|
||||
# Get yours at https://aistudio.google.com/app/apikey
|
||||
GEMINI_API_KEY=your_gemini_api_key_here
|
||||
|
||||
# Get yours at https://console.groq.com
|
||||
GROQ_API_KEY=your_groq_api_key_here
|
||||
@@ -0,0 +1,52 @@
|
||||
# Open Lovable - Vercel Sandbox
|
||||
|
||||
This project is configured to use Vercel Sandboxes for code execution.
|
||||
|
||||
## Setup
|
||||
|
||||
1. Configure Vercel authentication (see below)
|
||||
2. Get your Firecrawl API key from [https://firecrawl.dev](https://firecrawl.dev)
|
||||
3. Copy `.env.example` to `.env` and add your credentials
|
||||
4. Run `npm install` to install dependencies
|
||||
5. Run `npm run dev` to start the development server
|
||||
|
||||
## Vercel Authentication
|
||||
|
||||
### Option 1: OIDC Token (Recommended for Vercel deployments)
|
||||
When running in a Vercel environment, authentication happens automatically via OIDC tokens. No configuration needed!
|
||||
|
||||
### Option 2: Personal Access Token (For local development)
|
||||
1. Create a Personal Access Token in your [Vercel account settings](https://vercel.com/account/tokens)
|
||||
2. Get your Team ID from your [team settings](https://vercel.com/teams)
|
||||
3. Create a project and get the Project ID
|
||||
4. Add these to your `.env` file:
|
||||
- `VERCEL_TOKEN`
|
||||
- `VERCEL_TEAM_ID`
|
||||
- `VERCEL_PROJECT_ID`
|
||||
|
||||
## Vercel Sandbox Features
|
||||
|
||||
- Lightweight ephemeral Linux VMs
|
||||
- Powered by Firecracker MicroVMs
|
||||
- 5-minute default timeout (max 45 minutes)
|
||||
- 8 vCPUs maximum
|
||||
- Root access for package installation
|
||||
- Node 22 runtime included
|
||||
|
||||
## Configuration
|
||||
|
||||
You can adjust Vercel settings in `config/app.config.ts`:
|
||||
|
||||
- `maxDuration`: Sandbox session timeout (default: 5 minutes)
|
||||
- Authentication method (OIDC or PAT)
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. Verify your authentication credentials
|
||||
2. Check if you're using the correct authentication method
|
||||
3. Ensure your Vercel account has sandbox access
|
||||
4. Check the console for detailed error messages
|
||||
|
||||
For more help, visit the [Vercel Sandbox documentation](https://vercel.com/docs/vercel-sandbox).
|
||||
Reference in New Issue
Block a user