#!/usr/bin/env node /** * Interactive setup script for private prompts.chat clones. * Configures branding, theme, authentication, and features. * * Usage: node scripts/setup.js */ const fs = require('fs'); const path = require('path'); const readline = require('readline'); const CONFIG_FILE = path.join(__dirname, '..', 'prompts.config.ts'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); function question(prompt, defaultValue = '') { const defaultText = defaultValue ? ` (${defaultValue})` : ''; return new Promise((resolve) => { rl.question(`${prompt}${defaultText}: `, (answer) => { resolve(answer.trim() || defaultValue); }); }); } function confirm(prompt, defaultValue = true) { const hint = defaultValue ? '[Y/n]' : '[y/N]'; return new Promise((resolve) => { rl.question(`${prompt} ${hint}: `, (answer) => { if (!answer.trim()) { resolve(defaultValue); } else { resolve(answer.toLowerCase().startsWith('y')); } }); }); } function select(prompt, options, defaultIndex = 0) { console.log(`\n${prompt}`); options.forEach((opt, i) => { const marker = i === defaultIndex ? '>' : ' '; console.log(` ${marker} ${i + 1}. ${opt.label}`); }); return new Promise((resolve) => { rl.question(`Enter choice (1-${options.length}) [${defaultIndex + 1}]: `, (answer) => { const index = answer.trim() ? parseInt(answer) - 1 : defaultIndex; if (index >= 0 && index < options.length) { resolve(options[index].value); } else { resolve(options[defaultIndex].value); } }); }); } function multiSelect(prompt, options) { console.log(`\n${prompt}`); options.forEach((opt, i) => { console.log(` ${i + 1}. ${opt.label}`); }); return new Promise((resolve) => { rl.question(`Enter choices (comma-separated, e.g., 1,2): `, (answer) => { if (!answer.trim()) { resolve([]); return; } const indices = answer.split(',').map(s => parseInt(s.trim()) - 1); const selected = indices .filter(i => i >= 0 && i < options.length) .map(i => options[i].value); resolve(selected); }); }); } function generateConfig(config) { const sponsorsSection = config.sponsors.length > 0 ? `[ ${config.sponsors.map(s => `{ name: "${s.name}", logo: "${s.logo}", url: "${s.url}" }`).join(',\n ')} ]` : '[]'; return `import { defineConfig } from "@/lib/config"; // Private clone configuration - generated by setup script const useCloneBranding = true; export default defineConfig({ // Branding - your organization's identity branding: { name: "${config.branding.name}", logo: "${config.branding.logo}", logoDark: "${config.branding.logoDark}", favicon: "${config.branding.favicon}", description: "${config.branding.description}", }, // Theme - design system configuration theme: { radius: "${config.theme.radius}", variant: "${config.theme.variant}", density: "${config.theme.density}", colors: { primary: "${config.theme.primaryColor}", }, }, // Authentication plugins auth: { providers: [${config.auth.providers.map(p => `"${p}"`).join(', ')}], allowRegistration: ${config.auth.allowRegistration}, }, // Storage plugin for media uploads storage: { provider: "${config.storage.provider}", }, // Internationalization i18n: { locales: [${config.i18n.locales.map(l => `"${l}"`).join(', ')}], defaultLocale: "${config.i18n.defaultLocale}", }, // Features features: { privatePrompts: ${config.features.privatePrompts}, changeRequests: ${config.features.changeRequests}, categories: ${config.features.categories}, tags: ${config.features.tags}, aiSearch: ${config.features.aiSearch}, }, // Homepage customization (clone branding mode) homepage: { useCloneBranding, achievements: { enabled: false, // Disabled for private clones }, sponsors: { enabled: ${config.sponsors.length > 0}, items: ${sponsorsSection}, }, }, }); `; } async function main() { console.log('\n╔═══════════════════════════════════════════════════════════╗'); console.log('║ prompts.chat - Private Clone Setup ║'); console.log('╠═══════════════════════════════════════════════════════════╣'); console.log('║ This wizard will help you configure your private ║'); console.log('║ prompt library with your own branding and settings. ║'); console.log('╚═══════════════════════════════════════════════════════════╝\n'); const config = { branding: {}, theme: {}, auth: {}, storage: {}, i18n: {}, features: {}, sponsors: [] }; // === BRANDING === console.log('\n─── BRANDING ───────────────────────────────────────────────\n'); config.branding.name = await question('Organization/App name', 'My Prompt Library'); config.branding.description = await question('Description', 'Collect, organize, and share AI prompts'); config.branding.logo = await question('Logo path (public folder)', '/logo.svg'); config.branding.logoDark = await question('Dark mode logo path', config.branding.logo); config.branding.favicon = await question('Favicon path', '/logo.svg'); // === THEME === console.log('\n─── THEME ──────────────────────────────────────────────────\n'); config.theme.primaryColor = await question('Primary color (hex)', '#6366f1'); config.theme.radius = await select('Border radius style:', [ { label: 'None - Sharp corners', value: 'none' }, { label: 'Small - Subtle rounding', value: 'sm' }, { label: 'Medium - Moderate rounding', value: 'md' }, { label: 'Large - Very rounded', value: 'lg' } ], 1); config.theme.variant = await select('UI variant:', [ { label: 'Default - Standard modern look', value: 'default' }, { label: 'Flat - Minimal shadows', value: 'flat' }, { label: 'Brutal - Bold neo-brutalist style', value: 'brutal' } ], 0); config.theme.density = await select('Spacing density:', [ { label: 'Compact - Tighter spacing', value: 'compact' }, { label: 'Default - Balanced spacing', value: 'default' }, { label: 'Comfortable - More breathing room', value: 'comfortable' } ], 1); // === AUTHENTICATION === console.log('\n─── AUTHENTICATION ─────────────────────────────────────────\n'); config.auth.providers = await multiSelect('Select authentication providers:', [ { label: 'GitHub OAuth', value: 'github' }, { label: 'Google OAuth', value: 'google' }, { label: 'Microsoft Azure AD', value: 'azure' }, { label: 'Email/Password (Credentials)', value: 'credentials' } ]); if (config.auth.providers.length === 0) { console.log(' ⚠️ No providers selected, defaulting to credentials'); config.auth.providers = ['credentials']; } if (config.auth.providers.includes('credentials')) { config.auth.allowRegistration = await confirm('Allow public registration?', true); } else { config.auth.allowRegistration = false; } // === STORAGE === console.log('\n─── STORAGE ────────────────────────────────────────────────\n'); config.storage.provider = await select('Media storage provider:', [ { label: 'URL-based (external links)', value: 'url' }, { label: 'AWS S3 (requires configuration)', value: 's3' } ], 0); // === INTERNATIONALIZATION === console.log('\n─── INTERNATIONALIZATION ───────────────────────────────────\n'); const selectedLocales = await multiSelect('Select supported languages:', [ { label: 'English', value: 'en' }, { label: 'Spanish', value: 'es' }, { label: 'French', value: 'fr' }, { label: 'German', value: 'de' }, { label: 'Italian', value: 'it' }, { label: 'Portuguese', value: 'pt' }, { label: 'Turkish', value: 'tr' }, { label: 'Japanese', value: 'ja' }, { label: 'Korean', value: 'ko' }, { label: 'Chinese', value: 'zh' }, { label: 'Arabic', value: 'ar' } ]); config.i18n.locales = selectedLocales.length > 0 ? selectedLocales : ['en']; if (config.i18n.locales.length > 1) { console.log(`\nSelected locales: ${config.i18n.locales.join(', ')}`); config.i18n.defaultLocale = await question('Default locale', config.i18n.locales[0]); } else { config.i18n.defaultLocale = config.i18n.locales[0]; } // === FEATURES === console.log('\n─── FEATURES ───────────────────────────────────────────────\n'); config.features.privatePrompts = await confirm('Enable private prompts?', true); config.features.changeRequests = await confirm('Enable change request system (versioning)?', true); config.features.categories = await confirm('Enable categories?', true); config.features.tags = await confirm('Enable tags?', true); config.features.aiSearch = await confirm('Enable AI-powered search? (requires OPENAI_API_KEY)', false); // === SPONSORS === console.log('\n─── SPONSORS (Optional) ────────────────────────────────────\n'); const addSponsors = await confirm('Add sponsor logos to homepage?', false); if (addSponsors) { let addMore = true; while (addMore) { console.log('\n Adding sponsor:'); const name = await question(' Sponsor name'); const logo = await question(' Logo URL'); const url = await question(' Website URL'); if (name && logo && url) { config.sponsors.push({ name, logo, url }); console.log(` ✓ Added ${name}`); } addMore = await confirm(' Add another sponsor?', false); } } // === SUMMARY === console.log('\n─── CONFIGURATION SUMMARY ──────────────────────────────────\n'); console.log(` Name: ${config.branding.name}`); console.log(` Description: ${config.branding.description}`); console.log(` Primary Color: ${config.theme.primaryColor}`); console.log(` UI Style: ${config.theme.variant} / ${config.theme.radius} radius`); console.log(` Auth: ${config.auth.providers.join(', ')}`); console.log(` Storage: ${config.storage.provider}`); console.log(` Languages: ${config.i18n.locales.join(', ')}`); console.log(` Features: ${Object.entries(config.features).filter(([,v]) => v).map(([k]) => k).join(', ')}`); if (config.sponsors.length > 0) { console.log(` Sponsors: ${config.sponsors.map(s => s.name).join(', ')}`); } console.log(''); const proceed = await confirm('Generate configuration file?', true); if (!proceed) { console.log('\n❌ Setup cancelled.\n'); rl.close(); process.exit(0); } // === GENERATE CONFIG === const configContent = generateConfig(config); // Backup existing config if it exists if (fs.existsSync(CONFIG_FILE)) { const backupPath = CONFIG_FILE + '.backup'; fs.copyFileSync(CONFIG_FILE, backupPath); console.log(`\n📦 Backed up existing config to prompts.config.ts.backup`); } fs.writeFileSync(CONFIG_FILE, configContent); console.log('✅ Generated prompts.config.ts'); // === ENV FILE === console.log('\n─── ENVIRONMENT VARIABLES ──────────────────────────────────\n'); const envExample = path.join(__dirname, '..', '.env.example'); const envFile = path.join(__dirname, '..', '.env'); if (!fs.existsSync(envFile) && fs.existsSync(envExample)) { const createEnv = await confirm('Create .env file from .env.example?', true); if (createEnv) { fs.copyFileSync(envExample, envFile); console.log('✅ Created .env file'); } } console.log('\n📋 Required environment variables:'); console.log(' DATABASE_URL - PostgreSQL connection string'); console.log(' AUTH_SECRET - NextAuth secret (generate with: openssl rand -base64 32)'); if (config.auth.providers.includes('github')) { console.log(' AUTH_GITHUB_ID - GitHub OAuth client ID'); console.log(' AUTH_GITHUB_SECRET - GitHub OAuth client secret'); } if (config.auth.providers.includes('google')) { console.log(' AUTH_GOOGLE_ID - Google OAuth client ID'); console.log(' AUTH_GOOGLE_SECRET - Google OAuth client secret'); } if (config.auth.providers.includes('azure')) { console.log(' AUTH_AZURE_AD_CLIENT_ID - Azure AD client ID'); console.log(' AUTH_AZURE_AD_CLIENT_SECRET - Azure AD client secret'); console.log(' AUTH_AZURE_AD_ISSUER - Azure AD issuer URL'); } if (config.features.aiSearch) { console.log(' OPENAI_API_KEY - OpenAI API key for semantic search'); } if (config.storage.provider === 's3') { console.log(' AWS_ACCESS_KEY_ID - AWS access key'); console.log(' AWS_SECRET_ACCESS_KEY - AWS secret key'); console.log(' AWS_S3_BUCKET - S3 bucket name'); console.log(' AWS_S3_REGION - S3 region'); } // === NEXT STEPS === console.log('\n─── NEXT STEPS ─────────────────────────────────────────────\n'); console.log(' 1. Edit .env with your database and auth credentials'); console.log(' 2. Add your logo files to the public/ folder'); console.log(' 3. Run: npm run db:push'); console.log(' 4. Run: npm run dev'); console.log(''); console.log('📖 For more details, see SELF-HOSTING.md'); console.log('\n═══════════════════════════════════════════════════════════════\n'); rl.close(); } main().catch((err) => { console.error('Setup failed:', err); rl.close(); process.exit(1); });