@chimerai/cli 0.2.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -0
  3. package/dist/cli.d.ts +7 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +317 -0
  6. package/dist/commands/add.d.ts +11 -0
  7. package/dist/commands/add.d.ts.map +1 -0
  8. package/dist/commands/add.js +2126 -0
  9. package/dist/commands/create.d.ts +12 -0
  10. package/dist/commands/create.d.ts.map +1 -0
  11. package/dist/commands/create.js +1703 -0
  12. package/dist/commands/deploy.d.ts +11 -0
  13. package/dist/commands/deploy.d.ts.map +1 -0
  14. package/dist/commands/deploy.js +219 -0
  15. package/dist/commands/dev.d.ts +17 -0
  16. package/dist/commands/dev.d.ts.map +1 -0
  17. package/dist/commands/dev.js +206 -0
  18. package/dist/commands/doctor.d.ts +11 -0
  19. package/dist/commands/doctor.d.ts.map +1 -0
  20. package/dist/commands/doctor.js +728 -0
  21. package/dist/commands/generate.d.ts +19 -0
  22. package/dist/commands/generate.d.ts.map +1 -0
  23. package/dist/commands/generate.js +429 -0
  24. package/dist/commands/init.d.ts +11 -0
  25. package/dist/commands/init.d.ts.map +1 -0
  26. package/dist/commands/init.js +269 -0
  27. package/dist/commands/list.d.ts +12 -0
  28. package/dist/commands/list.d.ts.map +1 -0
  29. package/dist/commands/list.js +328 -0
  30. package/dist/commands/migrate.d.ts +14 -0
  31. package/dist/commands/migrate.d.ts.map +1 -0
  32. package/dist/commands/migrate.js +197 -0
  33. package/dist/commands/plugin.d.ts +10 -0
  34. package/dist/commands/plugin.d.ts.map +1 -0
  35. package/dist/commands/plugin.js +239 -0
  36. package/dist/commands/remove.d.ts +11 -0
  37. package/dist/commands/remove.d.ts.map +1 -0
  38. package/dist/commands/remove.js +472 -0
  39. package/dist/commands/secret.d.ts +12 -0
  40. package/dist/commands/secret.d.ts.map +1 -0
  41. package/dist/commands/secret.js +102 -0
  42. package/dist/commands/setup.d.ts +9 -0
  43. package/dist/commands/setup.d.ts.map +1 -0
  44. package/dist/commands/setup.js +788 -0
  45. package/dist/commands/update.d.ts +14 -0
  46. package/dist/commands/update.d.ts.map +1 -0
  47. package/dist/commands/update.js +211 -0
  48. package/dist/commands/use.d.ts +9 -0
  49. package/dist/commands/use.d.ts.map +1 -0
  50. package/dist/commands/use.js +51 -0
  51. package/dist/index.d.ts +22 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +45 -0
  54. package/dist/license.d.ts +55 -0
  55. package/dist/license.d.ts.map +1 -0
  56. package/dist/license.js +258 -0
  57. package/dist/scanner.d.ts +31 -0
  58. package/dist/scanner.d.ts.map +1 -0
  59. package/dist/scanner.js +113 -0
  60. package/dist/schema-manager.d.ts +26 -0
  61. package/dist/schema-manager.d.ts.map +1 -0
  62. package/dist/schema-manager.js +132 -0
  63. package/dist/templates/admin.d.ts +49 -0
  64. package/dist/templates/admin.d.ts.map +1 -0
  65. package/dist/templates/admin.js +1358 -0
  66. package/dist/templates/ai-routes.d.ts +17 -0
  67. package/dist/templates/ai-routes.d.ts.map +1 -0
  68. package/dist/templates/ai-routes.js +1130 -0
  69. package/dist/templates/ai-service-tools.d.ts +22 -0
  70. package/dist/templates/ai-service-tools.d.ts.map +1 -0
  71. package/dist/templates/ai-service-tools.js +1424 -0
  72. package/dist/templates/ai-service.d.ts +66 -0
  73. package/dist/templates/ai-service.d.ts.map +1 -0
  74. package/dist/templates/ai-service.js +2202 -0
  75. package/dist/templates/api-routes.d.ts +108 -0
  76. package/dist/templates/api-routes.d.ts.map +1 -0
  77. package/dist/templates/api-routes.js +1219 -0
  78. package/dist/templates/auth.d.ts +48 -0
  79. package/dist/templates/auth.d.ts.map +1 -0
  80. package/dist/templates/auth.js +381 -0
  81. package/dist/templates/billing.d.ts +44 -0
  82. package/dist/templates/billing.d.ts.map +1 -0
  83. package/dist/templates/billing.js +551 -0
  84. package/dist/templates/chat.d.ts +63 -0
  85. package/dist/templates/chat.d.ts.map +1 -0
  86. package/dist/templates/chat.js +1979 -0
  87. package/dist/templates/components.d.ts +22 -0
  88. package/dist/templates/components.d.ts.map +1 -0
  89. package/dist/templates/components.js +672 -0
  90. package/dist/templates/config.d.ts +6 -0
  91. package/dist/templates/config.d.ts.map +1 -0
  92. package/dist/templates/config.js +86 -0
  93. package/dist/templates/docker.d.ts +25 -0
  94. package/dist/templates/docker.d.ts.map +1 -0
  95. package/dist/templates/docker.js +165 -0
  96. package/dist/templates/gdpr.d.ts +16 -0
  97. package/dist/templates/gdpr.d.ts.map +1 -0
  98. package/dist/templates/gdpr.js +259 -0
  99. package/dist/templates/index.d.ts +77 -0
  100. package/dist/templates/index.d.ts.map +1 -0
  101. package/dist/templates/index.js +339 -0
  102. package/dist/templates/layout.d.ts +67 -0
  103. package/dist/templates/layout.d.ts.map +1 -0
  104. package/dist/templates/layout.js +670 -0
  105. package/dist/templates/mfa.d.ts +23 -0
  106. package/dist/templates/mfa.d.ts.map +1 -0
  107. package/dist/templates/mfa.js +353 -0
  108. package/dist/templates/middleware.d.ts +12 -0
  109. package/dist/templates/middleware.d.ts.map +1 -0
  110. package/dist/templates/middleware.js +116 -0
  111. package/dist/templates/prisma.d.ts +35 -0
  112. package/dist/templates/prisma.d.ts.map +1 -0
  113. package/dist/templates/prisma.js +724 -0
  114. package/dist/templates/provider-routes.d.ts +21 -0
  115. package/dist/templates/provider-routes.d.ts.map +1 -0
  116. package/dist/templates/provider-routes.js +1203 -0
  117. package/dist/templates/rag.d.ts +48 -0
  118. package/dist/templates/rag.d.ts.map +1 -0
  119. package/dist/templates/rag.js +532 -0
  120. package/dist/templates/widget.d.ts +64 -0
  121. package/dist/templates/widget.d.ts.map +1 -0
  122. package/dist/templates/widget.js +1360 -0
  123. package/dist/utils/provider-db.d.ts +63 -0
  124. package/dist/utils/provider-db.d.ts.map +1 -0
  125. package/dist/utils/provider-db.js +300 -0
  126. package/dist/utils.d.ts +78 -0
  127. package/dist/utils.d.ts.map +1 -0
  128. package/dist/utils.js +330 -0
  129. package/package.json +60 -0
@@ -0,0 +1,1703 @@
1
+ "use strict";
2
+ /**
3
+ * Create Command - Generate a new ChimerAI project with selected features
4
+ * REFACTORED: Uses inline template generators instead of fs.copy()
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ var __importDefault = (this && this.__importDefault) || function (mod) {
40
+ return (mod && mod.__esModule) ? mod : { "default": mod };
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.createCommand = createCommand;
44
+ const inquirer_1 = __importDefault(require("inquirer"));
45
+ const chalk_1 = __importDefault(require("chalk"));
46
+ const ora_1 = __importDefault(require("ora"));
47
+ const fs_extra_1 = __importDefault(require("fs-extra"));
48
+ const path_1 = __importDefault(require("path"));
49
+ const child_process_1 = require("child_process");
50
+ const templates = __importStar(require("../templates/index.js"));
51
+ const index_js_1 = require("../templates/index.js");
52
+ const utils_js_1 = require("../utils.js");
53
+ const AVAILABLE_FEATURES = [
54
+ {
55
+ name: '🔐 Authentication (NextAuth)',
56
+ value: 'auth',
57
+ checked: true,
58
+ },
59
+ {
60
+ name: '👥 RBAC System (Users, Roles, Permissions)',
61
+ value: 'rbac',
62
+ checked: true,
63
+ },
64
+ {
65
+ name: '🔌 Model Providers Management',
66
+ value: 'model-providers',
67
+ checked: true,
68
+ },
69
+ {
70
+ name: '📝 Prompt Template Management',
71
+ value: 'prompts',
72
+ checked: true,
73
+ },
74
+ {
75
+ name: '💬 AI Chat Interface',
76
+ value: 'chat',
77
+ checked: false,
78
+ },
79
+ {
80
+ name: '🔍 RAG / Vector Store (FAISS)',
81
+ value: 'rag',
82
+ checked: false,
83
+ },
84
+ {
85
+ name: '💳 Billing System (Stripe)',
86
+ value: 'billing',
87
+ checked: false,
88
+ },
89
+ {
90
+ name: '📊 API Usage Analytics',
91
+ value: 'analytics',
92
+ checked: true,
93
+ },
94
+ {
95
+ name: '🎨 Admin Dashboard',
96
+ value: 'admin',
97
+ checked: true,
98
+ },
99
+ ];
100
+ async function createCommand(projectName, options) {
101
+ console.log(chalk_1.default.bold.cyan('\n🚀 Create New ChimerAI Project\n'));
102
+ // Validate project name
103
+ if (!projectName) {
104
+ (0, utils_js_1.handleCliError)('Please provide a project name. Usage: chimerai create <project-name>');
105
+ }
106
+ const targetDir = path_1.default.resolve(projectName);
107
+ // Check if directory exists
108
+ if (fs_extra_1.default.existsSync(targetDir)) {
109
+ (0, utils_js_1.handleCliError)(`Directory "${projectName}" already exists`);
110
+ }
111
+ let selectedFeatures;
112
+ if (options.yes) {
113
+ // Use defaults
114
+ selectedFeatures = AVAILABLE_FEATURES.filter((f) => f.checked).map((f) => f.value);
115
+ console.log(chalk_1.default.cyan('Using default feature set...'));
116
+ }
117
+ else {
118
+ // Interactive selection
119
+ const answers = await inquirer_1.default.prompt([
120
+ {
121
+ type: 'checkbox',
122
+ name: 'features',
123
+ message: 'Select features to include:',
124
+ choices: AVAILABLE_FEATURES,
125
+ pageSize: 15,
126
+ },
127
+ ]);
128
+ selectedFeatures = answers.features;
129
+ }
130
+ if (selectedFeatures.length === 0) {
131
+ (0, utils_js_1.handleCliError)('No features selected');
132
+ }
133
+ console.log(chalk_1.default.bold.green('\n✨ Creating project with features:'));
134
+ selectedFeatures.forEach((f) => {
135
+ const feature = AVAILABLE_FEATURES.find((feat) => feat.value === f);
136
+ console.log(chalk_1.default.white(` ${feature?.name}`));
137
+ });
138
+ console.log(chalk_1.default.white(` 📦 Database: ${options.sqlite ? 'SQLite (no Docker needed)' : 'PostgreSQL'}`));
139
+ console.log('');
140
+ // Create project
141
+ await createProject(targetDir, projectName, selectedFeatures, options.sqlite);
142
+ console.log(chalk_1.default.bold.green('\n✅ Project created successfully!\n'));
143
+ // Auto-install dependencies if --install flag is set
144
+ if (options.install) {
145
+ console.log(chalk_1.default.cyan('\n🚀 Running installation script...\n'));
146
+ try {
147
+ // Detect OS and run appropriate install script
148
+ const isWindows = process.platform === 'win32';
149
+ const installScript = isWindows ? 'install.bat' : 'bash install.sh';
150
+ (0, child_process_1.execSync)(installScript, { cwd: targetDir, stdio: 'inherit' });
151
+ console.log(chalk_1.default.bold.green('\n🎉 Full installation completed!\n'));
152
+ console.log(chalk_1.default.cyan('You can now run:'));
153
+ console.log(chalk_1.default.white(` cd ${projectName}`));
154
+ console.log(chalk_1.default.white(' npm run dev'));
155
+ console.log(chalk_1.default.gray('\n Server will run on http://localhost:3001\n'));
156
+ console.log(chalk_1.default.yellow('Login with:'));
157
+ console.log(chalk_1.default.white(' Email: admin@example.com'));
158
+ console.log(chalk_1.default.white(' Password: admin123\n'));
159
+ }
160
+ catch (error) {
161
+ console.log(chalk_1.default.red('\n❌ Installation failed'));
162
+ console.log(chalk_1.default.yellow(`You can complete setup manually with: ${process.platform === 'win32' ? '.\\install.bat' : './install.sh'}\n`));
163
+ }
164
+ }
165
+ // Show next steps
166
+ console.log(chalk_1.default.cyan('Next steps:'));
167
+ console.log(chalk_1.default.white(` cd ${projectName}`));
168
+ if (!options.install) {
169
+ console.log(chalk_1.default.bold.white('\n Quick start:'));
170
+ console.log(chalk_1.default.white(' ./install.bat (Windows)'));
171
+ console.log(chalk_1.default.white(' ./install.sh (Linux/macOS)'));
172
+ console.log(chalk_1.default.bold.white('\n Or manually:'));
173
+ console.log(chalk_1.default.white(' npm install'));
174
+ console.log(chalk_1.default.white(' docker-compose up -d'));
175
+ console.log(chalk_1.default.white(' npm run db:push'));
176
+ console.log(chalk_1.default.white(' npm run db:seed'));
177
+ console.log(chalk_1.default.white(' npm run dev'));
178
+ }
179
+ else {
180
+ console.log(chalk_1.default.white(' docker-compose up -d'));
181
+ console.log(chalk_1.default.white(' npm run db:push'));
182
+ console.log(chalk_1.default.white(' npm run db:seed'));
183
+ console.log(chalk_1.default.white(' npm run dev'));
184
+ }
185
+ console.log(chalk_1.default.gray('\n Server will run on http://localhost:3001\n'));
186
+ }
187
+ async function createProject(targetDir, projectName, features, sqlite) {
188
+ const spinner = (0, ora_1.default)('Creating project structure...').start();
189
+ try {
190
+ // Create base directory
191
+ await fs_extra_1.default.ensureDir(targetDir);
192
+ // 1. Create base Next.js structure
193
+ spinner.text = 'Setting up Next.js base...';
194
+ await createBaseStructure(targetDir, projectName, features);
195
+ // 2. Create package.json
196
+ spinner.text = 'Generating package.json...';
197
+ await createPackageJson(targetDir, projectName, features);
198
+ // 3. Create TypeScript config
199
+ spinner.text = 'Setting up TypeScript...';
200
+ await createTsConfig(targetDir);
201
+ // 4. Create Prisma schema
202
+ spinner.text = 'Setting up database schema...';
203
+ await createPrismaSchema(targetDir, features, sqlite);
204
+ // 5. Create environment files
205
+ spinner.text = 'Creating environment files...';
206
+ await createEnvFiles(targetDir, features, sqlite);
207
+ // 6. Copy feature-specific files
208
+ spinner.text = 'Adding selected features...';
209
+ await copyFeatureFiles(targetDir, features);
210
+ // 7. Create seed script
211
+ spinner.text = 'Creating database seed...';
212
+ await createSeedScript(targetDir, features, sqlite);
213
+ // 8. Create Docker Compose
214
+ spinner.text = 'Setting up Docker...';
215
+ await createDockerCompose(targetDir);
216
+ // 9. Create install scripts
217
+ spinner.text = 'Creating install scripts...';
218
+ await createInstallScripts(targetDir);
219
+ // 10. Create README
220
+ spinner.text = 'Creating documentation...';
221
+ await createReadme(targetDir, projectName, features);
222
+ // 11. Create .chimerai marker file
223
+ spinner.text = 'Registering project...';
224
+ const chimeraiMarker = {
225
+ version: '1.0.0',
226
+ name: projectName,
227
+ created: new Date().toISOString().split('T')[0],
228
+ };
229
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, '.chimerai'), JSON.stringify(chimeraiMarker, null, 2));
230
+ // Register in global registry
231
+ (0, utils_js_1.registerProject)(projectName, targetDir);
232
+ spinner.succeed(chalk_1.default.green('Project structure created'));
233
+ }
234
+ catch (error) {
235
+ spinner.fail(chalk_1.default.red('Failed to create project'));
236
+ console.error(error.message);
237
+ throw error;
238
+ }
239
+ }
240
+ async function createBaseStructure(targetDir, projectName, features) {
241
+ // Create base directories
242
+ const dirs = [
243
+ 'app',
244
+ 'app/api',
245
+ 'components',
246
+ 'components/ui',
247
+ 'lib',
248
+ 'prisma',
249
+ 'public',
250
+ 'styles',
251
+ ];
252
+ // Add feature-specific directories
253
+ if (features.includes('admin')) {
254
+ dirs.push('app/admin', 'app/admin/settings', 'app/admin/logs');
255
+ }
256
+ if (features.includes('chat')) {
257
+ dirs.push('app/(app)', 'app/(app)/chat', 'components/chat', 'app/api/conversations/[id]');
258
+ }
259
+ if (features.includes('auth')) {
260
+ dirs.push('app/(auth)', 'app/(auth)/login', 'app/api/auth/[...nextauth]');
261
+ }
262
+ if (features.includes('rbac')) {
263
+ dirs.push('app/admin/users', 'app/admin/roles');
264
+ }
265
+ if (features.includes('model-providers')) {
266
+ dirs.push('app/dashboard/providers');
267
+ }
268
+ if (features.includes('prompts')) {
269
+ dirs.push('app/dashboard/prompts', 'app/api/prompts', 'app/api/prompts/[id]');
270
+ }
271
+ for (const dir of dirs) {
272
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, dir));
273
+ }
274
+ // Create app/layout.tsx using template generator
275
+ const layoutContent = features.includes('auth')
276
+ ? templates.generateAppLayoutWithAuth(projectName)
277
+ : templates.generateAppLayout(projectName);
278
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/layout.tsx'), layoutContent);
279
+ // Create app/page.tsx using template generator
280
+ const pageContent = templates.generateAppPage();
281
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/page.tsx'), pageContent);
282
+ // Create globals.css using template generator
283
+ const globalsCss = templates.generateGlobalsCss();
284
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/globals.css'), globalsCss);
285
+ }
286
+ async function createPackageJson(targetDir, projectName, features) {
287
+ const dependencies = {
288
+ next: '^15.0.0',
289
+ react: '^18.3.1',
290
+ 'react-dom': '^18.3.1',
291
+ '@prisma/client': '^5.22.0',
292
+ };
293
+ const devDependencies = {
294
+ '@types/node': '^20',
295
+ '@types/react': '^18',
296
+ '@types/react-dom': '^18',
297
+ typescript: '^5',
298
+ tailwindcss: '^3.4.0',
299
+ postcss: '^8',
300
+ autoprefixer: '^10',
301
+ prisma: '^5.22.0',
302
+ tsx: '^4.20.6',
303
+ };
304
+ // Add feature-specific dependencies
305
+ if (features.includes('auth')) {
306
+ dependencies['next-auth'] = '^4.24.10';
307
+ dependencies['@auth/prisma-adapter'] = '^2.11.1';
308
+ dependencies['bcryptjs'] = '^2.4.3';
309
+ dependencies['next-themes'] = '^0.4.4';
310
+ dependencies['sonner'] = '^1.7.0';
311
+ devDependencies['@types/bcryptjs'] = '^2.4.6';
312
+ }
313
+ // Note: ChimerAI workspace packages are not yet published to npm
314
+ // For standalone projects, these features will need manual implementation
315
+ // or you can use the complete starter kit with 'chimerai init'
316
+ if (features.includes('rbac')) {
317
+ // dependencies['@chimerai/rbac-core'] = 'workspace:*'; // Not available standalone yet
318
+ }
319
+ if (features.includes('model-providers')) {
320
+ // dependencies['@chimerai/model-providers'] = 'workspace:*'; // Not available standalone yet
321
+ }
322
+ if (features.includes('admin')) {
323
+ // dependencies['@chimerai/admin-ui'] = 'workspace:*'; // Not available standalone yet
324
+ }
325
+ if (features.includes('chat')) {
326
+ dependencies['react-markdown'] = '^9.0.0';
327
+ dependencies['remark-gfm'] = '^4.0.0';
328
+ }
329
+ if (features.includes('billing')) {
330
+ // dependencies['@chimerai/billing'] = 'workspace:*'; // Not available standalone yet
331
+ dependencies['stripe'] = '^14.0.0';
332
+ }
333
+ // UI dependencies
334
+ dependencies['@radix-ui/react-dialog'] = '^1.0.5';
335
+ dependencies['@radix-ui/react-dropdown-menu'] = '^2.0.6';
336
+ dependencies['@radix-ui/react-slot'] = '^1.2.4';
337
+ dependencies['class-variance-authority'] = '^0.7.0';
338
+ dependencies['clsx'] = '^2.0.0';
339
+ dependencies['tailwind-merge'] = '^2.2.0';
340
+ dependencies['lucide-react'] = '^0.294.0';
341
+ const packageJson = {
342
+ name: projectName.toLowerCase().replace(/\s+/g, '-'),
343
+ version: '0.1.0',
344
+ private: true,
345
+ scripts: {
346
+ dev: 'next dev -p 3001',
347
+ build: 'prisma generate && next build',
348
+ start: 'next start',
349
+ lint: 'next lint',
350
+ postinstall: 'prisma generate',
351
+ 'db:generate': 'prisma generate',
352
+ 'db:push': 'prisma db push',
353
+ 'db:seed': 'tsx prisma/seed.ts',
354
+ 'db:studio': 'prisma studio',
355
+ },
356
+ dependencies,
357
+ devDependencies,
358
+ prisma: {
359
+ seed: 'tsx prisma/seed.ts',
360
+ },
361
+ };
362
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'package.json'), JSON.stringify(packageJson, null, 2));
363
+ }
364
+ async function createTsConfig(targetDir) {
365
+ // Create next.config.js using template generator
366
+ const nextConfig = templates.generateNextConfig();
367
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'next.config.js'), nextConfig);
368
+ // Create tsconfig.json using template generator
369
+ const tsConfig = templates.generateTsConfig();
370
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'tsconfig.json'), tsConfig);
371
+ // Create tailwind.config.js using template generator
372
+ const tailwindConfig = templates.generateTailwindConfig();
373
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'tailwind.config.js'), tailwindConfig);
374
+ // Create postcss.config.js using template generator
375
+ const postcssConfig = templates.generatePostcssConfig();
376
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'postcss.config.js'), postcssConfig);
377
+ }
378
+ async function createPrismaSchema(targetDir, features, sqlite) {
379
+ const dbProvider = sqlite ? 'sqlite' : 'postgresql';
380
+ let schemaContent = `generator client {
381
+ provider = "prisma-client-js"
382
+ }
383
+
384
+ datasource db {
385
+ provider = "${dbProvider}"
386
+ url = env("DATABASE_URL")
387
+ }
388
+
389
+ `;
390
+ // Add User model if auth is included
391
+ if (features.includes('auth')) {
392
+ schemaContent += `model User {
393
+ id String @id @default(cuid())
394
+ name String?
395
+ email String? @unique
396
+ emailVerified DateTime?
397
+ image String?
398
+ password String?
399
+ createdAt DateTime @default(now())
400
+ updatedAt DateTime @updatedAt
401
+
402
+ accounts Account[]
403
+ sessions Session[]
404
+ `;
405
+ if (features.includes('rbac')) {
406
+ schemaContent += ` roles UserRole[]
407
+ `;
408
+ }
409
+ // Provider relations (ALWAYS included)
410
+ schemaContent += ` providers Provider[] @relation("CreatedProviders")
411
+ apiUsage ApiUsage[]
412
+ apiKeys ApiKey[]
413
+ `;
414
+ if (features.includes('rbac')) {
415
+ schemaContent += ` modelAccess ModelAccess[]
416
+ auditLogs AuditLog[]
417
+ `;
418
+ }
419
+ if (features.includes('chat')) {
420
+ schemaContent += ` conversations Conversation[]
421
+ `;
422
+ }
423
+ schemaContent += `}
424
+
425
+ model Account {
426
+ id String @id @default(cuid())
427
+ userId String
428
+ type String
429
+ provider String
430
+ providerAccountId String
431
+ refresh_token String? @db.Text
432
+ access_token String? @db.Text
433
+ expires_at Int?
434
+ token_type String?
435
+ scope String?
436
+ id_token String? @db.Text
437
+ session_state String?
438
+
439
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
440
+
441
+ @@unique([provider, providerAccountId])
442
+ }
443
+
444
+ model Session {
445
+ id String @id @default(cuid())
446
+ sessionToken String @unique
447
+ userId String
448
+ expires DateTime
449
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
450
+ }
451
+
452
+ model VerificationToken {
453
+ identifier String
454
+ token String @unique
455
+ expires DateTime
456
+
457
+ @@unique([identifier, token])
458
+ }
459
+
460
+ model ApiKey {
461
+ id String @id @default(cuid())
462
+ name String
463
+ keyHash String @unique
464
+ userId String
465
+ scopes String[] @default([])
466
+ revoked Boolean @default(false)
467
+ lastUsedAt DateTime?
468
+ expiresAt DateTime?
469
+ createdAt DateTime @default(now())
470
+ updatedAt DateTime @updatedAt
471
+
472
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
473
+
474
+ @@index([userId])
475
+ }
476
+
477
+ `;
478
+ }
479
+ // Add RBAC models
480
+ if (features.includes('rbac')) {
481
+ schemaContent += `model Role {
482
+ id String @id @default(cuid())
483
+ name String @unique
484
+ description String?
485
+ permissions String[]
486
+ createdAt DateTime @default(now())
487
+ updatedAt DateTime @updatedAt
488
+
489
+ users UserRole[]
490
+ }
491
+
492
+ model UserRole {
493
+ userId String
494
+ roleId String
495
+
496
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
497
+ role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
498
+
499
+ @@id([userId, roleId])
500
+ }
501
+
502
+ `;
503
+ }
504
+ // ── PROVIDER MODELS (ALWAYS included — core infrastructure) ──────────
505
+ schemaContent += `// === Provider Management (Core Infrastructure) ===
506
+
507
+ model Provider {
508
+ id String @id @default(cuid())
509
+ name String
510
+ type String // "openai", "anthropic", "ollama", "google", "custom"
511
+ description String?
512
+ baseUrl String?
513
+ apiKey String? @db.Text // AES-256-GCM encrypted, null for keyless providers (e.g. Ollama)
514
+ config Json @default("{}")
515
+ status String @default("active")
516
+ isDefault Boolean @default(false)
517
+ priority Int @default(0)
518
+ tags String[]
519
+ createdAt DateTime @default(now())
520
+ updatedAt DateTime @updatedAt
521
+ createdBy String?
522
+
523
+ models Model[]
524
+ health ProviderHealth?
525
+ apiUsage ApiUsage[]
526
+ ${features.includes('chat') ? ' conversations Conversation[]\n' : ''}${features.includes('auth') ? ' creator User? @relation("CreatedProviders", fields: [createdBy], references: [id])\n' : ''}
527
+ @@index([type])
528
+ @@index([status])
529
+ }
530
+
531
+ model Model {
532
+ id String @id @default(cuid())
533
+ providerId String
534
+ modelId String // e.g. "gpt-4", "claude-3-sonnet"
535
+ name String
536
+ description String?
537
+ capabilities String[] // ["chat", "embedding", "image", "vision"]
538
+ contextWindow Int @default(4096)
539
+ maxOutputTokens Int?
540
+ inputCost Float @default(0) // $ per 1M tokens
541
+ outputCost Float @default(0)
542
+ isAvailable Boolean @default(true)
543
+ isDeprecated Boolean @default(false)
544
+
545
+ provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
546
+
547
+ @@unique([providerId, modelId])
548
+ @@index([providerId])
549
+ }
550
+
551
+ model ProviderHealth {
552
+ id String @id @default(cuid())
553
+ providerId String @unique
554
+ status String @default("unknown") // "healthy", "degraded", "unhealthy"
555
+ responseTime Int? // ms
556
+ lastCheck DateTime @default(now())
557
+ errorMessage String?
558
+ modelsAvailable Int @default(0)
559
+ chatAvailable Boolean @default(false)
560
+ embeddingAvailable Boolean @default(false)
561
+ apiKeyValid Boolean @default(false)
562
+
563
+ provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
564
+ }
565
+
566
+ model ApiUsage {
567
+ id String @id @default(cuid())
568
+ userId String
569
+ providerId String?
570
+ model String
571
+ endpoint String
572
+ promptTokens Int @default(0)
573
+ completionTokens Int @default(0)
574
+ totalTokens Int @default(0)
575
+ tokensUsed Int @default(0)
576
+ creditsUsed Int @default(0)
577
+ cost Float @default(0)
578
+ success Boolean @default(true)
579
+ errorMessage String?
580
+ responseTime Int @default(0) // ms
581
+ createdAt DateTime @default(now())
582
+
583
+ ${features.includes('auth') ? ' user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n' : ''} provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
584
+
585
+ @@index([userId])
586
+ @@index([providerId])
587
+ @@index([createdAt])
588
+ }
589
+
590
+ `;
591
+ // Add ModelAccess when auth + rbac are enabled (for requireModelPermission)
592
+ if (features.includes('auth') && features.includes('rbac')) {
593
+ schemaContent += `model ModelAccess {
594
+ id String @id @default(cuid())
595
+ userId String
596
+ modelId String
597
+ granted Boolean @default(true)
598
+ createdAt DateTime @default(now())
599
+ updatedAt DateTime @updatedAt
600
+
601
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
602
+
603
+ @@unique([userId, modelId])
604
+ @@index([userId])
605
+ @@index([modelId])
606
+ }
607
+
608
+ model AuditLog {
609
+ id String @id @default(cuid())
610
+ action String
611
+ userId String
612
+ targetType String?
613
+ targetId String?
614
+ metadata Json?
615
+ ipAddress String?
616
+ createdAt DateTime @default(now())
617
+
618
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
619
+
620
+ @@index([userId])
621
+ @@index([action])
622
+ @@index([createdAt])
623
+ }
624
+
625
+ `;
626
+ }
627
+ // Add Prompt Templates
628
+ if (features.includes('prompts')) {
629
+ schemaContent += `model PromptTemplate {
630
+ id String @id @default(cuid())
631
+ name String @unique
632
+ category String
633
+ description String?
634
+ content String @db.Text
635
+ variables String[]
636
+ language String @default("en")
637
+ version Int @default(1)
638
+ isActive Boolean @default(true)
639
+ isDefault Boolean @default(false)
640
+ tags String[]
641
+ metadata Json?
642
+ createdBy String?
643
+ createdAt DateTime @default(now())
644
+ updatedAt DateTime @updatedAt
645
+
646
+ @@index([category])
647
+ @@index([isActive])
648
+ @@index([isDefault])
649
+ }
650
+
651
+ `;
652
+ }
653
+ // Add Conversation & Message models for chat feature
654
+ if (features.includes('chat')) {
655
+ schemaContent += `model Conversation {
656
+ id String @id @default(cuid())
657
+ userId String
658
+ title String @default("New Chat")
659
+ model String?
660
+ providerId String?
661
+ metadata Json?
662
+ archived Boolean @default(false)
663
+ createdAt DateTime @default(now())
664
+ updatedAt DateTime @updatedAt
665
+
666
+ ${features.includes('auth') ? ' user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n' : ''} provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
667
+ messages Message[]
668
+
669
+ @@index([userId])
670
+ @@index([archived])
671
+ @@index([providerId])
672
+ }
673
+
674
+ model Message {
675
+ id String @id @default(cuid())
676
+ conversationId String
677
+ role String
678
+ content String @db.Text
679
+ model String?
680
+ tokens Int?
681
+ createdAt DateTime @default(now())
682
+
683
+ conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
684
+
685
+ @@index([conversationId])
686
+ }
687
+
688
+ `;
689
+ }
690
+ // SystemSetting is always included (used by admin settings + app-settings API)
691
+ schemaContent += `model SystemSetting {
692
+ key String @id
693
+ value String @db.Text
694
+ updatedAt DateTime @updatedAt
695
+ }
696
+ `;
697
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'prisma/schema.prisma'), schemaContent);
698
+ // Apply SQLite compatibility transform if needed
699
+ if (sqlite) {
700
+ const schemaPath = path_1.default.join(targetDir, 'prisma/schema.prisma');
701
+ const schema = await fs_extra_1.default.readFile(schemaPath, 'utf-8');
702
+ const transformed = transformSchemaForSqlite(schema);
703
+ if (transformed !== schema) {
704
+ await fs_extra_1.default.writeFile(schemaPath, transformed);
705
+ }
706
+ }
707
+ }
708
+ /**
709
+ * Transform PostgreSQL-specific types to SQLite-compatible equivalents.
710
+ * - Removes @db.Text (SQLite strings are unbounded)
711
+ * - Converts Json/Json? → String/String?
712
+ * - Converts String[] → String (arrays not supported in SQLite)
713
+ * - Fixes array defaults: @default(["chat"]) → @default("chat")
714
+ */
715
+ function transformSchemaForSqlite(schema) {
716
+ let s = schema;
717
+ // Remove @db.Text (SQLite strings are unbounded)
718
+ s = s.replace(/\s+@db\.Text/g, '');
719
+ // Convert Json/Json? → String/String? with proper defaults
720
+ s = s.replace(/(\s+)Json(\??)(\s+)/g, '$1String$2$3');
721
+ // Convert array defaults BEFORE replacing String[] → String
722
+ // e.g. @default(["chat","vision"]) → @default("chat,vision")
723
+ // @default([]) → @default("[]")
724
+ s = s.replace(/@default\(\[([^\]]*)\]\)/g, (_match, inner) => {
725
+ const trimmed = inner.trim();
726
+ if (!trimmed)
727
+ return '@default("[]")'; // empty array → "[]"
728
+ const items = trimmed
729
+ .replace(/"/g, '')
730
+ .split(',')
731
+ .map((x) => x.trim())
732
+ .join(',');
733
+ return `@default("${items}")`;
734
+ });
735
+ // Convert String[] → String, adding @default("[]") if none exists
736
+ s = s.replace(/^(\s+\w+\s+)String\[\](.*)$/gm, (_match, prefix, rest) => {
737
+ const hasDefault = /@default\(/.test(rest);
738
+ return hasDefault ? `${prefix}String${rest}` : `${prefix}String${rest} @default("[]")`;
739
+ });
740
+ return s;
741
+ }
742
+ async function createEnvFiles(targetDir, features, sqlite) {
743
+ // Generate random 32-byte hex encryption key
744
+ const encryptionKey = Array.from({ length: 64 }, () => '0123456789abcdef'.charAt(Math.floor(Math.random() * 16))).join('');
745
+ // Generate random internal API token
746
+ const internalToken = 'chimerai_internal_' +
747
+ Array.from({ length: 64 }, () => '0123456789abcdef'.charAt(Math.floor(Math.random() * 16))).join('');
748
+ // Generate a random NextAuth secret (32 bytes hex = 64 chars)
749
+ const nextAuthSecret = Array.from({ length: 64 }, () => '0123456789abcdef'.charAt(Math.floor(Math.random() * 16))).join('');
750
+ const dbUrl = sqlite ? 'file:./dev.db' : 'postgresql://postgres:postgres@localhost:5432/myapp_db';
751
+ let envContent = `# Database${sqlite ? ' (SQLite — no Docker needed)' : ''}
752
+ DATABASE_URL=${dbUrl}
753
+
754
+ `;
755
+ if (features.includes('auth')) {
756
+ envContent += `# Authentication
757
+ NEXTAUTH_SECRET=${nextAuthSecret}
758
+ NEXTAUTH_URL=http://localhost:3000
759
+
760
+ `;
761
+ }
762
+ // ── Provider Infrastructure (ALWAYS included — core) ──────────
763
+ envContent += `# Provider Encryption (AES-256-GCM for API key storage)
764
+ PROVIDER_ENCRYPTION_KEY=${encryptionKey}
765
+
766
+ # Internal API Token (AI-Service ↔ Frontend communication)
767
+ INTERNAL_API_TOKEN=${internalToken}
768
+
769
+ # AI Service URL
770
+ AI_SERVICE_URL=http://localhost:8002
771
+
772
+ # AI Providers (Optional — can also be added via Provider Management UI)
773
+ OPENAI_API_KEY=
774
+ ANTHROPIC_API_KEY=
775
+ AZURE_OPENAI_API_KEY=
776
+ AZURE_OPENAI_ENDPOINT=
777
+
778
+ `;
779
+ if (features.includes('billing')) {
780
+ envContent += `# Stripe
781
+ STRIPE_PUBLISHABLE_KEY=
782
+ STRIPE_SECRET_KEY=
783
+ STRIPE_WEBHOOK_SECRET=
784
+
785
+ `;
786
+ }
787
+ // Widget / External Integration
788
+ envContent += `# CORS / Widget Configuration (External Chat Embedding)
789
+ # Comma-separated list of allowed origins for cross-origin requests (e.g. Blazor, Vue, React embeds)
790
+ # Use * to allow all origins (development only!)
791
+ # Example: CORS_ALLOWED_ORIGINS=https://my-blog.com,https://my-shop.com
792
+ CORS_ALLOWED_ORIGINS=
793
+ # Legacy alias (still supported): WIDGET_ALLOWED_ORIGINS
794
+
795
+ # Upstash Redis (optional — for rate-limiting in serverless/multi-instance)
796
+ # Without this, rate-limiting uses in-memory fallback (fine for single-instance)
797
+ # UPSTASH_REDIS_REST_URL=
798
+ # UPSTASH_REDIS_REST_TOKEN=
799
+
800
+ `;
801
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, '.env'), envContent);
802
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, '.env.example'), envContent
803
+ .replace(encryptionKey, 'your-64-char-hex-key')
804
+ .replace(nextAuthSecret, 'your-secret-key-change-in-production')
805
+ .replace(internalToken, 'chimerai_internal_your-token'));
806
+ }
807
+ async function copyFeatureFiles(targetDir, features) {
808
+ if (features.includes('auth')) {
809
+ // Create auth routes and pages using template generators
810
+ const nextAuthRoute = templates.generateNextAuthRoute();
811
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/auth/[...nextauth]'));
812
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/auth/[...nextauth]/route.ts'), nextAuthRoute);
813
+ const loginPage = templates.generateLoginPage();
814
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/auth/signin'));
815
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/auth/signin/page.tsx'), loginPage);
816
+ const sessionProvider = templates.generateSessionProvider();
817
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'components'));
818
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'components/SessionProvider.tsx'), sessionProvider);
819
+ const authLib = templates.generateAuthLib();
820
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib'));
821
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/auth.ts'), authLib);
822
+ const nextAuthTypes = templates.generateNextAuthTypes();
823
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'types'));
824
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'types/next-auth.d.ts'), nextAuthTypes);
825
+ // Dashboard page + layout (authenticated landing page after login)
826
+ const dashboardPage = templates.generateDashboardPage(features);
827
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/dashboard'));
828
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/dashboard/page.tsx'), dashboardPage);
829
+ const dashboardLayout = templates.generateDashboardLayout();
830
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/dashboard/layout.tsx'), dashboardLayout);
831
+ // Dashboard profile page
832
+ const profilePage = templates.generateProfilePage();
833
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/dashboard/profile'));
834
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/dashboard/profile/page.tsx'), profilePage);
835
+ // Dashboard settings page
836
+ const settingsPage = templates.generateSettingsPage();
837
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/dashboard/settings'));
838
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/dashboard/settings/page.tsx'), settingsPage);
839
+ // User profile API route
840
+ const userProfileRoute = templates.generateUserProfileRoute();
841
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/user/profile'));
842
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/user/profile/route.ts'), userProfileRoute);
843
+ // GDPR self-service routes
844
+ const gdprExportRoute = templates.generateGdprDataExportRoute();
845
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/user/data-export'));
846
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/user/data-export/route.ts'), gdprExportRoute);
847
+ const gdprDeleteRoute = templates.generateGdprAccountDeleteRoute();
848
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/user/account'));
849
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/user/account/route.ts'), gdprDeleteRoute);
850
+ }
851
+ // Health check endpoint (used by Docker healthcheck and monitoring)
852
+ const healthRoute = templates.generateHealthRoute();
853
+ // Next.js middleware (security headers + CORS for widget endpoints)
854
+ const middleware = templates.generateMiddleware();
855
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'middleware.ts'), middleware);
856
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/health'));
857
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/health/route.ts'), healthRoute);
858
+ // Create Prisma utilities using template generators
859
+ const prismaLib = templates.generatePrismaLib();
860
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib'));
861
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/prisma.ts'), prismaLib);
862
+ // ── Provider Infrastructure (ALWAYS generated — core) ─────────────
863
+ // Encryption library (AES-256-GCM)
864
+ const encryptionLib = templates.generateEncryptionLib();
865
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/encryption.ts'), encryptionLib);
866
+ // API key auth helper
867
+ const apiKeyAuthLib = templates.generateApiKeyAuthLib();
868
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/api-key-auth.ts'), apiKeyAuthLib);
869
+ // API protection middleware (auth checks, error responses, usage tracking)
870
+ // Only generated when auth is enabled (imports @/lib/auth)
871
+ if (features.includes('auth')) {
872
+ const apiProtectionLib = templates.generateApiProtectionLib();
873
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/api-protection.ts'), apiProtectionLib);
874
+ // Dual-auth resolve helper (session + API key)
875
+ const resolveAuth = templates.generateResolveAuth();
876
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib/auth'));
877
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/auth/resolve-auth.ts'), resolveAuth);
878
+ }
879
+ // ── Widget Infrastructure (embeddable chat for external apps) ──────
880
+ // Rate limiter (supports both session and API-key tiers)
881
+ const rateLimiter = templates.generateRateLimiter();
882
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/rate-limit.ts'), rateLimiter);
883
+ // Widget bundle (Web Component + Shadow DOM, self-contained JS)
884
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'public/widget'));
885
+ const widgetBundle = templates.generateWidgetBundle();
886
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'public/widget/chat.js'), widgetBundle);
887
+ const widgetLoader = templates.generateWidgetLoader();
888
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'public/widget/loader.js'), widgetLoader);
889
+ // API-Key management routes
890
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/v1/api-keys'));
891
+ const apiKeysRoute = templates.generateApiKeysRoute();
892
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/v1/api-keys/route.ts'), apiKeysRoute);
893
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/v1/api-keys/[id]'));
894
+ const apiKeyIdRoute = templates.generateApiKeyIdRoute();
895
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/v1/api-keys/[id]/route.ts'), apiKeyIdRoute);
896
+ // API-Key management page (settings UI)
897
+ if (features.includes('auth')) {
898
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/(app)/settings/api-keys'));
899
+ const apiKeyPage = templates.generateApiKeyManagementPage();
900
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/(app)/settings/api-keys/page.tsx'), apiKeyPage);
901
+ }
902
+ // Notify provider change utility
903
+ const notifyLib = templates.generateNotifyProviderChangeLib();
904
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/notify-provider-change.ts'), notifyLib);
905
+ // Provider CRUD route — /api/providers
906
+ const providerCrudRoute = templates.generateProviderCrudRoute();
907
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/providers'));
908
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/providers/route.ts'), providerCrudRoute);
909
+ // Provider [id] route — /api/providers/[id]
910
+ const providerIdRoute = templates.generateProviderIdRoute();
911
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/providers/[id]'));
912
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/providers/[id]/route.ts'), providerIdRoute);
913
+ // Provider test route — /api/providers/[id]/test
914
+ const providerTestRoute = templates.generateProviderTestRoute();
915
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/providers/[id]/test'));
916
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/providers/[id]/test/route.ts'), providerTestRoute);
917
+ // Provider sync route — /api/providers/[id]/sync
918
+ const providerSyncRoute = templates.generateProviderSyncRoute();
919
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/providers/[id]/sync'));
920
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/providers/[id]/sync/route.ts'), providerSyncRoute);
921
+ // Internal providers route — /api/internal/providers
922
+ const internalProvidersRoute = templates.generateInternalProvidersRoute();
923
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/internal/providers'));
924
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/internal/providers/route.ts'), internalProvidersRoute);
925
+ // Internal provider [id] route — /api/internal/providers/[id]
926
+ const internalProviderIdRoute = templates.generateInternalProviderIdRoute();
927
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/internal/providers/[id]'));
928
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/internal/providers/[id]/route.ts'), internalProviderIdRoute);
929
+ // Internal usage route — /api/internal/providers/[id]/usage
930
+ const internalUsageRoute = templates.generateInternalProviderUsageRoute();
931
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/internal/providers/[id]/usage'));
932
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/internal/providers/[id]/usage/route.ts'), internalUsageRoute);
933
+ // Provider Management page (always generated)
934
+ const providersPage = templates.generateModelProvidersPage();
935
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/dashboard/providers'));
936
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/dashboard/providers/page.tsx'), providersPage);
937
+ if (features.includes('admin')) {
938
+ // Admin layout with session check + admin role guard
939
+ const adminLayout = templates.generateAdminLayout();
940
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/admin'));
941
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/admin/layout.tsx'), adminLayout);
942
+ const adminDashboard = templates.generateAdminDashboardPage();
943
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/admin/page.tsx'), adminDashboard);
944
+ const adminUsers = templates.generateAdminUsersPage();
945
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/admin/users'));
946
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/admin/users/page.tsx'), adminUsers);
947
+ const adminRoles = templates.generateAdminRolesPage();
948
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/admin/roles'));
949
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/admin/roles/page.tsx'), adminRoles);
950
+ // Admin settings page
951
+ const adminSettings = templates.generateAdminSettingsPage();
952
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/admin/settings'));
953
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/admin/settings/page.tsx'), adminSettings);
954
+ // Audit log helper utility
955
+ const auditLogHelper = templates.generateAuditLogHelper();
956
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib'));
957
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/audit.ts'), auditLogHelper);
958
+ // Admin audit logs page
959
+ const adminLogs = templates.generateAdminLogsPage();
960
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/admin/logs'));
961
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/admin/logs/page.tsx'), adminLogs);
962
+ // Audit logs API route
963
+ const auditLogsRoute = templates.generateAuditLogRoute();
964
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/admin/audit-logs'));
965
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/admin/audit-logs/route.ts'), auditLogsRoute);
966
+ // Create admin API routes
967
+ const adminUsersRoute = templates.generateAdminUsersRoute();
968
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/admin/users'));
969
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/admin/users/route.ts'), adminUsersRoute);
970
+ const adminUsersIdRoute = templates.generateAdminUsersIdRoute();
971
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/admin/users/[id]'));
972
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/admin/users/[id]/route.ts'), adminUsersIdRoute);
973
+ const adminRolesRoute = templates.generateAdminRolesRoute();
974
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/admin/roles'));
975
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/admin/roles/route.ts'), adminRolesRoute);
976
+ const adminRolesIdRoute = templates.generateAdminRolesIdRoute();
977
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/admin/roles/[id]'));
978
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/admin/roles/[id]/route.ts'), adminRolesIdRoute);
979
+ // Admin settings API route (GET, PUT)
980
+ const adminSettingsRoute = templates.generateAdminSettingsRoute();
981
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/admin/settings'));
982
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/admin/settings/route.ts'), adminSettingsRoute);
983
+ // Public app-settings API route (used by useAppName hook)
984
+ const appSettingsRoute = templates.generateAppSettingsRoute();
985
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/app-settings'));
986
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/app-settings/route.ts'), appSettingsRoute);
987
+ // useAppName client hook
988
+ const useAppNameHook = templates.generateUseAppNameHook();
989
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib'));
990
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/use-app-name.ts'), useAppNameHook);
991
+ // ── RBAC Permission utilities (required by admin API routes) ───
992
+ const permissionsLib = `/**
993
+ * Permission utility functions
994
+ * Handles permission checks and role-based access control
995
+ */
996
+
997
+ export const AVAILABLE_PERMISSIONS = [
998
+ 'users:read',
999
+ 'users:write',
1000
+ 'users:delete',
1001
+ 'roles:read',
1002
+ 'roles:write',
1003
+ 'roles:delete',
1004
+ 'settings:read',
1005
+ 'settings:write',
1006
+ 'admin:*',
1007
+ ] as const;
1008
+
1009
+ export type Permission = typeof AVAILABLE_PERMISSIONS[number];
1010
+
1011
+ interface User {
1012
+ id: string;
1013
+ email: string;
1014
+ roles?: Array<{ permissions: string[] }>;
1015
+ }
1016
+
1017
+ export function hasPermission(user: User | null, permission: string): boolean {
1018
+ if (!user || !user.roles) return false;
1019
+ const allPermissions = user.roles.flatMap(role => role.permissions || []);
1020
+
1021
+ // Tier 1: Super-Wildcard — '*' matcht ALLES
1022
+ if (allPermissions.includes('*')) return true;
1023
+
1024
+ // Tier 2: Kategorie-Wildcard — 'admin:*' matcht 'admin:users:read', 'admin:roles:write', etc.
1025
+ for (const perm of allPermissions) {
1026
+ if (perm.endsWith(':*')) {
1027
+ const prefix = perm.slice(0, -1); // 'admin:*' → 'admin:'
1028
+ if (permission.startsWith(prefix)) return true;
1029
+ }
1030
+ }
1031
+
1032
+ // Tier 3: Exakter Match
1033
+ return allPermissions.includes(permission);
1034
+ }
1035
+
1036
+ export function hasAnyPermission(user: User | null, permissions: string[]): boolean {
1037
+ if (!user) return false;
1038
+ return permissions.some(permission => hasPermission(user, permission));
1039
+ }
1040
+
1041
+ export function hasAllPermissions(user: User | null, permissions: string[]): boolean {
1042
+ if (!user) return false;
1043
+ return permissions.every(permission => hasPermission(user, permission));
1044
+ }
1045
+
1046
+ export function getUserPermissions(user: User | null): string[] {
1047
+ if (!user || !user.roles) return [];
1048
+ return [...new Set(user.roles.flatMap(role => role.permissions || []))];
1049
+ }
1050
+
1051
+ export function isValidPermission(permission: string): boolean {
1052
+ return AVAILABLE_PERMISSIONS.includes(permission as Permission);
1053
+ }
1054
+ `;
1055
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/permissions.ts'), permissionsLib);
1056
+ const requirePermissionLib = `import { getServerSession } from 'next-auth';
1057
+ import { NextResponse } from 'next/server';
1058
+ import { authOptions } from '@/lib/auth';
1059
+ import { hasPermission } from '@/lib/permissions';
1060
+
1061
+ /**
1062
+ * Server-side permission check for API routes
1063
+ * Returns NextResponse with 401/403 if check fails, null if OK
1064
+ */
1065
+ export async function requirePermission(permission: string) {
1066
+ const session = await getServerSession(authOptions);
1067
+
1068
+ if (!session || !session.user) {
1069
+ return NextResponse.json(
1070
+ { error: 'Unauthorized - Please sign in' },
1071
+ { status: 401 }
1072
+ );
1073
+ }
1074
+
1075
+ const user = await getServerSessionWithPermissions();
1076
+
1077
+ if (!user || !hasPermission(user as any, permission)) {
1078
+ return NextResponse.json(
1079
+ { error: \`Forbidden - Required permission: \${permission}\` },
1080
+ { status: 403 }
1081
+ );
1082
+ }
1083
+
1084
+ return null;
1085
+ }
1086
+
1087
+ async function getServerSessionWithPermissions() {
1088
+ const session = await getServerSession(authOptions);
1089
+ if (!session?.user?.email) return null;
1090
+
1091
+ const { prisma } = await import('@/lib/prisma');
1092
+ const user = await prisma.user.findUnique({
1093
+ where: { email: session.user.email },
1094
+ include: {
1095
+ roles: {
1096
+ select: {
1097
+ role: {
1098
+ select: {
1099
+ id: true,
1100
+ name: true,
1101
+ permissions: true
1102
+ }
1103
+ }
1104
+ }
1105
+ }
1106
+ }
1107
+ });
1108
+
1109
+ if (!user) return null;
1110
+
1111
+ // Flatten UserRole[] → { permissions: string[] }[]
1112
+ return {
1113
+ ...user,
1114
+ roles: (user.roles as any[]).map((ur: any) => ur.role),
1115
+ };
1116
+ }
1117
+ `;
1118
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib/auth'));
1119
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/auth/require-permission.ts'), requirePermissionLib);
1120
+ }
1121
+ if (features.includes('prompts')) {
1122
+ const promptsPage = templates.generatePromptManagementPage();
1123
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/dashboard/prompts'));
1124
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/dashboard/prompts/page.tsx'), promptsPage);
1125
+ // Prompts API routes
1126
+ const promptsRoute = templates.generatePromptsRoute();
1127
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/prompts'));
1128
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/prompts/route.ts'), promptsRoute);
1129
+ const promptsIdRoute = templates.generatePromptsIdRoute();
1130
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/prompts/[id]'));
1131
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/prompts/[id]/route.ts'), promptsIdRoute);
1132
+ }
1133
+ if (features.includes('chat')) {
1134
+ // Modular chat components (useChat hook + individual components)
1135
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'components/chat'));
1136
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'components/chat/use-chat.ts'), templates.generateUseChatHook());
1137
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'components/chat/chat-message.tsx'), templates.generateChatMessage());
1138
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'components/chat/chat-input.tsx'), templates.generateChatInput());
1139
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'components/chat/chat-sidebar.tsx'), templates.generateChatSidebar());
1140
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'components/chat/model-selector.tsx'), templates.generateModelSelector());
1141
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'components/chat/index.ts'), [
1142
+ "export { useChat } from './use-chat';",
1143
+ "export { ChatMessage } from './chat-message';",
1144
+ "export { ChatInput } from './chat-input';",
1145
+ "export { ChatSidebar } from './chat-sidebar';",
1146
+ "export { ModelSelector } from './model-selector';",
1147
+ "export type { ChatMessageData, MessageActions, ConversationItem, ModelOption } from './use-chat';",
1148
+ ].join('\n') + '\n');
1149
+ // Chat page
1150
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/(app)/chat'));
1151
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/(app)/chat/page.tsx'), templates.generateChatPage());
1152
+ // API routes
1153
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/v1/chat/stream'));
1154
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/v1/chat/stream/route.ts'), templates.generateChatStreamRouteWithPersistence());
1155
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/conversations'));
1156
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/conversations/route.ts'), templates.generateConversationsRoute());
1157
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/conversations/[id]'));
1158
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/conversations/[id]/route.ts'), templates.generateConversationDetailRoute());
1159
+ // Models listing route (used by ModelSelector in chat — session-only, internal)
1160
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/models'));
1161
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/models/route.ts'), templates.generateModelsRoute());
1162
+ // Public v1 models route (dual-auth: session OR API-key, for widgets/external apps)
1163
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/v1/models'));
1164
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/v1/models/route.ts'), templates.generateV1ModelsRoute());
1165
+ }
1166
+ // ── Billing / Stripe (when selected) ────────────────────────────
1167
+ if (features.includes('billing')) {
1168
+ const stripeLib = templates.generateStripeLib();
1169
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib'));
1170
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/stripe.ts'), stripeLib);
1171
+ const billingPage = templates.generateBillingPage();
1172
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/billing'));
1173
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/billing/page.tsx'), billingPage);
1174
+ const checkoutRoute = templates.generateCheckoutRoute();
1175
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/billing/checkout'));
1176
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/billing/checkout/route.ts'), checkoutRoute);
1177
+ const portalRoute = templates.generatePortalRoute();
1178
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/billing/portal'));
1179
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/billing/portal/route.ts'), portalRoute);
1180
+ const subscriptionRoute = templates.generateSubscriptionRoute();
1181
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/billing/subscription'));
1182
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/billing/subscription/route.ts'), subscriptionRoute);
1183
+ const stripeWebhook = templates.generateStripeWebhookRoute();
1184
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/webhooks/stripe'));
1185
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/webhooks/stripe/route.ts'), stripeWebhook);
1186
+ }
1187
+ // ── RAG / Vector Store (when selected) ──────────────────────────
1188
+ if (features.includes('rag')) {
1189
+ const ragLib = templates.generateRagLib();
1190
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib'));
1191
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/rag.ts'), ragLib);
1192
+ const ragPage = templates.generateRagPage();
1193
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/rag'));
1194
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/rag/page.tsx'), ragPage);
1195
+ const ragUploadRoute = templates.generateRagUploadRoute();
1196
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/rag'));
1197
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/rag/route.ts'), ragUploadRoute);
1198
+ const ragQueryRoute = templates.generateRagQueryRoute();
1199
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/rag/query'));
1200
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/rag/query/route.ts'), ragQueryRoute);
1201
+ const ragStatsRoute = templates.generateRagStatsRoute();
1202
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/rag/stats'));
1203
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/rag/stats/route.ts'), ragStatsRoute);
1204
+ const ragClearRoute = templates.generateRagClearRoute();
1205
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/rag/clear'));
1206
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/rag/clear/route.ts'), ragClearRoute);
1207
+ }
1208
+ }
1209
+ async function createSeedScript(targetDir, features, sqlite) {
1210
+ // Generate seed script with feature-specific configuration
1211
+ const seedScript = `import { PrismaClient } from '@prisma/client';
1212
+ import * as bcrypt from 'bcryptjs';
1213
+ import crypto from 'crypto';
1214
+
1215
+ const prisma = new PrismaClient();
1216
+
1217
+ // ── Encryption helpers (same as lib/encryption.ts) ──────────────
1218
+ function getKey(): Buffer {
1219
+ const key = process.env.PROVIDER_ENCRYPTION_KEY;
1220
+ if (!key) throw new Error('PROVIDER_ENCRYPTION_KEY required for seeding');
1221
+ if (key.length === 64 && /^[0-9a-fA-F]+$/.test(key)) {
1222
+ return Buffer.from(key, 'hex');
1223
+ }
1224
+ return crypto.createHash('sha256').update(key).digest();
1225
+ }
1226
+
1227
+ function encrypt(text: string): string {
1228
+ if (!text) return '';
1229
+ const key = getKey();
1230
+ const iv = crypto.randomBytes(16);
1231
+ const cipher = crypto.createCipheriv('aes-256-gcm', key, iv, { authTagLength: 16 });
1232
+ let encrypted = cipher.update(text, 'utf8', 'base64');
1233
+ encrypted += cipher.final('base64');
1234
+ const authTag = cipher.getAuthTag();
1235
+ return iv.toString('base64') + ':' + authTag.toString('base64') + ':' + encrypted;
1236
+ }
1237
+ // ────────────────────────────────────────────────────────────────
1238
+
1239
+ async function main() {
1240
+ console.log('🌱 Seeding database...');
1241
+
1242
+ // Create default admin user
1243
+ const hashedPassword = await bcrypt.hash('admin123', 10);
1244
+ const admin = await prisma.user.upsert({
1245
+ where: { email: 'admin@example.com' },
1246
+ update: {},
1247
+ create: {
1248
+ email: 'admin@example.com',
1249
+ name: 'Admin User',
1250
+ password: hashedPassword,
1251
+ },
1252
+ });
1253
+
1254
+ console.log('✅ Admin user created: admin@example.com / admin123');
1255
+ ${features.includes('rbac')
1256
+ ? `
1257
+ ${sqlite
1258
+ ? ` // Create default roles (SQLite-compatible: individual upserts)
1259
+ for (const role of [
1260
+ { name: 'admin', description: 'Full system access', permissions: JSON.stringify(['*']) },
1261
+ { name: 'user', description: 'Regular user access', permissions: JSON.stringify(['chat:read', 'chat:write', 'profile:read']) },
1262
+ { name: 'viewer', description: 'Read-only access', permissions: JSON.stringify(['chat:read']) },
1263
+ ]) {
1264
+ await prisma.role.upsert({
1265
+ where: { name: role.name },
1266
+ update: {},
1267
+ create: role,
1268
+ });
1269
+ }`
1270
+ : ` // Create default roles
1271
+ await prisma.role.createMany({
1272
+ data: [
1273
+ {
1274
+ name: 'admin',
1275
+ description: 'Full system access',
1276
+ permissions: ['*'],
1277
+ },
1278
+ {
1279
+ name: 'user',
1280
+ description: 'Regular user access',
1281
+ permissions: ['chat:read', 'chat:write', 'profile:read'],
1282
+ },
1283
+ {
1284
+ name: 'viewer',
1285
+ description: 'Read-only access',
1286
+ permissions: ['chat:read'],
1287
+ },
1288
+ ],
1289
+ skipDuplicates: true,
1290
+ });`}
1291
+
1292
+ console.log('✅ Default roles created');
1293
+
1294
+ // Assign admin role to admin user
1295
+ const adminRole = await prisma.role.findUnique({ where: { name: 'admin' } });
1296
+ if (adminRole) {
1297
+ await prisma.userRole.upsert({
1298
+ where: { userId_roleId: { userId: admin.id, roleId: adminRole.id } },
1299
+ update: {},
1300
+ create: { userId: admin.id, roleId: adminRole.id },
1301
+ });
1302
+ console.log('✅ Admin user assigned admin role');
1303
+ }
1304
+ `
1305
+ : ''}
1306
+ // ── Seed Providers (if API keys are in .env) ────────────────────
1307
+ const openaiKey = process.env.OPENAI_API_KEY;
1308
+ const anthropicKey = process.env.ANTHROPIC_API_KEY;
1309
+
1310
+ if (openaiKey) {
1311
+ const provider = await prisma.provider.upsert({
1312
+ where: { id: 'seed-openai' },
1313
+ update: {},
1314
+ create: {
1315
+ id: 'seed-openai',
1316
+ name: 'OpenAI',
1317
+ type: 'openai',
1318
+ description: 'OpenAI API (seeded from .env)',
1319
+ baseUrl: 'https://api.openai.com/v1',
1320
+ apiKey: encrypt(openaiKey),
1321
+ config: ${sqlite ? `JSON.stringify({ defaultModel: 'gpt-4o-mini' })` : `{ defaultModel: 'gpt-4o-mini' }`},
1322
+ status: 'active',
1323
+ isDefault: true,
1324
+ priority: 0,
1325
+ createdBy: admin.id,
1326
+ },
1327
+ });
1328
+
1329
+ // Create default OpenAI models
1330
+ ${sqlite
1331
+ ? ` for (const m of [
1332
+ { providerId: provider.id, modelId: 'gpt-4o', name: 'GPT-4o', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 128000, inputCost: 2.5, outputCost: 10 },
1333
+ { providerId: provider.id, modelId: 'gpt-4o-mini', name: 'GPT-4o Mini', capabilities: JSON.stringify(['chat']), contextWindow: 128000, inputCost: 0.15, outputCost: 0.6 },
1334
+ { providerId: provider.id, modelId: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 128000, inputCost: 10, outputCost: 30 },
1335
+ { providerId: provider.id, modelId: 'text-embedding-3-small', name: 'Embedding 3 Small', capabilities: JSON.stringify(['embedding']), contextWindow: 8191, inputCost: 0.02, outputCost: 0 },
1336
+ { providerId: provider.id, modelId: 'text-embedding-3-large', name: 'Embedding 3 Large', capabilities: JSON.stringify(['embedding']), contextWindow: 8191, inputCost: 0.13, outputCost: 0 },
1337
+ ]) {
1338
+ try { await prisma.model.create({ data: m }); } catch { /* skip duplicate */ }
1339
+ }`
1340
+ : ` await prisma.model.createMany({
1341
+ data: [
1342
+ { providerId: provider.id, modelId: 'gpt-4o', name: 'GPT-4o', capabilities: ['chat', 'vision'], contextWindow: 128000, inputCost: 2.5, outputCost: 10 },
1343
+ { providerId: provider.id, modelId: 'gpt-4o-mini', name: 'GPT-4o Mini', capabilities: ['chat'], contextWindow: 128000, inputCost: 0.15, outputCost: 0.6 },
1344
+ { providerId: provider.id, modelId: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: ['chat', 'vision'], contextWindow: 128000, inputCost: 10, outputCost: 30 },
1345
+ { providerId: provider.id, modelId: 'text-embedding-3-small', name: 'Embedding 3 Small', capabilities: ['embedding'], contextWindow: 8191, inputCost: 0.02, outputCost: 0 },
1346
+ { providerId: provider.id, modelId: 'text-embedding-3-large', name: 'Embedding 3 Large', capabilities: ['embedding'], contextWindow: 8191, inputCost: 0.13, outputCost: 0 },
1347
+ ],
1348
+ skipDuplicates: true,
1349
+ });`}
1350
+
1351
+ console.log('✅ OpenAI provider seeded with models');
1352
+ }
1353
+
1354
+ if (anthropicKey) {
1355
+ const provider = await prisma.provider.upsert({
1356
+ where: { id: 'seed-anthropic' },
1357
+ update: {},
1358
+ create: {
1359
+ id: 'seed-anthropic',
1360
+ name: 'Anthropic',
1361
+ type: 'anthropic',
1362
+ description: 'Anthropic Claude API (seeded from .env)',
1363
+ baseUrl: 'https://api.anthropic.com/v1',
1364
+ apiKey: encrypt(anthropicKey),
1365
+ config: ${sqlite ? `JSON.stringify({ defaultModel: 'claude-sonnet-4-20250514' })` : `{ defaultModel: 'claude-sonnet-4-20250514' }`},
1366
+ status: 'active',
1367
+ isDefault: false,
1368
+ priority: 1,
1369
+ createdBy: admin.id,
1370
+ },
1371
+ });
1372
+
1373
+ ${sqlite
1374
+ ? ` for (const m of [
1375
+ { providerId: provider.id, modelId: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 200000, inputCost: 3, outputCost: 15 },
1376
+ { providerId: provider.id, modelId: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 200000, inputCost: 3, outputCost: 15 },
1377
+ { providerId: provider.id, modelId: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', capabilities: JSON.stringify(['chat']), contextWindow: 200000, inputCost: 0.25, outputCost: 1.25 },
1378
+ ]) {
1379
+ try { await prisma.model.create({ data: m }); } catch { /* skip duplicate */ }
1380
+ }`
1381
+ : ` await prisma.model.createMany({
1382
+ data: [
1383
+ { providerId: provider.id, modelId: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', capabilities: ['chat', 'vision'], contextWindow: 200000, inputCost: 3, outputCost: 15 },
1384
+ { providerId: provider.id, modelId: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', capabilities: ['chat', 'vision'], contextWindow: 200000, inputCost: 3, outputCost: 15 },
1385
+ { providerId: provider.id, modelId: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', capabilities: ['chat'], contextWindow: 200000, inputCost: 0.25, outputCost: 1.25 },
1386
+ ],
1387
+ skipDuplicates: true,
1388
+ });`}
1389
+
1390
+ console.log('✅ Anthropic provider seeded with models');
1391
+ }
1392
+
1393
+ if (!openaiKey && !anthropicKey) {
1394
+ console.log('ℹ️ No API keys in .env — skip provider seeding. Add via Provider Management UI.');
1395
+ }
1396
+
1397
+ console.log('🎉 Seeding completed!');
1398
+ }
1399
+
1400
+ main()
1401
+ .catch((e) => {
1402
+ console.error('❌ Seeding failed:', e);
1403
+ process.exit(1);
1404
+ })
1405
+ .finally(async () => {
1406
+ await prisma.$disconnect();
1407
+ });
1408
+ `;
1409
+ const seedDest = path_1.default.join(targetDir, 'prisma/seed.ts');
1410
+ await fs_extra_1.default.ensureDir(path_1.default.dirname(seedDest));
1411
+ await fs_extra_1.default.writeFile(seedDest, seedScript);
1412
+ console.log(chalk_1.default.green(' ✓ Created seed script'));
1413
+ }
1414
+ async function createDockerCompose(targetDir) {
1415
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'docker-compose.yml'), (0, index_js_1.generateDockerComposeDev)());
1416
+ }
1417
+ async function createInstallScripts(targetDir) {
1418
+ // Windows install.bat
1419
+ const installBat = `@echo off
1420
+ REM Installation Script for ChimerAI Project
1421
+ echo.
1422
+ echo ================================================
1423
+ echo ChimerAI Project Setup
1424
+ echo ================================================
1425
+ echo.
1426
+
1427
+ REM Check Node.js
1428
+ where node >nul 2>nul
1429
+ if %ERRORLEVEL% NEQ 0 (
1430
+ echo [ERROR] Node.js is not installed
1431
+ pause
1432
+ exit /b 1
1433
+ )
1434
+
1435
+ REM Check Docker
1436
+ where docker >nul 2>nul
1437
+ if %ERRORLEVEL% NEQ 0 (
1438
+ echo [ERROR] Docker is not installed
1439
+ pause
1440
+ exit /b 1
1441
+ )
1442
+
1443
+ echo [1/5] Installing dependencies...
1444
+ call npm install
1445
+ if %ERRORLEVEL% NEQ 0 (
1446
+ echo [ERROR] Failed to install dependencies
1447
+ pause
1448
+ exit /b 1
1449
+ )
1450
+
1451
+ echo [2/5] Starting Docker containers...
1452
+ call docker-compose up -d
1453
+ if %ERRORLEVEL% NEQ 0 (
1454
+ echo [ERROR] Failed to start Docker
1455
+ pause
1456
+ exit /b 1
1457
+ )
1458
+
1459
+ echo [3/5] Waiting for database...
1460
+ timeout /t 5 /nobreak >nul
1461
+
1462
+ echo [4/5] Setting up database...
1463
+ call npm run db:push
1464
+ if %ERRORLEVEL% NEQ 0 (
1465
+ echo [ERROR] Failed to setup database
1466
+ pause
1467
+ exit /b 1
1468
+ )
1469
+
1470
+ echo [5/5] Seeding database...
1471
+ call npm run db:seed
1472
+ if %ERRORLEVEL% NEQ 0 (
1473
+ echo [WARNING] Seeding failed, but you can continue
1474
+ )
1475
+
1476
+ echo.
1477
+ echo ================================================
1478
+ echo Setup completed successfully!
1479
+ echo ================================================
1480
+ echo.
1481
+ echo Next steps:
1482
+ echo npm run dev
1483
+ echo Open: http://localhost:3001
1484
+ echo.
1485
+ echo Login with:
1486
+ echo Email: admin@example.com
1487
+ echo Password: admin123
1488
+ echo.
1489
+ pause
1490
+ `;
1491
+ // Linux/macOS install.sh
1492
+ const installSh = `#!/bin/bash
1493
+ set -e
1494
+
1495
+ echo ""
1496
+ echo "================================================"
1497
+ echo " ChimerAI Project Setup"
1498
+ echo "================================================"
1499
+ echo ""
1500
+
1501
+ # Check Node.js
1502
+ if ! command -v node &> /dev/null; then
1503
+ echo "[ERROR] Node.js is not installed"
1504
+ exit 1
1505
+ fi
1506
+
1507
+ # Check Docker
1508
+ if ! command -v docker &> /dev/null; then
1509
+ echo "[ERROR] Docker is not installed"
1510
+ exit 1
1511
+ fi
1512
+
1513
+ echo "[1/5] Installing dependencies..."
1514
+ npm install
1515
+
1516
+ echo "[2/5] Starting Docker containers..."
1517
+ docker-compose up -d
1518
+
1519
+ echo "[3/5] Waiting for database..."
1520
+ sleep 5
1521
+
1522
+ echo "[4/5] Setting up database..."
1523
+ npm run db:push
1524
+
1525
+ echo "[5/5] Seeding database..."
1526
+ npm run db:seed || echo "[WARNING] Seeding failed, but you can continue"
1527
+
1528
+ echo ""
1529
+ echo "================================================"
1530
+ echo " Setup completed successfully!"
1531
+ echo "================================================"
1532
+ echo ""
1533
+ echo "Next steps:"
1534
+ echo " npm run dev"
1535
+ echo " Open: http://localhost:3001"
1536
+ echo ""
1537
+ echo "Login with:"
1538
+ echo " Email: admin@example.com"
1539
+ echo " Password: admin123"
1540
+ echo ""
1541
+ `;
1542
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'install.bat'), installBat);
1543
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'install.sh'), installSh);
1544
+ // Make install.sh executable
1545
+ try {
1546
+ await fs_extra_1.default.chmod(path_1.default.join(targetDir, 'install.sh'), '755');
1547
+ }
1548
+ catch (error) {
1549
+ // Ignore on Windows
1550
+ }
1551
+ }
1552
+ async function createReadme(targetDir, projectName, features) {
1553
+ let readme = `# ${projectName}
1554
+
1555
+ Built with ChimerAI Kickstart
1556
+
1557
+ ## Features
1558
+
1559
+ `;
1560
+ const featureDescriptions = {
1561
+ auth: '- 🔐 Authentication with NextAuth',
1562
+ rbac: '- 👥 Role-Based Access Control (RBAC)',
1563
+ 'model-providers': '- 🔌 AI Model Provider Management',
1564
+ prompts: '- 📝 Prompt Template System',
1565
+ chat: '- 💬 AI Chat Interface',
1566
+ rag: '- 🔍 RAG / Vector Store',
1567
+ billing: '- 💳 Stripe Billing Integration',
1568
+ analytics: '- 📊 API Usage Analytics',
1569
+ admin: '- 🎨 Admin Dashboard',
1570
+ };
1571
+ features.forEach((f) => {
1572
+ if (featureDescriptions[f]) {
1573
+ readme += featureDescriptions[f] + '\n';
1574
+ }
1575
+ });
1576
+ readme += `
1577
+
1578
+ ## Getting Started
1579
+
1580
+ ### Prerequisites
1581
+
1582
+ - Node.js 20+
1583
+ - Docker Desktop
1584
+ - npm
1585
+
1586
+ ### Quick Start (Recommended)
1587
+
1588
+ **Windows:**
1589
+ \`\`\`bash
1590
+ install.bat
1591
+ \`\`\`
1592
+
1593
+ **Linux/macOS:**
1594
+ \`\`\`bash
1595
+ ./install.sh
1596
+ \`\`\`
1597
+
1598
+ The install script will automatically:
1599
+ - Install dependencies
1600
+ - Start Docker containers
1601
+ - Setup and seed the database
1602
+ - Show you the next steps
1603
+
1604
+ ### Manual Installation
1605
+
1606
+ If you prefer to run commands manually:
1607
+
1608
+ \`\`\`bash
1609
+ # Install dependencies
1610
+ npm install
1611
+
1612
+ # Start Docker services
1613
+ docker-compose up -d
1614
+
1615
+ # Setup database
1616
+ npm run db:push
1617
+ npm run db:seed
1618
+
1619
+ # Start development server
1620
+ npm run dev
1621
+ \`\`\`
1622
+
1623
+ Open [http://localhost:3001](http://localhost:3001) in your browser.
1624
+
1625
+ ### Default Admin Credentials
1626
+
1627
+ - Email: admin@example.com
1628
+ - Password: admin123
1629
+
1630
+ ⚠️ Change these in production!
1631
+
1632
+ ## Available Scripts
1633
+
1634
+ - \`pnpm dev\` - Start development server
1635
+ - \`pnpm build\` - Build for production
1636
+ - \`pnpm start\` - Start production server
1637
+ - \`pnpm lint\` - Run linter
1638
+ - \`pnpm db:push\` - Push database schema
1639
+ - \`pnpm db:seed\` - Seed database
1640
+ - \`pnpm db:studio\` - Open Prisma Studio
1641
+
1642
+ ## Tech Stack
1643
+
1644
+ - **Framework**: Next.js 15
1645
+ - **Language**: TypeScript
1646
+ - **Database**: PostgreSQL + Prisma
1647
+ - **Auth**: NextAuth.js
1648
+ - **Styling**: Tailwind CSS
1649
+ - **UI Components**: Radix UI
1650
+
1651
+ ## Project Structure
1652
+
1653
+ \`\`\`
1654
+ ├── app/ # Next.js app directory
1655
+ ├── components/ # React components
1656
+ ├── lib/ # Utility functions
1657
+ ├── prisma/ # Database schema
1658
+ └── public/ # Static assets
1659
+ \`\`\`
1660
+
1661
+ ## Learn More
1662
+
1663
+ - [ChimerAI Documentation](https://chimerai.dev)
1664
+ - [Next.js Documentation](https://nextjs.org/docs)
1665
+ - [Prisma Documentation](https://www.prisma.io/docs)
1666
+
1667
+ ## License
1668
+
1669
+ Commercial License - See LICENSE file
1670
+ `;
1671
+ await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'README.md'), readme);
1672
+ // Create docs directory with inline documentation
1673
+ // No external path references — CLI works standalone as npm package
1674
+ const docsDir = path_1.default.join(targetDir, 'docs');
1675
+ await fs_extra_1.default.ensureDir(docsDir);
1676
+ // Generate inline quickstart guide
1677
+ const quickstart = `# Quick Start
1678
+
1679
+ ## Prerequisites
1680
+ - Node.js 20+
1681
+ - Docker Desktop
1682
+ - pnpm (recommended)
1683
+
1684
+ ## Setup
1685
+ 1. \`pnpm install\`
1686
+ 2. \`docker-compose up -d\`
1687
+ 3. \`pnpm db:push\`
1688
+ 4. \`pnpm db:seed\`
1689
+ 5. \`pnpm dev\`
1690
+
1691
+ ## Default Login
1692
+ - Email: admin@example.com
1693
+ - Password: admin123
1694
+
1695
+ ## CLI Commands
1696
+ - \`chimerai add <component>\` — Add features
1697
+ - \`chimerai setup <service>\` — Configure integrations
1698
+ - \`chimerai doctor\` — Health check
1699
+ - \`chimerai update --diff\` — Check for template updates
1700
+ `;
1701
+ await fs_extra_1.default.writeFile(path_1.default.join(docsDir, 'QUICKSTART.md'), quickstart);
1702
+ console.log(chalk_1.default.green(' ✓ docs/QUICKSTART.md'));
1703
+ }