@chimerai/cli 0.2.89 → 0.2.91
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.
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +2 -4
- package/dist/commands/create.js +981 -965
- package/dist/templates/chat.d.ts +1 -0
- package/dist/templates/chat.d.ts.map +1 -1
- package/dist/templates/chat.js +135 -0
- package/dist/templates/components.d.ts.map +1 -1
- package/dist/templates/components.js +0 -6
- package/dist/templates/index.d.ts +3 -3
- package/dist/templates/index.d.ts.map +1 -1
- package/dist/templates/index.js +6 -3
- package/dist/templates/layout.d.ts +2 -0
- package/dist/templates/layout.d.ts.map +1 -1
- package/dist/templates/layout.js +127 -3
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -231,7 +231,15 @@ async function createCommand(projectName, options) {
|
|
|
231
231
|
*/
|
|
232
232
|
function validateFeatureCombination(features) {
|
|
233
233
|
const warnings = [];
|
|
234
|
-
const authRequired = [
|
|
234
|
+
const authRequired = [
|
|
235
|
+
'rbac',
|
|
236
|
+
'admin-dashboard',
|
|
237
|
+
'billing',
|
|
238
|
+
'mfa',
|
|
239
|
+
'gdpr',
|
|
240
|
+
'audit-log',
|
|
241
|
+
'chat-ui',
|
|
242
|
+
];
|
|
235
243
|
if (!features.includes('auth')) {
|
|
236
244
|
authRequired.forEach((f) => {
|
|
237
245
|
if (features.includes(f)) {
|
|
@@ -312,10 +320,10 @@ async function createBaseStructure(targetDir, projectName, features) {
|
|
|
312
320
|
'styles',
|
|
313
321
|
];
|
|
314
322
|
// Add feature-specific directories
|
|
315
|
-
if (features.includes('admin')) {
|
|
323
|
+
if (features.includes('admin-dashboard')) {
|
|
316
324
|
dirs.push('app/admin', 'app/admin/settings', 'app/admin/logs');
|
|
317
325
|
}
|
|
318
|
-
if (features.includes('chat')) {
|
|
326
|
+
if (features.includes('ai-chat')) {
|
|
319
327
|
dirs.push('app/(app)', 'app/(app)/chat', 'components/chat', 'app/api/conversations/[id]');
|
|
320
328
|
}
|
|
321
329
|
if (features.includes('auth')) {
|
|
@@ -327,7 +335,7 @@ async function createBaseStructure(targetDir, projectName, features) {
|
|
|
327
335
|
if (features.includes('model-providers')) {
|
|
328
336
|
dirs.push('app/dashboard/providers');
|
|
329
337
|
}
|
|
330
|
-
if (features.includes('
|
|
338
|
+
if (features.includes('prompt-management')) {
|
|
331
339
|
dirs.push('app/dashboard/prompts', 'app/api/prompts', 'app/api/prompts/[id]');
|
|
332
340
|
}
|
|
333
341
|
for (const dir of dirs) {
|
|
@@ -385,10 +393,10 @@ async function createPackageJson(targetDir, projectName, features) {
|
|
|
385
393
|
if (features.includes('model-providers')) {
|
|
386
394
|
// dependencies['@chimerai/model-providers'] = 'workspace:*'; // Not available standalone yet
|
|
387
395
|
}
|
|
388
|
-
if (features.includes('admin')) {
|
|
396
|
+
if (features.includes('admin-dashboard')) {
|
|
389
397
|
// dependencies['@chimerai/admin-ui'] = 'workspace:*'; // Not available standalone yet
|
|
390
398
|
}
|
|
391
|
-
if (features.includes('chat')) {
|
|
399
|
+
if (features.includes('ai-chat')) {
|
|
392
400
|
dependencies['react-markdown'] = '^9.0.0';
|
|
393
401
|
dependencies['remark-gfm'] = '^4.0.0';
|
|
394
402
|
}
|
|
@@ -445,322 +453,322 @@ async function createTsConfig(targetDir) {
|
|
|
445
453
|
}
|
|
446
454
|
async function createPrismaSchema(targetDir, features, sqlite) {
|
|
447
455
|
const dbProvider = sqlite ? 'sqlite' : 'postgresql';
|
|
448
|
-
let schemaContent = `generator client {
|
|
449
|
-
provider = "prisma-client-js"
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
datasource db {
|
|
453
|
-
provider = "${dbProvider}"
|
|
454
|
-
url = env("DATABASE_URL")
|
|
455
|
-
}
|
|
456
|
-
|
|
456
|
+
let schemaContent = `generator client {
|
|
457
|
+
provider = "prisma-client-js"
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
datasource db {
|
|
461
|
+
provider = "${dbProvider}"
|
|
462
|
+
url = env("DATABASE_URL")
|
|
463
|
+
}
|
|
464
|
+
|
|
457
465
|
`;
|
|
458
466
|
// Add User model if auth is included
|
|
459
467
|
if (features.includes('auth')) {
|
|
460
|
-
schemaContent += `model User {
|
|
461
|
-
id String @id @default(cuid())
|
|
462
|
-
name String?
|
|
463
|
-
email String? @unique
|
|
464
|
-
emailVerified DateTime?
|
|
465
|
-
image String?
|
|
466
|
-
password String?
|
|
467
|
-
createdAt DateTime @default(now())
|
|
468
|
-
updatedAt DateTime @updatedAt
|
|
469
|
-
|
|
470
|
-
accounts Account[]
|
|
471
|
-
sessions Session[]
|
|
468
|
+
schemaContent += `model User {
|
|
469
|
+
id String @id @default(cuid())
|
|
470
|
+
name String?
|
|
471
|
+
email String? @unique
|
|
472
|
+
emailVerified DateTime?
|
|
473
|
+
image String?
|
|
474
|
+
password String?
|
|
475
|
+
createdAt DateTime @default(now())
|
|
476
|
+
updatedAt DateTime @updatedAt
|
|
477
|
+
|
|
478
|
+
accounts Account[]
|
|
479
|
+
sessions Session[]
|
|
472
480
|
`;
|
|
473
481
|
if (features.includes('rbac')) {
|
|
474
|
-
schemaContent += ` roles UserRole[]
|
|
482
|
+
schemaContent += ` roles UserRole[]
|
|
475
483
|
`;
|
|
476
484
|
}
|
|
477
485
|
// Provider relations (ALWAYS included)
|
|
478
|
-
schemaContent += ` providers Provider[] @relation("CreatedProviders")
|
|
479
|
-
apiUsage ApiUsage[]
|
|
480
|
-
apiKeys ApiKey[]
|
|
486
|
+
schemaContent += ` providers Provider[] @relation("CreatedProviders")
|
|
487
|
+
apiUsage ApiUsage[]
|
|
488
|
+
apiKeys ApiKey[]
|
|
481
489
|
`;
|
|
482
490
|
if (features.includes('rbac')) {
|
|
483
|
-
schemaContent += ` modelAccess ModelAccess[]
|
|
484
|
-
auditLogs AuditLog[]
|
|
491
|
+
schemaContent += ` modelAccess ModelAccess[]
|
|
492
|
+
auditLogs AuditLog[]
|
|
485
493
|
`;
|
|
486
494
|
}
|
|
487
|
-
if (features.includes('chat')) {
|
|
488
|
-
schemaContent += ` conversations Conversation[]
|
|
495
|
+
if (features.includes('ai-chat')) {
|
|
496
|
+
schemaContent += ` conversations Conversation[]
|
|
489
497
|
`;
|
|
490
498
|
}
|
|
491
|
-
schemaContent += `}
|
|
492
|
-
|
|
493
|
-
model Account {
|
|
494
|
-
id String @id @default(cuid())
|
|
495
|
-
userId String
|
|
496
|
-
type String
|
|
497
|
-
provider String
|
|
498
|
-
providerAccountId String
|
|
499
|
-
refresh_token String? @db.Text
|
|
500
|
-
access_token String? @db.Text
|
|
501
|
-
expires_at Int?
|
|
502
|
-
token_type String?
|
|
503
|
-
scope String?
|
|
504
|
-
id_token String? @db.Text
|
|
505
|
-
session_state String?
|
|
506
|
-
|
|
507
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
508
|
-
|
|
509
|
-
@@unique([provider, providerAccountId])
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
model Session {
|
|
513
|
-
id String @id @default(cuid())
|
|
514
|
-
sessionToken String @unique
|
|
515
|
-
userId String
|
|
516
|
-
expires DateTime
|
|
517
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
model VerificationToken {
|
|
521
|
-
identifier String
|
|
522
|
-
token String @unique
|
|
523
|
-
expires DateTime
|
|
524
|
-
|
|
525
|
-
@@unique([identifier, token])
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
model ApiKey {
|
|
529
|
-
id String @id @default(cuid())
|
|
530
|
-
name String
|
|
531
|
-
keyHash String @unique
|
|
532
|
-
userId String
|
|
533
|
-
scopes String[] @default([])
|
|
534
|
-
revoked Boolean @default(false)
|
|
535
|
-
lastUsedAt DateTime?
|
|
536
|
-
expiresAt DateTime?
|
|
537
|
-
createdAt DateTime @default(now())
|
|
538
|
-
updatedAt DateTime @updatedAt
|
|
539
|
-
|
|
540
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
541
|
-
|
|
542
|
-
@@index([userId])
|
|
543
|
-
}
|
|
544
|
-
|
|
499
|
+
schemaContent += `}
|
|
500
|
+
|
|
501
|
+
model Account {
|
|
502
|
+
id String @id @default(cuid())
|
|
503
|
+
userId String
|
|
504
|
+
type String
|
|
505
|
+
provider String
|
|
506
|
+
providerAccountId String
|
|
507
|
+
refresh_token String? @db.Text
|
|
508
|
+
access_token String? @db.Text
|
|
509
|
+
expires_at Int?
|
|
510
|
+
token_type String?
|
|
511
|
+
scope String?
|
|
512
|
+
id_token String? @db.Text
|
|
513
|
+
session_state String?
|
|
514
|
+
|
|
515
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
516
|
+
|
|
517
|
+
@@unique([provider, providerAccountId])
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
model Session {
|
|
521
|
+
id String @id @default(cuid())
|
|
522
|
+
sessionToken String @unique
|
|
523
|
+
userId String
|
|
524
|
+
expires DateTime
|
|
525
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
model VerificationToken {
|
|
529
|
+
identifier String
|
|
530
|
+
token String @unique
|
|
531
|
+
expires DateTime
|
|
532
|
+
|
|
533
|
+
@@unique([identifier, token])
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
model ApiKey {
|
|
537
|
+
id String @id @default(cuid())
|
|
538
|
+
name String
|
|
539
|
+
keyHash String @unique
|
|
540
|
+
userId String
|
|
541
|
+
scopes String[] @default([])
|
|
542
|
+
revoked Boolean @default(false)
|
|
543
|
+
lastUsedAt DateTime?
|
|
544
|
+
expiresAt DateTime?
|
|
545
|
+
createdAt DateTime @default(now())
|
|
546
|
+
updatedAt DateTime @updatedAt
|
|
547
|
+
|
|
548
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
549
|
+
|
|
550
|
+
@@index([userId])
|
|
551
|
+
}
|
|
552
|
+
|
|
545
553
|
`;
|
|
546
554
|
}
|
|
547
555
|
// Add RBAC models
|
|
548
556
|
if (features.includes('rbac')) {
|
|
549
|
-
schemaContent += `model Role {
|
|
550
|
-
id String @id @default(cuid())
|
|
551
|
-
name String @unique
|
|
552
|
-
description String?
|
|
553
|
-
permissions String[]
|
|
554
|
-
createdAt DateTime @default(now())
|
|
555
|
-
updatedAt DateTime @updatedAt
|
|
556
|
-
|
|
557
|
-
users UserRole[]
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
model UserRole {
|
|
561
|
-
userId String
|
|
562
|
-
roleId String
|
|
563
|
-
|
|
564
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
565
|
-
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
566
|
-
|
|
567
|
-
@@id([userId, roleId])
|
|
568
|
-
}
|
|
569
|
-
|
|
557
|
+
schemaContent += `model Role {
|
|
558
|
+
id String @id @default(cuid())
|
|
559
|
+
name String @unique
|
|
560
|
+
description String?
|
|
561
|
+
permissions String[]
|
|
562
|
+
createdAt DateTime @default(now())
|
|
563
|
+
updatedAt DateTime @updatedAt
|
|
564
|
+
|
|
565
|
+
users UserRole[]
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
model UserRole {
|
|
569
|
+
userId String
|
|
570
|
+
roleId String
|
|
571
|
+
|
|
572
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
573
|
+
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
574
|
+
|
|
575
|
+
@@id([userId, roleId])
|
|
576
|
+
}
|
|
577
|
+
|
|
570
578
|
`;
|
|
571
579
|
}
|
|
572
580
|
// ── PROVIDER MODELS (ALWAYS included — core infrastructure) ──────────
|
|
573
|
-
schemaContent += `// === Provider Management (Core Infrastructure) ===
|
|
574
|
-
|
|
575
|
-
model Provider {
|
|
576
|
-
id String @id @default(cuid())
|
|
577
|
-
name String
|
|
578
|
-
type String // "openai", "anthropic", "ollama", "google", "custom"
|
|
579
|
-
description String?
|
|
580
|
-
baseUrl String?
|
|
581
|
-
apiKey String? @db.Text // AES-256-GCM encrypted, null for keyless providers (e.g. Ollama)
|
|
582
|
-
config Json @default("{}")
|
|
583
|
-
status String @default("active")
|
|
584
|
-
isDefault Boolean @default(false)
|
|
585
|
-
priority Int @default(0)
|
|
586
|
-
tags String[]
|
|
587
|
-
createdAt DateTime @default(now())
|
|
588
|
-
updatedAt DateTime @updatedAt
|
|
589
|
-
createdBy String?
|
|
590
|
-
|
|
591
|
-
models Model[]
|
|
592
|
-
health ProviderHealth?
|
|
593
|
-
apiUsage ApiUsage[]
|
|
594
|
-
${features.includes('chat') ? ' conversations Conversation[]\n' : ''}${features.includes('auth') ? ' creator User? @relation("CreatedProviders", fields: [createdBy], references: [id])\n' : ''}
|
|
595
|
-
@@index([type])
|
|
596
|
-
@@index([status])
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
model Model {
|
|
600
|
-
id String @id @default(cuid())
|
|
601
|
-
providerId String
|
|
602
|
-
modelId String // e.g. "gpt-4", "claude-3-sonnet"
|
|
603
|
-
name String
|
|
604
|
-
description String?
|
|
605
|
-
capabilities String[] // ["chat", "embedding", "image", "vision"]
|
|
606
|
-
contextWindow Int @default(4096)
|
|
607
|
-
maxOutputTokens Int?
|
|
608
|
-
inputCost Float @default(0) // $ per 1M tokens
|
|
609
|
-
outputCost Float @default(0)
|
|
610
|
-
isAvailable Boolean @default(true)
|
|
611
|
-
isDeprecated Boolean @default(false)
|
|
612
|
-
|
|
613
|
-
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
|
614
|
-
|
|
615
|
-
@@unique([providerId, modelId])
|
|
616
|
-
@@index([providerId])
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
model ProviderHealth {
|
|
620
|
-
id String @id @default(cuid())
|
|
621
|
-
providerId String @unique
|
|
622
|
-
status String @default("unknown") // "healthy", "degraded", "unhealthy"
|
|
623
|
-
responseTime Int? // ms
|
|
624
|
-
lastCheck DateTime @default(now())
|
|
625
|
-
errorMessage String?
|
|
626
|
-
modelsAvailable Int @default(0)
|
|
627
|
-
chatAvailable Boolean @default(false)
|
|
628
|
-
embeddingAvailable Boolean @default(false)
|
|
629
|
-
apiKeyValid Boolean @default(false)
|
|
630
|
-
|
|
631
|
-
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
model ApiUsage {
|
|
635
|
-
id String @id @default(cuid())
|
|
636
|
-
userId String
|
|
637
|
-
providerId String?
|
|
638
|
-
model String
|
|
639
|
-
endpoint String
|
|
640
|
-
promptTokens Int @default(0)
|
|
641
|
-
completionTokens Int @default(0)
|
|
642
|
-
totalTokens Int @default(0)
|
|
643
|
-
tokensUsed Int @default(0)
|
|
644
|
-
creditsUsed Int @default(0)
|
|
645
|
-
cost Float @default(0)
|
|
646
|
-
success Boolean @default(true)
|
|
647
|
-
errorMessage String?
|
|
648
|
-
responseTime Int @default(0) // ms
|
|
649
|
-
createdAt DateTime @default(now())
|
|
650
|
-
|
|
651
|
-
${features.includes('auth') ? ' user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n' : ''} provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
|
|
652
|
-
|
|
653
|
-
@@index([userId])
|
|
654
|
-
@@index([providerId])
|
|
655
|
-
@@index([createdAt])
|
|
656
|
-
}
|
|
657
|
-
|
|
581
|
+
schemaContent += `// === Provider Management (Core Infrastructure) ===
|
|
582
|
+
|
|
583
|
+
model Provider {
|
|
584
|
+
id String @id @default(cuid())
|
|
585
|
+
name String
|
|
586
|
+
type String // "openai", "anthropic", "ollama", "google", "custom"
|
|
587
|
+
description String?
|
|
588
|
+
baseUrl String?
|
|
589
|
+
apiKey String? @db.Text // AES-256-GCM encrypted, null for keyless providers (e.g. Ollama)
|
|
590
|
+
config Json @default("{}")
|
|
591
|
+
status String @default("active")
|
|
592
|
+
isDefault Boolean @default(false)
|
|
593
|
+
priority Int @default(0)
|
|
594
|
+
tags String[]
|
|
595
|
+
createdAt DateTime @default(now())
|
|
596
|
+
updatedAt DateTime @updatedAt
|
|
597
|
+
createdBy String?
|
|
598
|
+
|
|
599
|
+
models Model[]
|
|
600
|
+
health ProviderHealth?
|
|
601
|
+
apiUsage ApiUsage[]
|
|
602
|
+
${features.includes('ai-chat') ? ' conversations Conversation[]\n' : ''}${features.includes('auth') ? ' creator User? @relation("CreatedProviders", fields: [createdBy], references: [id])\n' : ''}
|
|
603
|
+
@@index([type])
|
|
604
|
+
@@index([status])
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
model Model {
|
|
608
|
+
id String @id @default(cuid())
|
|
609
|
+
providerId String
|
|
610
|
+
modelId String // e.g. "gpt-4", "claude-3-sonnet"
|
|
611
|
+
name String
|
|
612
|
+
description String?
|
|
613
|
+
capabilities String[] // ["chat", "embedding", "image", "vision"]
|
|
614
|
+
contextWindow Int @default(4096)
|
|
615
|
+
maxOutputTokens Int?
|
|
616
|
+
inputCost Float @default(0) // $ per 1M tokens
|
|
617
|
+
outputCost Float @default(0)
|
|
618
|
+
isAvailable Boolean @default(true)
|
|
619
|
+
isDeprecated Boolean @default(false)
|
|
620
|
+
|
|
621
|
+
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
|
622
|
+
|
|
623
|
+
@@unique([providerId, modelId])
|
|
624
|
+
@@index([providerId])
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
model ProviderHealth {
|
|
628
|
+
id String @id @default(cuid())
|
|
629
|
+
providerId String @unique
|
|
630
|
+
status String @default("unknown") // "healthy", "degraded", "unhealthy"
|
|
631
|
+
responseTime Int? // ms
|
|
632
|
+
lastCheck DateTime @default(now())
|
|
633
|
+
errorMessage String?
|
|
634
|
+
modelsAvailable Int @default(0)
|
|
635
|
+
chatAvailable Boolean @default(false)
|
|
636
|
+
embeddingAvailable Boolean @default(false)
|
|
637
|
+
apiKeyValid Boolean @default(false)
|
|
638
|
+
|
|
639
|
+
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
model ApiUsage {
|
|
643
|
+
id String @id @default(cuid())
|
|
644
|
+
userId String
|
|
645
|
+
providerId String?
|
|
646
|
+
model String
|
|
647
|
+
endpoint String
|
|
648
|
+
promptTokens Int @default(0)
|
|
649
|
+
completionTokens Int @default(0)
|
|
650
|
+
totalTokens Int @default(0)
|
|
651
|
+
tokensUsed Int @default(0)
|
|
652
|
+
creditsUsed Int @default(0)
|
|
653
|
+
cost Float @default(0)
|
|
654
|
+
success Boolean @default(true)
|
|
655
|
+
errorMessage String?
|
|
656
|
+
responseTime Int @default(0) // ms
|
|
657
|
+
createdAt DateTime @default(now())
|
|
658
|
+
|
|
659
|
+
${features.includes('auth') ? ' user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n' : ''} provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
|
|
660
|
+
|
|
661
|
+
@@index([userId])
|
|
662
|
+
@@index([providerId])
|
|
663
|
+
@@index([createdAt])
|
|
664
|
+
}
|
|
665
|
+
|
|
658
666
|
`;
|
|
659
667
|
// Add ModelAccess when auth + rbac are enabled (for requireModelPermission)
|
|
660
668
|
if (features.includes('auth') && features.includes('rbac')) {
|
|
661
|
-
schemaContent += `model ModelAccess {
|
|
662
|
-
id String @id @default(cuid())
|
|
663
|
-
userId String
|
|
664
|
-
modelId String
|
|
665
|
-
granted Boolean @default(true)
|
|
666
|
-
createdAt DateTime @default(now())
|
|
667
|
-
updatedAt DateTime @updatedAt
|
|
668
|
-
|
|
669
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
670
|
-
|
|
671
|
-
@@unique([userId, modelId])
|
|
672
|
-
@@index([userId])
|
|
673
|
-
@@index([modelId])
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
model AuditLog {
|
|
677
|
-
id String @id @default(cuid())
|
|
678
|
-
action String
|
|
679
|
-
userId String
|
|
680
|
-
targetType String?
|
|
681
|
-
targetId String?
|
|
682
|
-
metadata Json?
|
|
683
|
-
ipAddress String?
|
|
684
|
-
createdAt DateTime @default(now())
|
|
685
|
-
|
|
686
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
687
|
-
|
|
688
|
-
@@index([userId])
|
|
689
|
-
@@index([action])
|
|
690
|
-
@@index([createdAt])
|
|
691
|
-
}
|
|
692
|
-
|
|
669
|
+
schemaContent += `model ModelAccess {
|
|
670
|
+
id String @id @default(cuid())
|
|
671
|
+
userId String
|
|
672
|
+
modelId String
|
|
673
|
+
granted Boolean @default(true)
|
|
674
|
+
createdAt DateTime @default(now())
|
|
675
|
+
updatedAt DateTime @updatedAt
|
|
676
|
+
|
|
677
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
678
|
+
|
|
679
|
+
@@unique([userId, modelId])
|
|
680
|
+
@@index([userId])
|
|
681
|
+
@@index([modelId])
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
model AuditLog {
|
|
685
|
+
id String @id @default(cuid())
|
|
686
|
+
action String
|
|
687
|
+
userId String
|
|
688
|
+
targetType String?
|
|
689
|
+
targetId String?
|
|
690
|
+
metadata Json?
|
|
691
|
+
ipAddress String?
|
|
692
|
+
createdAt DateTime @default(now())
|
|
693
|
+
|
|
694
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
695
|
+
|
|
696
|
+
@@index([userId])
|
|
697
|
+
@@index([action])
|
|
698
|
+
@@index([createdAt])
|
|
699
|
+
}
|
|
700
|
+
|
|
693
701
|
`;
|
|
694
702
|
}
|
|
695
703
|
// Add Prompt Templates
|
|
696
|
-
if (features.includes('
|
|
697
|
-
schemaContent += `model PromptTemplate {
|
|
698
|
-
id String @id @default(cuid())
|
|
699
|
-
name String @unique
|
|
700
|
-
category String
|
|
701
|
-
description String?
|
|
702
|
-
content String @db.Text
|
|
703
|
-
variables String[]
|
|
704
|
-
language String @default("en")
|
|
705
|
-
version Int @default(1)
|
|
706
|
-
isActive Boolean @default(true)
|
|
707
|
-
isDefault Boolean @default(false)
|
|
708
|
-
tags String[]
|
|
709
|
-
metadata Json?
|
|
710
|
-
createdBy String?
|
|
711
|
-
createdAt DateTime @default(now())
|
|
712
|
-
updatedAt DateTime @updatedAt
|
|
713
|
-
|
|
714
|
-
@@index([category])
|
|
715
|
-
@@index([isActive])
|
|
716
|
-
@@index([isDefault])
|
|
717
|
-
}
|
|
718
|
-
|
|
704
|
+
if (features.includes('prompt-management')) {
|
|
705
|
+
schemaContent += `model PromptTemplate {
|
|
706
|
+
id String @id @default(cuid())
|
|
707
|
+
name String @unique
|
|
708
|
+
category String
|
|
709
|
+
description String?
|
|
710
|
+
content String @db.Text
|
|
711
|
+
variables String[]
|
|
712
|
+
language String @default("en")
|
|
713
|
+
version Int @default(1)
|
|
714
|
+
isActive Boolean @default(true)
|
|
715
|
+
isDefault Boolean @default(false)
|
|
716
|
+
tags String[]
|
|
717
|
+
metadata Json?
|
|
718
|
+
createdBy String?
|
|
719
|
+
createdAt DateTime @default(now())
|
|
720
|
+
updatedAt DateTime @updatedAt
|
|
721
|
+
|
|
722
|
+
@@index([category])
|
|
723
|
+
@@index([isActive])
|
|
724
|
+
@@index([isDefault])
|
|
725
|
+
}
|
|
726
|
+
|
|
719
727
|
`;
|
|
720
728
|
}
|
|
721
729
|
// Add Conversation & Message models for chat feature
|
|
722
|
-
if (features.includes('chat')) {
|
|
723
|
-
schemaContent += `model Conversation {
|
|
724
|
-
id String @id @default(cuid())
|
|
725
|
-
userId String
|
|
726
|
-
title String @default("New Chat")
|
|
727
|
-
model String?
|
|
728
|
-
providerId String?
|
|
729
|
-
metadata Json?
|
|
730
|
-
archived Boolean @default(false)
|
|
731
|
-
createdAt DateTime @default(now())
|
|
732
|
-
updatedAt DateTime @updatedAt
|
|
733
|
-
|
|
734
|
-
${features.includes('auth') ? ' user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n' : ''} provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
|
|
735
|
-
messages Message[]
|
|
736
|
-
|
|
737
|
-
@@index([userId])
|
|
738
|
-
@@index([archived])
|
|
739
|
-
@@index([providerId])
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
model Message {
|
|
743
|
-
id String @id @default(cuid())
|
|
744
|
-
conversationId String
|
|
745
|
-
role String
|
|
746
|
-
content String @db.Text
|
|
747
|
-
model String?
|
|
748
|
-
tokens Int?
|
|
749
|
-
createdAt DateTime @default(now())
|
|
750
|
-
|
|
751
|
-
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
752
|
-
|
|
753
|
-
@@index([conversationId])
|
|
754
|
-
}
|
|
755
|
-
|
|
730
|
+
if (features.includes('ai-chat')) {
|
|
731
|
+
schemaContent += `model Conversation {
|
|
732
|
+
id String @id @default(cuid())
|
|
733
|
+
userId String
|
|
734
|
+
title String @default("New Chat")
|
|
735
|
+
model String?
|
|
736
|
+
providerId String?
|
|
737
|
+
metadata Json?
|
|
738
|
+
archived Boolean @default(false)
|
|
739
|
+
createdAt DateTime @default(now())
|
|
740
|
+
updatedAt DateTime @updatedAt
|
|
741
|
+
|
|
742
|
+
${features.includes('auth') ? ' user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n' : ''} provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
|
|
743
|
+
messages Message[]
|
|
744
|
+
|
|
745
|
+
@@index([userId])
|
|
746
|
+
@@index([archived])
|
|
747
|
+
@@index([providerId])
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
model Message {
|
|
751
|
+
id String @id @default(cuid())
|
|
752
|
+
conversationId String
|
|
753
|
+
role String
|
|
754
|
+
content String @db.Text
|
|
755
|
+
model String?
|
|
756
|
+
tokens Int?
|
|
757
|
+
createdAt DateTime @default(now())
|
|
758
|
+
|
|
759
|
+
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
760
|
+
|
|
761
|
+
@@index([conversationId])
|
|
762
|
+
}
|
|
763
|
+
|
|
756
764
|
`;
|
|
757
765
|
}
|
|
758
766
|
// SystemSetting is always included (used by admin settings + app-settings API)
|
|
759
|
-
schemaContent += `model SystemSetting {
|
|
760
|
-
key String @id
|
|
761
|
-
value String @db.Text
|
|
762
|
-
updatedAt DateTime @updatedAt
|
|
763
|
-
}
|
|
767
|
+
schemaContent += `model SystemSetting {
|
|
768
|
+
key String @id
|
|
769
|
+
value String @db.Text
|
|
770
|
+
updatedAt DateTime @updatedAt
|
|
771
|
+
}
|
|
764
772
|
`;
|
|
765
773
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'prisma/schema.prisma'), schemaContent);
|
|
766
774
|
// Apply SQLite compatibility transform if needed
|
|
@@ -816,55 +824,55 @@ async function createEnvFiles(targetDir, features, sqlite) {
|
|
|
816
824
|
// Generate a random NextAuth secret (32 bytes hex = 64 chars)
|
|
817
825
|
const nextAuthSecret = Array.from({ length: 64 }, () => '0123456789abcdef'.charAt(Math.floor(Math.random() * 16))).join('');
|
|
818
826
|
const dbUrl = sqlite ? 'file:./dev.db' : 'postgresql://postgres:postgres@localhost:5432/myapp_db';
|
|
819
|
-
let envContent = `# Database${sqlite ? ' (SQLite — no Docker needed)' : ''}
|
|
820
|
-
DATABASE_URL=${dbUrl}
|
|
821
|
-
|
|
827
|
+
let envContent = `# Database${sqlite ? ' (SQLite — no Docker needed)' : ''}
|
|
828
|
+
DATABASE_URL=${dbUrl}
|
|
829
|
+
|
|
822
830
|
`;
|
|
823
831
|
if (features.includes('auth')) {
|
|
824
|
-
envContent += `# Authentication
|
|
825
|
-
NEXTAUTH_SECRET=${nextAuthSecret}
|
|
826
|
-
NEXTAUTH_URL=http://localhost:3000
|
|
827
|
-
|
|
832
|
+
envContent += `# Authentication
|
|
833
|
+
NEXTAUTH_SECRET=${nextAuthSecret}
|
|
834
|
+
NEXTAUTH_URL=http://localhost:3000
|
|
835
|
+
|
|
828
836
|
`;
|
|
829
837
|
}
|
|
830
838
|
// ── Provider Infrastructure (ALWAYS included — core) ──────────
|
|
831
|
-
envContent += `# Provider Encryption (AES-256-GCM for API key storage)
|
|
832
|
-
PROVIDER_ENCRYPTION_KEY=${encryptionKey}
|
|
833
|
-
|
|
834
|
-
# Internal API Token (AI-Service ↔ Frontend communication)
|
|
835
|
-
INTERNAL_API_TOKEN=${internalToken}
|
|
836
|
-
|
|
837
|
-
# AI Service URL
|
|
838
|
-
AI_SERVICE_URL=http://localhost:8002
|
|
839
|
-
|
|
840
|
-
# AI Providers (Optional — can also be added via Provider Management UI)
|
|
841
|
-
OPENAI_API_KEY=
|
|
842
|
-
ANTHROPIC_API_KEY=
|
|
843
|
-
AZURE_OPENAI_API_KEY=
|
|
844
|
-
AZURE_OPENAI_ENDPOINT=
|
|
845
|
-
|
|
839
|
+
envContent += `# Provider Encryption (AES-256-GCM for API key storage)
|
|
840
|
+
PROVIDER_ENCRYPTION_KEY=${encryptionKey}
|
|
841
|
+
|
|
842
|
+
# Internal API Token (AI-Service ↔ Frontend communication)
|
|
843
|
+
INTERNAL_API_TOKEN=${internalToken}
|
|
844
|
+
|
|
845
|
+
# AI Service URL
|
|
846
|
+
AI_SERVICE_URL=http://localhost:8002
|
|
847
|
+
|
|
848
|
+
# AI Providers (Optional — can also be added via Provider Management UI)
|
|
849
|
+
OPENAI_API_KEY=
|
|
850
|
+
ANTHROPIC_API_KEY=
|
|
851
|
+
AZURE_OPENAI_API_KEY=
|
|
852
|
+
AZURE_OPENAI_ENDPOINT=
|
|
853
|
+
|
|
846
854
|
`;
|
|
847
855
|
if (features.includes('billing')) {
|
|
848
|
-
envContent += `# Stripe
|
|
849
|
-
STRIPE_PUBLISHABLE_KEY=
|
|
850
|
-
STRIPE_SECRET_KEY=
|
|
851
|
-
STRIPE_WEBHOOK_SECRET=
|
|
852
|
-
|
|
856
|
+
envContent += `# Stripe
|
|
857
|
+
STRIPE_PUBLISHABLE_KEY=
|
|
858
|
+
STRIPE_SECRET_KEY=
|
|
859
|
+
STRIPE_WEBHOOK_SECRET=
|
|
860
|
+
|
|
853
861
|
`;
|
|
854
862
|
}
|
|
855
863
|
// Widget / External Integration
|
|
856
|
-
envContent += `# CORS / Widget Configuration (External Chat Embedding)
|
|
857
|
-
# Comma-separated list of allowed origins for cross-origin requests (e.g. Blazor, Vue, React embeds)
|
|
858
|
-
# Use * to allow all origins (development only!)
|
|
859
|
-
# Example: CORS_ALLOWED_ORIGINS=https://my-blog.com,https://my-shop.com
|
|
860
|
-
CORS_ALLOWED_ORIGINS=
|
|
861
|
-
# Legacy alias (still supported): WIDGET_ALLOWED_ORIGINS
|
|
862
|
-
|
|
863
|
-
# Upstash Redis (optional — for rate-limiting in serverless/multi-instance)
|
|
864
|
-
# Without this, rate-limiting uses in-memory fallback (fine for single-instance)
|
|
865
|
-
# UPSTASH_REDIS_REST_URL=
|
|
866
|
-
# UPSTASH_REDIS_REST_TOKEN=
|
|
867
|
-
|
|
864
|
+
envContent += `# CORS / Widget Configuration (External Chat Embedding)
|
|
865
|
+
# Comma-separated list of allowed origins for cross-origin requests (e.g. Blazor, Vue, React embeds)
|
|
866
|
+
# Use * to allow all origins (development only!)
|
|
867
|
+
# Example: CORS_ALLOWED_ORIGINS=https://my-blog.com,https://my-shop.com
|
|
868
|
+
CORS_ALLOWED_ORIGINS=
|
|
869
|
+
# Legacy alias (still supported): WIDGET_ALLOWED_ORIGINS
|
|
870
|
+
|
|
871
|
+
# Upstash Redis (optional — for rate-limiting in serverless/multi-instance)
|
|
872
|
+
# Without this, rate-limiting uses in-memory fallback (fine for single-instance)
|
|
873
|
+
# UPSTASH_REDIS_REST_URL=
|
|
874
|
+
# UPSTASH_REDIS_REST_TOKEN=
|
|
875
|
+
|
|
868
876
|
`;
|
|
869
877
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, '.env'), envContent);
|
|
870
878
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, '.env.example'), envContent
|
|
@@ -1004,7 +1012,15 @@ async function copyFeatureFiles(targetDir, features) {
|
|
|
1004
1012
|
const providersPage = templates.generateModelProvidersPage();
|
|
1005
1013
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/dashboard/providers'));
|
|
1006
1014
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/dashboard/providers/page.tsx'), providersPage);
|
|
1007
|
-
|
|
1015
|
+
// No-auth dashboard landing page + layout (when auth is not selected)
|
|
1016
|
+
if (!features.includes('auth')) {
|
|
1017
|
+
const noAuthDashPage = templates.generateDashboardPageNoAuth(features);
|
|
1018
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/dashboard'));
|
|
1019
|
+
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/dashboard/page.tsx'), noAuthDashPage);
|
|
1020
|
+
const noAuthDashLayout = templates.generateDashboardLayoutNoAuth();
|
|
1021
|
+
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/dashboard/layout.tsx'), noAuthDashLayout);
|
|
1022
|
+
}
|
|
1023
|
+
if (features.includes('admin-dashboard')) {
|
|
1008
1024
|
// Admin layout with session check + admin role guard
|
|
1009
1025
|
const adminLayout = templates.generateAdminLayout();
|
|
1010
1026
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/admin'));
|
|
@@ -1059,136 +1075,136 @@ async function copyFeatureFiles(targetDir, features) {
|
|
|
1059
1075
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib'));
|
|
1060
1076
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/use-app-name.ts'), useAppNameHook);
|
|
1061
1077
|
// ── RBAC Permission utilities (required by admin API routes) ───
|
|
1062
|
-
const permissionsLib = `/**
|
|
1063
|
-
* Permission utility functions
|
|
1064
|
-
* Handles permission checks and role-based access control
|
|
1065
|
-
*/
|
|
1066
|
-
|
|
1067
|
-
export const AVAILABLE_PERMISSIONS = [
|
|
1068
|
-
'users:read',
|
|
1069
|
-
'users:write',
|
|
1070
|
-
'users:delete',
|
|
1071
|
-
'roles:read',
|
|
1072
|
-
'roles:write',
|
|
1073
|
-
'roles:delete',
|
|
1074
|
-
'settings:read',
|
|
1075
|
-
'settings:write',
|
|
1076
|
-
'admin:*',
|
|
1077
|
-
] as const;
|
|
1078
|
-
|
|
1079
|
-
export type Permission = typeof AVAILABLE_PERMISSIONS[number];
|
|
1080
|
-
|
|
1081
|
-
interface User {
|
|
1082
|
-
id: string;
|
|
1083
|
-
email: string;
|
|
1084
|
-
roles?: Array<{ permissions: string[] }>;
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
export function hasPermission(user: User | null, permission: string): boolean {
|
|
1088
|
-
if (!user || !user.roles) return false;
|
|
1089
|
-
const allPermissions = user.roles.flatMap(role => role.permissions || []);
|
|
1090
|
-
|
|
1091
|
-
// Tier 1: Super-Wildcard — '*' matcht ALLES
|
|
1092
|
-
if (allPermissions.includes('*')) return true;
|
|
1093
|
-
|
|
1094
|
-
// Tier 2: Kategorie-Wildcard — 'admin:*' matcht 'admin:users:read', 'admin:roles:write', etc.
|
|
1095
|
-
for (const perm of allPermissions) {
|
|
1096
|
-
if (perm.endsWith(':*')) {
|
|
1097
|
-
const prefix = perm.slice(0, -1); // 'admin:*' → 'admin:'
|
|
1098
|
-
if (permission.startsWith(prefix)) return true;
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
// Tier 3: Exakter Match
|
|
1103
|
-
return allPermissions.includes(permission);
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
export function hasAnyPermission(user: User | null, permissions: string[]): boolean {
|
|
1107
|
-
if (!user) return false;
|
|
1108
|
-
return permissions.some(permission => hasPermission(user, permission));
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
export function hasAllPermissions(user: User | null, permissions: string[]): boolean {
|
|
1112
|
-
if (!user) return false;
|
|
1113
|
-
return permissions.every(permission => hasPermission(user, permission));
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
export function getUserPermissions(user: User | null): string[] {
|
|
1117
|
-
if (!user || !user.roles) return [];
|
|
1118
|
-
return [...new Set(user.roles.flatMap(role => role.permissions || []))];
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
export function isValidPermission(permission: string): boolean {
|
|
1122
|
-
return AVAILABLE_PERMISSIONS.includes(permission as Permission);
|
|
1123
|
-
}
|
|
1078
|
+
const permissionsLib = `/**
|
|
1079
|
+
* Permission utility functions
|
|
1080
|
+
* Handles permission checks and role-based access control
|
|
1081
|
+
*/
|
|
1082
|
+
|
|
1083
|
+
export const AVAILABLE_PERMISSIONS = [
|
|
1084
|
+
'users:read',
|
|
1085
|
+
'users:write',
|
|
1086
|
+
'users:delete',
|
|
1087
|
+
'roles:read',
|
|
1088
|
+
'roles:write',
|
|
1089
|
+
'roles:delete',
|
|
1090
|
+
'settings:read',
|
|
1091
|
+
'settings:write',
|
|
1092
|
+
'admin:*',
|
|
1093
|
+
] as const;
|
|
1094
|
+
|
|
1095
|
+
export type Permission = typeof AVAILABLE_PERMISSIONS[number];
|
|
1096
|
+
|
|
1097
|
+
interface User {
|
|
1098
|
+
id: string;
|
|
1099
|
+
email: string;
|
|
1100
|
+
roles?: Array<{ permissions: string[] }>;
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
export function hasPermission(user: User | null, permission: string): boolean {
|
|
1104
|
+
if (!user || !user.roles) return false;
|
|
1105
|
+
const allPermissions = user.roles.flatMap(role => role.permissions || []);
|
|
1106
|
+
|
|
1107
|
+
// Tier 1: Super-Wildcard — '*' matcht ALLES
|
|
1108
|
+
if (allPermissions.includes('*')) return true;
|
|
1109
|
+
|
|
1110
|
+
// Tier 2: Kategorie-Wildcard — 'admin:*' matcht 'admin:users:read', 'admin:roles:write', etc.
|
|
1111
|
+
for (const perm of allPermissions) {
|
|
1112
|
+
if (perm.endsWith(':*')) {
|
|
1113
|
+
const prefix = perm.slice(0, -1); // 'admin:*' → 'admin:'
|
|
1114
|
+
if (permission.startsWith(prefix)) return true;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
// Tier 3: Exakter Match
|
|
1119
|
+
return allPermissions.includes(permission);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
export function hasAnyPermission(user: User | null, permissions: string[]): boolean {
|
|
1123
|
+
if (!user) return false;
|
|
1124
|
+
return permissions.some(permission => hasPermission(user, permission));
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
export function hasAllPermissions(user: User | null, permissions: string[]): boolean {
|
|
1128
|
+
if (!user) return false;
|
|
1129
|
+
return permissions.every(permission => hasPermission(user, permission));
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
export function getUserPermissions(user: User | null): string[] {
|
|
1133
|
+
if (!user || !user.roles) return [];
|
|
1134
|
+
return [...new Set(user.roles.flatMap(role => role.permissions || []))];
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
export function isValidPermission(permission: string): boolean {
|
|
1138
|
+
return AVAILABLE_PERMISSIONS.includes(permission as Permission);
|
|
1139
|
+
}
|
|
1124
1140
|
`;
|
|
1125
1141
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/permissions.ts'), permissionsLib);
|
|
1126
|
-
const requirePermissionLib = `import { getServerSession } from 'next-auth';
|
|
1127
|
-
import { NextResponse } from 'next/server';
|
|
1128
|
-
import { authOptions } from '@/lib/auth';
|
|
1129
|
-
import { hasPermission } from '@/lib/permissions';
|
|
1130
|
-
|
|
1131
|
-
/**
|
|
1132
|
-
* Server-side permission check for API routes
|
|
1133
|
-
* Returns NextResponse with 401/403 if check fails, null if OK
|
|
1134
|
-
*/
|
|
1135
|
-
export async function requirePermission(permission: string) {
|
|
1136
|
-
const session = await getServerSession(authOptions);
|
|
1137
|
-
|
|
1138
|
-
if (!session || !session.user) {
|
|
1139
|
-
return NextResponse.json(
|
|
1140
|
-
{ error: 'Unauthorized - Please sign in' },
|
|
1141
|
-
{ status: 401 }
|
|
1142
|
-
);
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
const user = await getServerSessionWithPermissions();
|
|
1146
|
-
|
|
1147
|
-
if (!user || !hasPermission(user as any, permission)) {
|
|
1148
|
-
return NextResponse.json(
|
|
1149
|
-
{ error: \`Forbidden - Required permission: \${permission}\` },
|
|
1150
|
-
{ status: 403 }
|
|
1151
|
-
);
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
return null;
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
async function getServerSessionWithPermissions() {
|
|
1158
|
-
const session = await getServerSession(authOptions);
|
|
1159
|
-
if (!session?.user?.email) return null;
|
|
1160
|
-
|
|
1161
|
-
const { prisma } = await import('@/lib/prisma');
|
|
1162
|
-
const user = await prisma.user.findUnique({
|
|
1163
|
-
where: { email: session.user.email },
|
|
1164
|
-
include: {
|
|
1165
|
-
roles: {
|
|
1166
|
-
select: {
|
|
1167
|
-
role: {
|
|
1168
|
-
select: {
|
|
1169
|
-
id: true,
|
|
1170
|
-
name: true,
|
|
1171
|
-
permissions: true
|
|
1172
|
-
}
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1176
|
-
}
|
|
1177
|
-
});
|
|
1178
|
-
|
|
1179
|
-
if (!user) return null;
|
|
1180
|
-
|
|
1181
|
-
// Flatten UserRole[] → { permissions: string[] }[]
|
|
1182
|
-
return {
|
|
1183
|
-
...user,
|
|
1184
|
-
roles: (user.roles as any[]).map((ur: any) => ur.role),
|
|
1185
|
-
};
|
|
1186
|
-
}
|
|
1142
|
+
const requirePermissionLib = `import { getServerSession } from 'next-auth';
|
|
1143
|
+
import { NextResponse } from 'next/server';
|
|
1144
|
+
import { authOptions } from '@/lib/auth';
|
|
1145
|
+
import { hasPermission } from '@/lib/permissions';
|
|
1146
|
+
|
|
1147
|
+
/**
|
|
1148
|
+
* Server-side permission check for API routes
|
|
1149
|
+
* Returns NextResponse with 401/403 if check fails, null if OK
|
|
1150
|
+
*/
|
|
1151
|
+
export async function requirePermission(permission: string) {
|
|
1152
|
+
const session = await getServerSession(authOptions);
|
|
1153
|
+
|
|
1154
|
+
if (!session || !session.user) {
|
|
1155
|
+
return NextResponse.json(
|
|
1156
|
+
{ error: 'Unauthorized - Please sign in' },
|
|
1157
|
+
{ status: 401 }
|
|
1158
|
+
);
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
const user = await getServerSessionWithPermissions();
|
|
1162
|
+
|
|
1163
|
+
if (!user || !hasPermission(user as any, permission)) {
|
|
1164
|
+
return NextResponse.json(
|
|
1165
|
+
{ error: \`Forbidden - Required permission: \${permission}\` },
|
|
1166
|
+
{ status: 403 }
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
return null;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
async function getServerSessionWithPermissions() {
|
|
1174
|
+
const session = await getServerSession(authOptions);
|
|
1175
|
+
if (!session?.user?.email) return null;
|
|
1176
|
+
|
|
1177
|
+
const { prisma } = await import('@/lib/prisma');
|
|
1178
|
+
const user = await prisma.user.findUnique({
|
|
1179
|
+
where: { email: session.user.email },
|
|
1180
|
+
include: {
|
|
1181
|
+
roles: {
|
|
1182
|
+
select: {
|
|
1183
|
+
role: {
|
|
1184
|
+
select: {
|
|
1185
|
+
id: true,
|
|
1186
|
+
name: true,
|
|
1187
|
+
permissions: true
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
|
|
1195
|
+
if (!user) return null;
|
|
1196
|
+
|
|
1197
|
+
// Flatten UserRole[] → { permissions: string[] }[]
|
|
1198
|
+
return {
|
|
1199
|
+
...user,
|
|
1200
|
+
roles: (user.roles as any[]).map((ur: any) => ur.role),
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1187
1203
|
`;
|
|
1188
1204
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib/auth'));
|
|
1189
1205
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/auth/require-permission.ts'), requirePermissionLib);
|
|
1190
1206
|
}
|
|
1191
|
-
if (features.includes('
|
|
1207
|
+
if (features.includes('prompt-management')) {
|
|
1192
1208
|
const promptsPage = templates.generatePromptManagementPage();
|
|
1193
1209
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/dashboard/prompts'));
|
|
1194
1210
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/dashboard/prompts/page.tsx'), promptsPage);
|
|
@@ -1200,7 +1216,7 @@ async function getServerSessionWithPermissions() {
|
|
|
1200
1216
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/prompts/[id]'));
|
|
1201
1217
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/prompts/[id]/route.ts'), promptsIdRoute);
|
|
1202
1218
|
}
|
|
1203
|
-
if (features.includes('chat')) {
|
|
1219
|
+
if (features.includes('ai-chat')) {
|
|
1204
1220
|
// Modular chat components (useChat hook + individual components)
|
|
1205
1221
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'components/chat'));
|
|
1206
1222
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'components/chat/use-chat.ts'), templates.generateUseChatHook());
|
|
@@ -1218,7 +1234,7 @@ async function getServerSessionWithPermissions() {
|
|
|
1218
1234
|
].join('\n') + '\n');
|
|
1219
1235
|
// Chat page
|
|
1220
1236
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/(app)/chat'));
|
|
1221
|
-
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/(app)/chat/page.tsx'), templates.generateChatPage());
|
|
1237
|
+
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/(app)/chat/page.tsx'), features.includes('auth') ? templates.generateChatPage() : templates.generateChatPageNoAuth());
|
|
1222
1238
|
// API routes
|
|
1223
1239
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/v1/chat/stream'));
|
|
1224
1240
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/v1/chat/stream/route.ts'), templates.generateChatStreamRouteWithPersistence());
|
|
@@ -1279,215 +1295,215 @@ async function getServerSessionWithPermissions() {
|
|
|
1279
1295
|
async function createSeedScript(targetDir, features, sqlite) {
|
|
1280
1296
|
// Without auth there are no users/providers to seed — write a minimal placeholder
|
|
1281
1297
|
if (!features.includes('auth')) {
|
|
1282
|
-
const minimalSeed = `import { PrismaClient } from '@prisma/client';
|
|
1283
|
-
const prisma = new PrismaClient();
|
|
1284
|
-
async function main() {
|
|
1285
|
-
console.log('🌱 No auth feature selected — nothing to seed yet.');
|
|
1286
|
-
console.log(' Run: chimerai add auth to add authentication.');
|
|
1287
|
-
}
|
|
1288
|
-
main().catch(console.error).finally(() => prisma.$disconnect());
|
|
1298
|
+
const minimalSeed = `import { PrismaClient } from '@prisma/client';
|
|
1299
|
+
const prisma = new PrismaClient();
|
|
1300
|
+
async function main() {
|
|
1301
|
+
console.log('🌱 No auth feature selected — nothing to seed yet.');
|
|
1302
|
+
console.log(' Run: chimerai add auth to add authentication.');
|
|
1303
|
+
}
|
|
1304
|
+
main().catch(console.error).finally(() => prisma.$disconnect());
|
|
1289
1305
|
`;
|
|
1290
1306
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'prisma/seed.ts'), minimalSeed);
|
|
1291
1307
|
return;
|
|
1292
1308
|
}
|
|
1293
1309
|
// Generate seed script with feature-specific configuration
|
|
1294
|
-
const seedScript = `import { PrismaClient } from '@prisma/client';
|
|
1295
|
-
import * as bcrypt from 'bcryptjs';
|
|
1296
|
-
import crypto from 'crypto';
|
|
1297
|
-
|
|
1298
|
-
const prisma = new PrismaClient();
|
|
1299
|
-
|
|
1300
|
-
// ── Encryption helpers (same as lib/encryption.ts) ──────────────
|
|
1301
|
-
function getKey(): Buffer {
|
|
1302
|
-
const key = process.env.PROVIDER_ENCRYPTION_KEY;
|
|
1303
|
-
if (!key) throw new Error('PROVIDER_ENCRYPTION_KEY required for seeding');
|
|
1304
|
-
if (key.length === 64 && /^[0-9a-fA-F]+$/.test(key)) {
|
|
1305
|
-
return Buffer.from(key, 'hex');
|
|
1306
|
-
}
|
|
1307
|
-
return crypto.createHash('sha256').update(key).digest();
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
function encrypt(text: string): string {
|
|
1311
|
-
if (!text) return '';
|
|
1312
|
-
const key = getKey();
|
|
1313
|
-
const iv = crypto.randomBytes(16);
|
|
1314
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv, { authTagLength: 16 });
|
|
1315
|
-
let encrypted = cipher.update(text, 'utf8', 'base64');
|
|
1316
|
-
encrypted += cipher.final('base64');
|
|
1317
|
-
const authTag = cipher.getAuthTag();
|
|
1318
|
-
return iv.toString('base64') + ':' + authTag.toString('base64') + ':' + encrypted;
|
|
1319
|
-
}
|
|
1320
|
-
// ────────────────────────────────────────────────────────────────
|
|
1321
|
-
|
|
1322
|
-
async function main() {
|
|
1323
|
-
console.log('🌱 Seeding database...');
|
|
1324
|
-
|
|
1325
|
-
// Create default admin user (next-auth is always a core dependency)
|
|
1326
|
-
const hashedPassword = await bcrypt.hash('admin123', 10);
|
|
1327
|
-
const admin = await prisma.user.upsert({
|
|
1328
|
-
where: { email: 'admin@example.com' },
|
|
1329
|
-
update: {},
|
|
1330
|
-
create: {
|
|
1331
|
-
email: 'admin@example.com',
|
|
1332
|
-
name: 'Admin User',
|
|
1333
|
-
password: hashedPassword,
|
|
1334
|
-
},
|
|
1335
|
-
});
|
|
1336
|
-
|
|
1337
|
-
console.log('✅ Admin user created: admin@example.com / admin123');
|
|
1310
|
+
const seedScript = `import { PrismaClient } from '@prisma/client';
|
|
1311
|
+
import * as bcrypt from 'bcryptjs';
|
|
1312
|
+
import crypto from 'crypto';
|
|
1313
|
+
|
|
1314
|
+
const prisma = new PrismaClient();
|
|
1315
|
+
|
|
1316
|
+
// ── Encryption helpers (same as lib/encryption.ts) ──────────────
|
|
1317
|
+
function getKey(): Buffer {
|
|
1318
|
+
const key = process.env.PROVIDER_ENCRYPTION_KEY;
|
|
1319
|
+
if (!key) throw new Error('PROVIDER_ENCRYPTION_KEY required for seeding');
|
|
1320
|
+
if (key.length === 64 && /^[0-9a-fA-F]+$/.test(key)) {
|
|
1321
|
+
return Buffer.from(key, 'hex');
|
|
1322
|
+
}
|
|
1323
|
+
return crypto.createHash('sha256').update(key).digest();
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
function encrypt(text: string): string {
|
|
1327
|
+
if (!text) return '';
|
|
1328
|
+
const key = getKey();
|
|
1329
|
+
const iv = crypto.randomBytes(16);
|
|
1330
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv, { authTagLength: 16 });
|
|
1331
|
+
let encrypted = cipher.update(text, 'utf8', 'base64');
|
|
1332
|
+
encrypted += cipher.final('base64');
|
|
1333
|
+
const authTag = cipher.getAuthTag();
|
|
1334
|
+
return iv.toString('base64') + ':' + authTag.toString('base64') + ':' + encrypted;
|
|
1335
|
+
}
|
|
1336
|
+
// ────────────────────────────────────────────────────────────────
|
|
1337
|
+
|
|
1338
|
+
async function main() {
|
|
1339
|
+
console.log('🌱 Seeding database...');
|
|
1340
|
+
|
|
1341
|
+
// Create default admin user (next-auth is always a core dependency)
|
|
1342
|
+
const hashedPassword = await bcrypt.hash('admin123', 10);
|
|
1343
|
+
const admin = await prisma.user.upsert({
|
|
1344
|
+
where: { email: 'admin@example.com' },
|
|
1345
|
+
update: {},
|
|
1346
|
+
create: {
|
|
1347
|
+
email: 'admin@example.com',
|
|
1348
|
+
name: 'Admin User',
|
|
1349
|
+
password: hashedPassword,
|
|
1350
|
+
},
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
console.log('✅ Admin user created: admin@example.com / admin123');
|
|
1338
1354
|
${features.includes('rbac')
|
|
1339
|
-
? `
|
|
1355
|
+
? `
|
|
1340
1356
|
${sqlite
|
|
1341
|
-
? ` // Create default roles (SQLite-compatible: individual upserts)
|
|
1342
|
-
for (const role of [
|
|
1343
|
-
{ name: 'admin', description: 'Full system access', permissions: JSON.stringify(['*']) },
|
|
1344
|
-
{ name: 'user', description: 'Regular user access', permissions: JSON.stringify(['chat:read', 'chat:write', 'profile:read']) },
|
|
1345
|
-
{ name: 'viewer', description: 'Read-only access', permissions: JSON.stringify(['chat:read']) },
|
|
1346
|
-
]) {
|
|
1347
|
-
await prisma.role.upsert({
|
|
1348
|
-
where: { name: role.name },
|
|
1349
|
-
update: {},
|
|
1350
|
-
create: role,
|
|
1351
|
-
});
|
|
1357
|
+
? ` // Create default roles (SQLite-compatible: individual upserts)
|
|
1358
|
+
for (const role of [
|
|
1359
|
+
{ name: 'admin', description: 'Full system access', permissions: JSON.stringify(['*']) },
|
|
1360
|
+
{ name: 'user', description: 'Regular user access', permissions: JSON.stringify(['chat:read', 'chat:write', 'profile:read']) },
|
|
1361
|
+
{ name: 'viewer', description: 'Read-only access', permissions: JSON.stringify(['chat:read']) },
|
|
1362
|
+
]) {
|
|
1363
|
+
await prisma.role.upsert({
|
|
1364
|
+
where: { name: role.name },
|
|
1365
|
+
update: {},
|
|
1366
|
+
create: role,
|
|
1367
|
+
});
|
|
1352
1368
|
}`
|
|
1353
|
-
: ` // Create default roles
|
|
1354
|
-
await prisma.role.createMany({
|
|
1355
|
-
data: [
|
|
1356
|
-
{
|
|
1357
|
-
name: 'admin',
|
|
1358
|
-
description: 'Full system access',
|
|
1359
|
-
permissions: ['*'],
|
|
1360
|
-
},
|
|
1361
|
-
{
|
|
1362
|
-
name: 'user',
|
|
1363
|
-
description: 'Regular user access',
|
|
1364
|
-
permissions: ['chat:read', 'chat:write', 'profile:read'],
|
|
1365
|
-
},
|
|
1366
|
-
{
|
|
1367
|
-
name: 'viewer',
|
|
1368
|
-
description: 'Read-only access',
|
|
1369
|
-
permissions: ['chat:read'],
|
|
1370
|
-
},
|
|
1371
|
-
],
|
|
1372
|
-
skipDuplicates: true,
|
|
1373
|
-
});`}
|
|
1374
|
-
|
|
1375
|
-
console.log('✅ Default roles created');
|
|
1376
|
-
|
|
1377
|
-
// Assign admin role to admin user
|
|
1378
|
-
const adminRole = await prisma.role.findUnique({ where: { name: 'admin' } });
|
|
1379
|
-
if (adminRole) {
|
|
1380
|
-
await prisma.userRole.upsert({
|
|
1381
|
-
where: { userId_roleId: { userId: admin.id, roleId: adminRole.id } },
|
|
1382
|
-
update: {},
|
|
1383
|
-
create: { userId: admin.id, roleId: adminRole.id },
|
|
1384
|
-
});
|
|
1385
|
-
console.log('✅ Admin user assigned admin role');
|
|
1386
|
-
}
|
|
1369
|
+
: ` // Create default roles
|
|
1370
|
+
await prisma.role.createMany({
|
|
1371
|
+
data: [
|
|
1372
|
+
{
|
|
1373
|
+
name: 'admin',
|
|
1374
|
+
description: 'Full system access',
|
|
1375
|
+
permissions: ['*'],
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
name: 'user',
|
|
1379
|
+
description: 'Regular user access',
|
|
1380
|
+
permissions: ['chat:read', 'chat:write', 'profile:read'],
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
name: 'viewer',
|
|
1384
|
+
description: 'Read-only access',
|
|
1385
|
+
permissions: ['chat:read'],
|
|
1386
|
+
},
|
|
1387
|
+
],
|
|
1388
|
+
skipDuplicates: true,
|
|
1389
|
+
});`}
|
|
1390
|
+
|
|
1391
|
+
console.log('✅ Default roles created');
|
|
1392
|
+
|
|
1393
|
+
// Assign admin role to admin user
|
|
1394
|
+
const adminRole = await prisma.role.findUnique({ where: { name: 'admin' } });
|
|
1395
|
+
if (adminRole) {
|
|
1396
|
+
await prisma.userRole.upsert({
|
|
1397
|
+
where: { userId_roleId: { userId: admin.id, roleId: adminRole.id } },
|
|
1398
|
+
update: {},
|
|
1399
|
+
create: { userId: admin.id, roleId: adminRole.id },
|
|
1400
|
+
});
|
|
1401
|
+
console.log('✅ Admin user assigned admin role');
|
|
1402
|
+
}
|
|
1387
1403
|
`
|
|
1388
|
-
: ''}
|
|
1389
|
-
// ── Seed Providers (if API keys are in .env) ────────────────────
|
|
1390
|
-
const openaiKey = process.env.OPENAI_API_KEY;
|
|
1391
|
-
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
1392
|
-
|
|
1393
|
-
if (openaiKey) {
|
|
1394
|
-
const provider = await prisma.provider.upsert({
|
|
1395
|
-
where: { id: 'seed-openai' },
|
|
1396
|
-
update: {},
|
|
1397
|
-
create: {
|
|
1398
|
-
id: 'seed-openai',
|
|
1399
|
-
name: 'OpenAI',
|
|
1400
|
-
type: 'openai',
|
|
1401
|
-
description: 'OpenAI API (seeded from .env)',
|
|
1402
|
-
baseUrl: 'https://api.openai.com/v1',
|
|
1403
|
-
apiKey: encrypt(openaiKey),
|
|
1404
|
-
config: ${sqlite ? `JSON.stringify({ defaultModel: 'gpt-4o-mini' })` : `{ defaultModel: 'gpt-4o-mini' }`},
|
|
1405
|
-
status: 'active',
|
|
1406
|
-
isDefault: true,
|
|
1407
|
-
priority: 0,
|
|
1408
|
-
createdBy: admin.id,
|
|
1409
|
-
},
|
|
1410
|
-
});
|
|
1411
|
-
|
|
1412
|
-
// Create default OpenAI models
|
|
1404
|
+
: ''}
|
|
1405
|
+
// ── Seed Providers (if API keys are in .env) ────────────────────
|
|
1406
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
1407
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
1408
|
+
|
|
1409
|
+
if (openaiKey) {
|
|
1410
|
+
const provider = await prisma.provider.upsert({
|
|
1411
|
+
where: { id: 'seed-openai' },
|
|
1412
|
+
update: {},
|
|
1413
|
+
create: {
|
|
1414
|
+
id: 'seed-openai',
|
|
1415
|
+
name: 'OpenAI',
|
|
1416
|
+
type: 'openai',
|
|
1417
|
+
description: 'OpenAI API (seeded from .env)',
|
|
1418
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
1419
|
+
apiKey: encrypt(openaiKey),
|
|
1420
|
+
config: ${sqlite ? `JSON.stringify({ defaultModel: 'gpt-4o-mini' })` : `{ defaultModel: 'gpt-4o-mini' }`},
|
|
1421
|
+
status: 'active',
|
|
1422
|
+
isDefault: true,
|
|
1423
|
+
priority: 0,
|
|
1424
|
+
createdBy: admin.id,
|
|
1425
|
+
},
|
|
1426
|
+
});
|
|
1427
|
+
|
|
1428
|
+
// Create default OpenAI models
|
|
1413
1429
|
${sqlite
|
|
1414
|
-
? ` for (const m of [
|
|
1415
|
-
{ providerId: provider.id, modelId: 'gpt-4o', name: 'GPT-4o', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 128000, inputCost: 2.5, outputCost: 10 },
|
|
1416
|
-
{ providerId: provider.id, modelId: 'gpt-4o-mini', name: 'GPT-4o Mini', capabilities: JSON.stringify(['chat']), contextWindow: 128000, inputCost: 0.15, outputCost: 0.6 },
|
|
1417
|
-
{ providerId: provider.id, modelId: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 128000, inputCost: 10, outputCost: 30 },
|
|
1418
|
-
{ providerId: provider.id, modelId: 'text-embedding-3-small', name: 'Embedding 3 Small', capabilities: JSON.stringify(['embedding']), contextWindow: 8191, inputCost: 0.02, outputCost: 0 },
|
|
1419
|
-
{ providerId: provider.id, modelId: 'text-embedding-3-large', name: 'Embedding 3 Large', capabilities: JSON.stringify(['embedding']), contextWindow: 8191, inputCost: 0.13, outputCost: 0 },
|
|
1420
|
-
]) {
|
|
1421
|
-
try { await prisma.model.create({ data: m }); } catch { /* skip duplicate */ }
|
|
1430
|
+
? ` for (const m of [
|
|
1431
|
+
{ providerId: provider.id, modelId: 'gpt-4o', name: 'GPT-4o', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 128000, inputCost: 2.5, outputCost: 10 },
|
|
1432
|
+
{ providerId: provider.id, modelId: 'gpt-4o-mini', name: 'GPT-4o Mini', capabilities: JSON.stringify(['chat']), contextWindow: 128000, inputCost: 0.15, outputCost: 0.6 },
|
|
1433
|
+
{ providerId: provider.id, modelId: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 128000, inputCost: 10, outputCost: 30 },
|
|
1434
|
+
{ providerId: provider.id, modelId: 'text-embedding-3-small', name: 'Embedding 3 Small', capabilities: JSON.stringify(['embedding']), contextWindow: 8191, inputCost: 0.02, outputCost: 0 },
|
|
1435
|
+
{ providerId: provider.id, modelId: 'text-embedding-3-large', name: 'Embedding 3 Large', capabilities: JSON.stringify(['embedding']), contextWindow: 8191, inputCost: 0.13, outputCost: 0 },
|
|
1436
|
+
]) {
|
|
1437
|
+
try { await prisma.model.create({ data: m }); } catch { /* skip duplicate */ }
|
|
1422
1438
|
}`
|
|
1423
|
-
: ` await prisma.model.createMany({
|
|
1424
|
-
data: [
|
|
1425
|
-
{ providerId: provider.id, modelId: 'gpt-4o', name: 'GPT-4o', capabilities: ['chat', 'vision'], contextWindow: 128000, inputCost: 2.5, outputCost: 10 },
|
|
1426
|
-
{ providerId: provider.id, modelId: 'gpt-4o-mini', name: 'GPT-4o Mini', capabilities: ['chat'], contextWindow: 128000, inputCost: 0.15, outputCost: 0.6 },
|
|
1427
|
-
{ providerId: provider.id, modelId: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: ['chat', 'vision'], contextWindow: 128000, inputCost: 10, outputCost: 30 },
|
|
1428
|
-
{ providerId: provider.id, modelId: 'text-embedding-3-small', name: 'Embedding 3 Small', capabilities: ['embedding'], contextWindow: 8191, inputCost: 0.02, outputCost: 0 },
|
|
1429
|
-
{ providerId: provider.id, modelId: 'text-embedding-3-large', name: 'Embedding 3 Large', capabilities: ['embedding'], contextWindow: 8191, inputCost: 0.13, outputCost: 0 },
|
|
1430
|
-
],
|
|
1431
|
-
skipDuplicates: true,
|
|
1432
|
-
});`}
|
|
1433
|
-
|
|
1434
|
-
console.log('✅ OpenAI provider seeded with models');
|
|
1435
|
-
}
|
|
1436
|
-
|
|
1437
|
-
if (anthropicKey) {
|
|
1438
|
-
const provider = await prisma.provider.upsert({
|
|
1439
|
-
where: { id: 'seed-anthropic' },
|
|
1440
|
-
update: {},
|
|
1441
|
-
create: {
|
|
1442
|
-
id: 'seed-anthropic',
|
|
1443
|
-
name: 'Anthropic',
|
|
1444
|
-
type: 'anthropic',
|
|
1445
|
-
description: 'Anthropic Claude API (seeded from .env)',
|
|
1446
|
-
baseUrl: 'https://api.anthropic.com/v1',
|
|
1447
|
-
apiKey: encrypt(anthropicKey),
|
|
1448
|
-
config: ${sqlite ? `JSON.stringify({ defaultModel: 'claude-sonnet-4-20250514' })` : `{ defaultModel: 'claude-sonnet-4-20250514' }`},
|
|
1449
|
-
status: 'active',
|
|
1450
|
-
isDefault: false,
|
|
1451
|
-
priority: 1,
|
|
1452
|
-
createdBy: admin.id,
|
|
1453
|
-
},
|
|
1454
|
-
});
|
|
1455
|
-
|
|
1439
|
+
: ` await prisma.model.createMany({
|
|
1440
|
+
data: [
|
|
1441
|
+
{ providerId: provider.id, modelId: 'gpt-4o', name: 'GPT-4o', capabilities: ['chat', 'vision'], contextWindow: 128000, inputCost: 2.5, outputCost: 10 },
|
|
1442
|
+
{ providerId: provider.id, modelId: 'gpt-4o-mini', name: 'GPT-4o Mini', capabilities: ['chat'], contextWindow: 128000, inputCost: 0.15, outputCost: 0.6 },
|
|
1443
|
+
{ providerId: provider.id, modelId: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: ['chat', 'vision'], contextWindow: 128000, inputCost: 10, outputCost: 30 },
|
|
1444
|
+
{ providerId: provider.id, modelId: 'text-embedding-3-small', name: 'Embedding 3 Small', capabilities: ['embedding'], contextWindow: 8191, inputCost: 0.02, outputCost: 0 },
|
|
1445
|
+
{ providerId: provider.id, modelId: 'text-embedding-3-large', name: 'Embedding 3 Large', capabilities: ['embedding'], contextWindow: 8191, inputCost: 0.13, outputCost: 0 },
|
|
1446
|
+
],
|
|
1447
|
+
skipDuplicates: true,
|
|
1448
|
+
});`}
|
|
1449
|
+
|
|
1450
|
+
console.log('✅ OpenAI provider seeded with models');
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
if (anthropicKey) {
|
|
1454
|
+
const provider = await prisma.provider.upsert({
|
|
1455
|
+
where: { id: 'seed-anthropic' },
|
|
1456
|
+
update: {},
|
|
1457
|
+
create: {
|
|
1458
|
+
id: 'seed-anthropic',
|
|
1459
|
+
name: 'Anthropic',
|
|
1460
|
+
type: 'anthropic',
|
|
1461
|
+
description: 'Anthropic Claude API (seeded from .env)',
|
|
1462
|
+
baseUrl: 'https://api.anthropic.com/v1',
|
|
1463
|
+
apiKey: encrypt(anthropicKey),
|
|
1464
|
+
config: ${sqlite ? `JSON.stringify({ defaultModel: 'claude-sonnet-4-20250514' })` : `{ defaultModel: 'claude-sonnet-4-20250514' }`},
|
|
1465
|
+
status: 'active',
|
|
1466
|
+
isDefault: false,
|
|
1467
|
+
priority: 1,
|
|
1468
|
+
createdBy: admin.id,
|
|
1469
|
+
},
|
|
1470
|
+
});
|
|
1471
|
+
|
|
1456
1472
|
${sqlite
|
|
1457
|
-
? ` for (const m of [
|
|
1458
|
-
{ providerId: provider.id, modelId: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1459
|
-
{ 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 },
|
|
1460
|
-
{ providerId: provider.id, modelId: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', capabilities: JSON.stringify(['chat']), contextWindow: 200000, inputCost: 0.25, outputCost: 1.25 },
|
|
1461
|
-
]) {
|
|
1462
|
-
try { await prisma.model.create({ data: m }); } catch { /* skip duplicate */ }
|
|
1473
|
+
? ` for (const m of [
|
|
1474
|
+
{ providerId: provider.id, modelId: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1475
|
+
{ 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 },
|
|
1476
|
+
{ providerId: provider.id, modelId: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', capabilities: JSON.stringify(['chat']), contextWindow: 200000, inputCost: 0.25, outputCost: 1.25 },
|
|
1477
|
+
]) {
|
|
1478
|
+
try { await prisma.model.create({ data: m }); } catch { /* skip duplicate */ }
|
|
1463
1479
|
}`
|
|
1464
|
-
: ` await prisma.model.createMany({
|
|
1465
|
-
data: [
|
|
1466
|
-
{ providerId: provider.id, modelId: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', capabilities: ['chat', 'vision'], contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1467
|
-
{ providerId: provider.id, modelId: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', capabilities: ['chat', 'vision'], contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1468
|
-
{ providerId: provider.id, modelId: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', capabilities: ['chat'], contextWindow: 200000, inputCost: 0.25, outputCost: 1.25 },
|
|
1469
|
-
],
|
|
1470
|
-
skipDuplicates: true,
|
|
1471
|
-
});`}
|
|
1472
|
-
|
|
1473
|
-
console.log('✅ Anthropic provider seeded with models');
|
|
1474
|
-
}
|
|
1475
|
-
|
|
1476
|
-
if (!openaiKey && !anthropicKey) {
|
|
1477
|
-
console.log('ℹ️ No API keys in .env — skip provider seeding. Add via Provider Management UI.');
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
console.log('🎉 Seeding completed!');
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
main()
|
|
1484
|
-
.catch((e) => {
|
|
1485
|
-
console.error('❌ Seeding failed:', e);
|
|
1486
|
-
process.exit(1);
|
|
1487
|
-
})
|
|
1488
|
-
.finally(async () => {
|
|
1489
|
-
await prisma.$disconnect();
|
|
1490
|
-
});
|
|
1480
|
+
: ` await prisma.model.createMany({
|
|
1481
|
+
data: [
|
|
1482
|
+
{ providerId: provider.id, modelId: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', capabilities: ['chat', 'vision'], contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1483
|
+
{ providerId: provider.id, modelId: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', capabilities: ['chat', 'vision'], contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1484
|
+
{ providerId: provider.id, modelId: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', capabilities: ['chat'], contextWindow: 200000, inputCost: 0.25, outputCost: 1.25 },
|
|
1485
|
+
],
|
|
1486
|
+
skipDuplicates: true,
|
|
1487
|
+
});`}
|
|
1488
|
+
|
|
1489
|
+
console.log('✅ Anthropic provider seeded with models');
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1492
|
+
if (!openaiKey && !anthropicKey) {
|
|
1493
|
+
console.log('ℹ️ No API keys in .env — skip provider seeding. Add via Provider Management UI.');
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
console.log('🎉 Seeding completed!');
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
main()
|
|
1500
|
+
.catch((e) => {
|
|
1501
|
+
console.error('❌ Seeding failed:', e);
|
|
1502
|
+
process.exit(1);
|
|
1503
|
+
})
|
|
1504
|
+
.finally(async () => {
|
|
1505
|
+
await prisma.$disconnect();
|
|
1506
|
+
});
|
|
1491
1507
|
`;
|
|
1492
1508
|
const seedDest = path_1.default.join(targetDir, 'prisma/seed.ts');
|
|
1493
1509
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(seedDest));
|
|
@@ -1500,208 +1516,208 @@ async function createDockerCompose(targetDir) {
|
|
|
1500
1516
|
async function createInstallScripts(targetDir, features, sqlite) {
|
|
1501
1517
|
// Windows install.bat
|
|
1502
1518
|
const installBat = sqlite
|
|
1503
|
-
? `@echo off
|
|
1504
|
-
REM Installation Script for ChimerAI Project (SQLite)
|
|
1505
|
-
echo.
|
|
1506
|
-
echo ================================================
|
|
1507
|
-
echo ChimerAI Project Setup (SQLite - No Docker needed)
|
|
1508
|
-
echo ================================================
|
|
1509
|
-
echo.
|
|
1510
|
-
|
|
1511
|
-
REM Check Node.js
|
|
1512
|
-
where node >nul 2>nul
|
|
1513
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1514
|
-
echo [ERROR] Node.js is not installed
|
|
1515
|
-
pause
|
|
1516
|
-
exit /b 1
|
|
1517
|
-
)
|
|
1518
|
-
|
|
1519
|
-
echo [1/3] Installing dependencies...
|
|
1520
|
-
call npm install
|
|
1521
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1522
|
-
echo [ERROR] Failed to install dependencies
|
|
1523
|
-
pause
|
|
1524
|
-
exit /b 1
|
|
1525
|
-
)
|
|
1526
|
-
|
|
1527
|
-
echo [2/3] Setting up database...
|
|
1528
|
-
call npm run db:push
|
|
1529
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1530
|
-
echo [ERROR] Failed to setup database
|
|
1531
|
-
pause
|
|
1532
|
-
exit /b 1
|
|
1533
|
-
)
|
|
1534
|
-
|
|
1535
|
-
echo [3/3] Seeding database...
|
|
1536
|
-
call npm run db:seed
|
|
1537
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1538
|
-
echo [WARNING] Seeding failed, but you can continue
|
|
1539
|
-
)
|
|
1540
|
-
|
|
1541
|
-
echo.
|
|
1542
|
-
echo ================================================
|
|
1543
|
-
echo Setup completed successfully!
|
|
1544
|
-
echo ================================================
|
|
1545
|
-
echo.
|
|
1546
|
-
echo Next steps:
|
|
1547
|
-
echo npm run dev
|
|
1548
|
-
echo Open: http://localhost:3001
|
|
1549
|
-
echo.
|
|
1550
|
-
${features.includes('auth') ? 'echo Login with:\necho Email: admin@example.com\necho Password: admin123\necho.' : ''}
|
|
1551
|
-
pause
|
|
1519
|
+
? `@echo off
|
|
1520
|
+
REM Installation Script for ChimerAI Project (SQLite)
|
|
1521
|
+
echo.
|
|
1522
|
+
echo ================================================
|
|
1523
|
+
echo ChimerAI Project Setup (SQLite - No Docker needed)
|
|
1524
|
+
echo ================================================
|
|
1525
|
+
echo.
|
|
1526
|
+
|
|
1527
|
+
REM Check Node.js
|
|
1528
|
+
where node >nul 2>nul
|
|
1529
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1530
|
+
echo [ERROR] Node.js is not installed
|
|
1531
|
+
pause
|
|
1532
|
+
exit /b 1
|
|
1533
|
+
)
|
|
1534
|
+
|
|
1535
|
+
echo [1/3] Installing dependencies...
|
|
1536
|
+
call npm install
|
|
1537
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1538
|
+
echo [ERROR] Failed to install dependencies
|
|
1539
|
+
pause
|
|
1540
|
+
exit /b 1
|
|
1541
|
+
)
|
|
1542
|
+
|
|
1543
|
+
echo [2/3] Setting up database...
|
|
1544
|
+
call npm run db:push
|
|
1545
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1546
|
+
echo [ERROR] Failed to setup database
|
|
1547
|
+
pause
|
|
1548
|
+
exit /b 1
|
|
1549
|
+
)
|
|
1550
|
+
|
|
1551
|
+
echo [3/3] Seeding database...
|
|
1552
|
+
call npm run db:seed
|
|
1553
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1554
|
+
echo [WARNING] Seeding failed, but you can continue
|
|
1555
|
+
)
|
|
1556
|
+
|
|
1557
|
+
echo.
|
|
1558
|
+
echo ================================================
|
|
1559
|
+
echo Setup completed successfully!
|
|
1560
|
+
echo ================================================
|
|
1561
|
+
echo.
|
|
1562
|
+
echo Next steps:
|
|
1563
|
+
echo npm run dev
|
|
1564
|
+
echo Open: http://localhost:3001
|
|
1565
|
+
echo.
|
|
1566
|
+
${features.includes('auth') ? 'echo Login with:\necho Email: admin@example.com\necho Password: admin123\necho.' : ''}
|
|
1567
|
+
pause
|
|
1552
1568
|
`
|
|
1553
|
-
: `@echo off
|
|
1554
|
-
REM Installation Script for ChimerAI Project
|
|
1555
|
-
echo.
|
|
1556
|
-
echo ================================================
|
|
1557
|
-
echo ChimerAI Project Setup
|
|
1558
|
-
echo ================================================
|
|
1559
|
-
echo.
|
|
1560
|
-
|
|
1561
|
-
REM Check Node.js
|
|
1562
|
-
where node >nul 2>nul
|
|
1563
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1564
|
-
echo [ERROR] Node.js is not installed
|
|
1565
|
-
pause
|
|
1566
|
-
exit /b 1
|
|
1567
|
-
)
|
|
1568
|
-
|
|
1569
|
-
REM Check Docker
|
|
1570
|
-
where docker >nul 2>nul
|
|
1571
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1572
|
-
echo [ERROR] Docker is not installed
|
|
1573
|
-
pause
|
|
1574
|
-
exit /b 1
|
|
1575
|
-
)
|
|
1576
|
-
|
|
1577
|
-
echo [1/5] Installing dependencies...
|
|
1578
|
-
call npm install
|
|
1579
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1580
|
-
echo [ERROR] Failed to install dependencies
|
|
1581
|
-
pause
|
|
1582
|
-
exit /b 1
|
|
1583
|
-
)
|
|
1584
|
-
|
|
1585
|
-
echo [2/5] Starting Docker containers...
|
|
1586
|
-
call docker-compose up -d
|
|
1587
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1588
|
-
echo [ERROR] Failed to start Docker
|
|
1589
|
-
pause
|
|
1590
|
-
exit /b 1
|
|
1591
|
-
)
|
|
1592
|
-
|
|
1593
|
-
echo [3/5] Waiting for database...
|
|
1594
|
-
timeout /t 5 /nobreak >nul
|
|
1595
|
-
|
|
1596
|
-
echo [4/5] Setting up database...
|
|
1597
|
-
call npm run db:push
|
|
1598
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1599
|
-
echo [ERROR] Failed to setup database
|
|
1600
|
-
pause
|
|
1601
|
-
exit /b 1
|
|
1602
|
-
)
|
|
1603
|
-
|
|
1604
|
-
echo [5/5] Seeding database...
|
|
1605
|
-
call npm run db:seed
|
|
1606
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1607
|
-
echo [WARNING] Seeding failed, but you can continue
|
|
1608
|
-
)
|
|
1609
|
-
|
|
1610
|
-
echo.
|
|
1611
|
-
echo ================================================
|
|
1612
|
-
echo Setup completed successfully!
|
|
1613
|
-
echo ================================================
|
|
1614
|
-
echo.
|
|
1615
|
-
echo Next steps:
|
|
1616
|
-
echo npm run dev
|
|
1617
|
-
echo Open: http://localhost:3001
|
|
1618
|
-
echo.
|
|
1619
|
-
${features.includes('auth') ? 'echo Login with:\necho Email: admin@example.com\necho Password: admin123\necho.' : ''}
|
|
1620
|
-
pause
|
|
1569
|
+
: `@echo off
|
|
1570
|
+
REM Installation Script for ChimerAI Project
|
|
1571
|
+
echo.
|
|
1572
|
+
echo ================================================
|
|
1573
|
+
echo ChimerAI Project Setup
|
|
1574
|
+
echo ================================================
|
|
1575
|
+
echo.
|
|
1576
|
+
|
|
1577
|
+
REM Check Node.js
|
|
1578
|
+
where node >nul 2>nul
|
|
1579
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1580
|
+
echo [ERROR] Node.js is not installed
|
|
1581
|
+
pause
|
|
1582
|
+
exit /b 1
|
|
1583
|
+
)
|
|
1584
|
+
|
|
1585
|
+
REM Check Docker
|
|
1586
|
+
where docker >nul 2>nul
|
|
1587
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1588
|
+
echo [ERROR] Docker is not installed
|
|
1589
|
+
pause
|
|
1590
|
+
exit /b 1
|
|
1591
|
+
)
|
|
1592
|
+
|
|
1593
|
+
echo [1/5] Installing dependencies...
|
|
1594
|
+
call npm install
|
|
1595
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1596
|
+
echo [ERROR] Failed to install dependencies
|
|
1597
|
+
pause
|
|
1598
|
+
exit /b 1
|
|
1599
|
+
)
|
|
1600
|
+
|
|
1601
|
+
echo [2/5] Starting Docker containers...
|
|
1602
|
+
call docker-compose up -d
|
|
1603
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1604
|
+
echo [ERROR] Failed to start Docker
|
|
1605
|
+
pause
|
|
1606
|
+
exit /b 1
|
|
1607
|
+
)
|
|
1608
|
+
|
|
1609
|
+
echo [3/5] Waiting for database...
|
|
1610
|
+
timeout /t 5 /nobreak >nul
|
|
1611
|
+
|
|
1612
|
+
echo [4/5] Setting up database...
|
|
1613
|
+
call npm run db:push
|
|
1614
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1615
|
+
echo [ERROR] Failed to setup database
|
|
1616
|
+
pause
|
|
1617
|
+
exit /b 1
|
|
1618
|
+
)
|
|
1619
|
+
|
|
1620
|
+
echo [5/5] Seeding database...
|
|
1621
|
+
call npm run db:seed
|
|
1622
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1623
|
+
echo [WARNING] Seeding failed, but you can continue
|
|
1624
|
+
)
|
|
1625
|
+
|
|
1626
|
+
echo.
|
|
1627
|
+
echo ================================================
|
|
1628
|
+
echo Setup completed successfully!
|
|
1629
|
+
echo ================================================
|
|
1630
|
+
echo.
|
|
1631
|
+
echo Next steps:
|
|
1632
|
+
echo npm run dev
|
|
1633
|
+
echo Open: http://localhost:3001
|
|
1634
|
+
echo.
|
|
1635
|
+
${features.includes('auth') ? 'echo Login with:\necho Email: admin@example.com\necho Password: admin123\necho.' : ''}
|
|
1636
|
+
pause
|
|
1621
1637
|
`;
|
|
1622
1638
|
// Linux/macOS install.sh
|
|
1623
1639
|
const installSh = sqlite
|
|
1624
|
-
? `#!/bin/bash
|
|
1625
|
-
set -e
|
|
1626
|
-
|
|
1627
|
-
echo ""
|
|
1628
|
-
echo "================================================"
|
|
1629
|
-
echo " ChimerAI Project Setup (SQLite - No Docker needed)"
|
|
1630
|
-
echo "================================================"
|
|
1631
|
-
echo ""
|
|
1632
|
-
|
|
1633
|
-
# Check Node.js
|
|
1634
|
-
if ! command -v node &> /dev/null; then
|
|
1635
|
-
echo "[ERROR] Node.js is not installed"
|
|
1636
|
-
exit 1
|
|
1637
|
-
fi
|
|
1638
|
-
|
|
1639
|
-
echo "[1/3] Installing dependencies..."
|
|
1640
|
-
npm install
|
|
1641
|
-
|
|
1642
|
-
echo "[2/3] Setting up database..."
|
|
1643
|
-
npm run db:push
|
|
1644
|
-
|
|
1645
|
-
echo "[3/3] Seeding database..."
|
|
1646
|
-
npm run db:seed || echo "[WARNING] Seeding failed, but you can continue"
|
|
1647
|
-
|
|
1648
|
-
echo ""
|
|
1649
|
-
echo "================================================"
|
|
1650
|
-
echo " Setup completed successfully!"
|
|
1651
|
-
echo "================================================"
|
|
1652
|
-
echo ""
|
|
1653
|
-
echo "Next steps:"
|
|
1654
|
-
echo " npm run dev"
|
|
1655
|
-
echo " Open: http://localhost:3001"
|
|
1656
|
-
echo ""
|
|
1657
|
-
${features.includes('auth') ? 'echo "Login with:"\necho " Email: admin@example.com"\necho " Password: admin123"\necho ""' : ''}
|
|
1640
|
+
? `#!/bin/bash
|
|
1641
|
+
set -e
|
|
1642
|
+
|
|
1643
|
+
echo ""
|
|
1644
|
+
echo "================================================"
|
|
1645
|
+
echo " ChimerAI Project Setup (SQLite - No Docker needed)"
|
|
1646
|
+
echo "================================================"
|
|
1647
|
+
echo ""
|
|
1648
|
+
|
|
1649
|
+
# Check Node.js
|
|
1650
|
+
if ! command -v node &> /dev/null; then
|
|
1651
|
+
echo "[ERROR] Node.js is not installed"
|
|
1652
|
+
exit 1
|
|
1653
|
+
fi
|
|
1654
|
+
|
|
1655
|
+
echo "[1/3] Installing dependencies..."
|
|
1656
|
+
npm install
|
|
1657
|
+
|
|
1658
|
+
echo "[2/3] Setting up database..."
|
|
1659
|
+
npm run db:push
|
|
1660
|
+
|
|
1661
|
+
echo "[3/3] Seeding database..."
|
|
1662
|
+
npm run db:seed || echo "[WARNING] Seeding failed, but you can continue"
|
|
1663
|
+
|
|
1664
|
+
echo ""
|
|
1665
|
+
echo "================================================"
|
|
1666
|
+
echo " Setup completed successfully!"
|
|
1667
|
+
echo "================================================"
|
|
1668
|
+
echo ""
|
|
1669
|
+
echo "Next steps:"
|
|
1670
|
+
echo " npm run dev"
|
|
1671
|
+
echo " Open: http://localhost:3001"
|
|
1672
|
+
echo ""
|
|
1673
|
+
${features.includes('auth') ? 'echo "Login with:"\necho " Email: admin@example.com"\necho " Password: admin123"\necho ""' : ''}
|
|
1658
1674
|
`
|
|
1659
|
-
: `#!/bin/bash
|
|
1660
|
-
set -e
|
|
1661
|
-
|
|
1662
|
-
echo ""
|
|
1663
|
-
echo "================================================"
|
|
1664
|
-
echo " ChimerAI Project Setup"
|
|
1665
|
-
echo "================================================"
|
|
1666
|
-
echo ""
|
|
1667
|
-
|
|
1668
|
-
# Check Node.js
|
|
1669
|
-
if ! command -v node &> /dev/null; then
|
|
1670
|
-
echo "[ERROR] Node.js is not installed"
|
|
1671
|
-
exit 1
|
|
1672
|
-
fi
|
|
1673
|
-
|
|
1674
|
-
# Check Docker
|
|
1675
|
-
if ! command -v docker &> /dev/null; then
|
|
1676
|
-
echo "[ERROR] Docker is not installed"
|
|
1677
|
-
exit 1
|
|
1678
|
-
fi
|
|
1679
|
-
|
|
1680
|
-
echo "[1/5] Installing dependencies..."
|
|
1681
|
-
npm install
|
|
1682
|
-
|
|
1683
|
-
echo "[2/5] Starting Docker containers..."
|
|
1684
|
-
docker-compose up -d
|
|
1685
|
-
|
|
1686
|
-
echo "[3/5] Waiting for database..."
|
|
1687
|
-
sleep 5
|
|
1688
|
-
|
|
1689
|
-
echo "[4/5] Setting up database..."
|
|
1690
|
-
npm run db:push
|
|
1691
|
-
|
|
1692
|
-
echo "[5/5] Seeding database..."
|
|
1693
|
-
npm run db:seed || echo "[WARNING] Seeding failed, but you can continue"
|
|
1694
|
-
|
|
1695
|
-
echo ""
|
|
1696
|
-
echo "================================================"
|
|
1697
|
-
echo " Setup completed successfully!"
|
|
1698
|
-
echo "================================================"
|
|
1699
|
-
echo ""
|
|
1700
|
-
echo "Next steps:"
|
|
1701
|
-
echo " npm run dev"
|
|
1702
|
-
echo " Open: http://localhost:3001"
|
|
1703
|
-
echo ""
|
|
1704
|
-
${features.includes('auth') ? 'echo "Login with:"\necho " Email: admin@example.com"\necho " Password: admin123"\necho ""' : ''}
|
|
1675
|
+
: `#!/bin/bash
|
|
1676
|
+
set -e
|
|
1677
|
+
|
|
1678
|
+
echo ""
|
|
1679
|
+
echo "================================================"
|
|
1680
|
+
echo " ChimerAI Project Setup"
|
|
1681
|
+
echo "================================================"
|
|
1682
|
+
echo ""
|
|
1683
|
+
|
|
1684
|
+
# Check Node.js
|
|
1685
|
+
if ! command -v node &> /dev/null; then
|
|
1686
|
+
echo "[ERROR] Node.js is not installed"
|
|
1687
|
+
exit 1
|
|
1688
|
+
fi
|
|
1689
|
+
|
|
1690
|
+
# Check Docker
|
|
1691
|
+
if ! command -v docker &> /dev/null; then
|
|
1692
|
+
echo "[ERROR] Docker is not installed"
|
|
1693
|
+
exit 1
|
|
1694
|
+
fi
|
|
1695
|
+
|
|
1696
|
+
echo "[1/5] Installing dependencies..."
|
|
1697
|
+
npm install
|
|
1698
|
+
|
|
1699
|
+
echo "[2/5] Starting Docker containers..."
|
|
1700
|
+
docker-compose up -d
|
|
1701
|
+
|
|
1702
|
+
echo "[3/5] Waiting for database..."
|
|
1703
|
+
sleep 5
|
|
1704
|
+
|
|
1705
|
+
echo "[4/5] Setting up database..."
|
|
1706
|
+
npm run db:push
|
|
1707
|
+
|
|
1708
|
+
echo "[5/5] Seeding database..."
|
|
1709
|
+
npm run db:seed || echo "[WARNING] Seeding failed, but you can continue"
|
|
1710
|
+
|
|
1711
|
+
echo ""
|
|
1712
|
+
echo "================================================"
|
|
1713
|
+
echo " Setup completed successfully!"
|
|
1714
|
+
echo "================================================"
|
|
1715
|
+
echo ""
|
|
1716
|
+
echo "Next steps:"
|
|
1717
|
+
echo " npm run dev"
|
|
1718
|
+
echo " Open: http://localhost:3001"
|
|
1719
|
+
echo ""
|
|
1720
|
+
${features.includes('auth') ? 'echo "Login with:"\necho " Email: admin@example.com"\necho " Password: admin123"\necho ""' : ''}
|
|
1705
1721
|
`;
|
|
1706
1722
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'install.bat'), installBat);
|
|
1707
1723
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'install.sh'), installSh);
|
|
@@ -1714,12 +1730,12 @@ ${features.includes('auth') ? 'echo "Login with:"\necho " Email: admin@example.
|
|
|
1714
1730
|
}
|
|
1715
1731
|
}
|
|
1716
1732
|
async function createReadme(targetDir, projectName, features) {
|
|
1717
|
-
let readme = `# ${projectName}
|
|
1718
|
-
|
|
1719
|
-
Built with ChimerAI Kickstart
|
|
1720
|
-
|
|
1721
|
-
## Features
|
|
1722
|
-
|
|
1733
|
+
let readme = `# ${projectName}
|
|
1734
|
+
|
|
1735
|
+
Built with ChimerAI Kickstart
|
|
1736
|
+
|
|
1737
|
+
## Features
|
|
1738
|
+
|
|
1723
1739
|
`;
|
|
1724
1740
|
const featureDescriptions = {
|
|
1725
1741
|
auth: '- 🔐 Authentication with NextAuth',
|
|
@@ -1737,95 +1753,95 @@ Built with ChimerAI Kickstart
|
|
|
1737
1753
|
readme += featureDescriptions[f] + '\n';
|
|
1738
1754
|
}
|
|
1739
1755
|
});
|
|
1740
|
-
readme += `
|
|
1741
|
-
|
|
1742
|
-
## Getting Started
|
|
1743
|
-
|
|
1744
|
-
### Prerequisites
|
|
1745
|
-
|
|
1746
|
-
- Node.js 20+
|
|
1747
|
-
- Docker Desktop
|
|
1748
|
-
- npm
|
|
1749
|
-
|
|
1750
|
-
### Quick Start (Recommended)
|
|
1751
|
-
|
|
1752
|
-
**Windows:**
|
|
1753
|
-
\`\`\`bash
|
|
1754
|
-
install.bat
|
|
1755
|
-
\`\`\`
|
|
1756
|
-
|
|
1757
|
-
**Linux/macOS:**
|
|
1758
|
-
\`\`\`bash
|
|
1759
|
-
./install.sh
|
|
1760
|
-
\`\`\`
|
|
1761
|
-
|
|
1762
|
-
The install script will automatically:
|
|
1763
|
-
- Install dependencies
|
|
1764
|
-
- Start Docker containers
|
|
1765
|
-
- Setup and seed the database
|
|
1766
|
-
- Show you the next steps
|
|
1767
|
-
|
|
1768
|
-
### Manual Installation
|
|
1769
|
-
|
|
1770
|
-
If you prefer to run commands manually:
|
|
1771
|
-
|
|
1772
|
-
\`\`\`bash
|
|
1773
|
-
# Install dependencies
|
|
1774
|
-
npm install
|
|
1775
|
-
|
|
1776
|
-
# Start Docker services
|
|
1777
|
-
docker-compose up -d
|
|
1778
|
-
|
|
1779
|
-
# Setup database
|
|
1780
|
-
npm run db:push
|
|
1781
|
-
npm run db:seed
|
|
1782
|
-
|
|
1783
|
-
# Start development server
|
|
1784
|
-
npm run dev
|
|
1785
|
-
\`\`\`
|
|
1786
|
-
|
|
1787
|
-
Open [http://localhost:3001](http://localhost:3001) in your browser.
|
|
1788
|
-
|
|
1789
|
-
${features.includes('auth') ? `### Default Admin Credentials\n\n- Email: admin@example.com\n- Password: admin123\n\n⚠️ Change these in production!` : ''}
|
|
1790
|
-
|
|
1791
|
-
## Available Scripts
|
|
1792
|
-
|
|
1793
|
-
- \`pnpm dev\` - Start development server
|
|
1794
|
-
- \`pnpm build\` - Build for production
|
|
1795
|
-
- \`pnpm start\` - Start production server
|
|
1796
|
-
- \`pnpm lint\` - Run linter
|
|
1797
|
-
- \`pnpm db:push\` - Push database schema
|
|
1798
|
-
- \`pnpm db:seed\` - Seed database
|
|
1799
|
-
- \`pnpm db:studio\` - Open Prisma Studio
|
|
1800
|
-
|
|
1801
|
-
## Tech Stack
|
|
1802
|
-
|
|
1803
|
-
- **Framework**: Next.js 15
|
|
1804
|
-
- **Language**: TypeScript
|
|
1805
|
-
- **Database**: PostgreSQL + Prisma
|
|
1806
|
-
- **Auth**: NextAuth.js
|
|
1807
|
-
- **Styling**: Tailwind CSS
|
|
1808
|
-
- **UI Components**: Radix UI
|
|
1809
|
-
|
|
1810
|
-
## Project Structure
|
|
1811
|
-
|
|
1812
|
-
\`\`\`
|
|
1813
|
-
├── app/ # Next.js app directory
|
|
1814
|
-
├── components/ # React components
|
|
1815
|
-
├── lib/ # Utility functions
|
|
1816
|
-
├── prisma/ # Database schema
|
|
1817
|
-
└── public/ # Static assets
|
|
1818
|
-
\`\`\`
|
|
1819
|
-
|
|
1820
|
-
## Learn More
|
|
1821
|
-
|
|
1822
|
-
- [ChimerAI Documentation](https://chimerai.dev)
|
|
1823
|
-
- [Next.js Documentation](https://nextjs.org/docs)
|
|
1824
|
-
- [Prisma Documentation](https://www.prisma.io/docs)
|
|
1825
|
-
|
|
1826
|
-
## License
|
|
1827
|
-
|
|
1828
|
-
Commercial License - See LICENSE file
|
|
1756
|
+
readme += `
|
|
1757
|
+
|
|
1758
|
+
## Getting Started
|
|
1759
|
+
|
|
1760
|
+
### Prerequisites
|
|
1761
|
+
|
|
1762
|
+
- Node.js 20+
|
|
1763
|
+
- Docker Desktop
|
|
1764
|
+
- npm
|
|
1765
|
+
|
|
1766
|
+
### Quick Start (Recommended)
|
|
1767
|
+
|
|
1768
|
+
**Windows:**
|
|
1769
|
+
\`\`\`bash
|
|
1770
|
+
install.bat
|
|
1771
|
+
\`\`\`
|
|
1772
|
+
|
|
1773
|
+
**Linux/macOS:**
|
|
1774
|
+
\`\`\`bash
|
|
1775
|
+
./install.sh
|
|
1776
|
+
\`\`\`
|
|
1777
|
+
|
|
1778
|
+
The install script will automatically:
|
|
1779
|
+
- Install dependencies
|
|
1780
|
+
- Start Docker containers
|
|
1781
|
+
- Setup and seed the database
|
|
1782
|
+
- Show you the next steps
|
|
1783
|
+
|
|
1784
|
+
### Manual Installation
|
|
1785
|
+
|
|
1786
|
+
If you prefer to run commands manually:
|
|
1787
|
+
|
|
1788
|
+
\`\`\`bash
|
|
1789
|
+
# Install dependencies
|
|
1790
|
+
npm install
|
|
1791
|
+
|
|
1792
|
+
# Start Docker services
|
|
1793
|
+
docker-compose up -d
|
|
1794
|
+
|
|
1795
|
+
# Setup database
|
|
1796
|
+
npm run db:push
|
|
1797
|
+
npm run db:seed
|
|
1798
|
+
|
|
1799
|
+
# Start development server
|
|
1800
|
+
npm run dev
|
|
1801
|
+
\`\`\`
|
|
1802
|
+
|
|
1803
|
+
Open [http://localhost:3001](http://localhost:3001) in your browser.
|
|
1804
|
+
|
|
1805
|
+
${features.includes('auth') ? `### Default Admin Credentials\n\n- Email: admin@example.com\n- Password: admin123\n\n⚠️ Change these in production!` : ''}
|
|
1806
|
+
|
|
1807
|
+
## Available Scripts
|
|
1808
|
+
|
|
1809
|
+
- \`pnpm dev\` - Start development server
|
|
1810
|
+
- \`pnpm build\` - Build for production
|
|
1811
|
+
- \`pnpm start\` - Start production server
|
|
1812
|
+
- \`pnpm lint\` - Run linter
|
|
1813
|
+
- \`pnpm db:push\` - Push database schema
|
|
1814
|
+
- \`pnpm db:seed\` - Seed database
|
|
1815
|
+
- \`pnpm db:studio\` - Open Prisma Studio
|
|
1816
|
+
|
|
1817
|
+
## Tech Stack
|
|
1818
|
+
|
|
1819
|
+
- **Framework**: Next.js 15
|
|
1820
|
+
- **Language**: TypeScript
|
|
1821
|
+
- **Database**: PostgreSQL + Prisma
|
|
1822
|
+
- **Auth**: NextAuth.js
|
|
1823
|
+
- **Styling**: Tailwind CSS
|
|
1824
|
+
- **UI Components**: Radix UI
|
|
1825
|
+
|
|
1826
|
+
## Project Structure
|
|
1827
|
+
|
|
1828
|
+
\`\`\`
|
|
1829
|
+
├── app/ # Next.js app directory
|
|
1830
|
+
├── components/ # React components
|
|
1831
|
+
├── lib/ # Utility functions
|
|
1832
|
+
├── prisma/ # Database schema
|
|
1833
|
+
└── public/ # Static assets
|
|
1834
|
+
\`\`\`
|
|
1835
|
+
|
|
1836
|
+
## Learn More
|
|
1837
|
+
|
|
1838
|
+
- [ChimerAI Documentation](https://chimerai.dev)
|
|
1839
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
1840
|
+
- [Prisma Documentation](https://www.prisma.io/docs)
|
|
1841
|
+
|
|
1842
|
+
## License
|
|
1843
|
+
|
|
1844
|
+
Commercial License - See LICENSE file
|
|
1829
1845
|
`;
|
|
1830
1846
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'README.md'), readme);
|
|
1831
1847
|
// Create docs directory with inline documentation
|
|
@@ -1833,28 +1849,28 @@ Commercial License - See LICENSE file
|
|
|
1833
1849
|
const docsDir = path_1.default.join(targetDir, 'docs');
|
|
1834
1850
|
await fs_extra_1.default.ensureDir(docsDir);
|
|
1835
1851
|
// Generate inline quickstart guide
|
|
1836
|
-
const quickstart = `# Quick Start
|
|
1837
|
-
|
|
1838
|
-
## Prerequisites
|
|
1839
|
-
- Node.js 20+
|
|
1840
|
-
- Docker Desktop
|
|
1841
|
-
- pnpm (recommended)
|
|
1842
|
-
|
|
1843
|
-
## Setup
|
|
1844
|
-
1. \`pnpm install\`
|
|
1845
|
-
2. \`docker-compose up -d\`
|
|
1846
|
-
3. \`pnpm db:push\`
|
|
1847
|
-
4. \`pnpm db:seed\`
|
|
1848
|
-
5. \`pnpm dev\`
|
|
1849
|
-
|
|
1850
|
-
## Default Login
|
|
1851
|
-
${features.includes('auth') ? '- Email: admin@example.com\n- Password: admin123' : '- No auth configured. Run: `chimerai add auth`'}
|
|
1852
|
-
|
|
1853
|
-
## CLI Commands
|
|
1854
|
-
- \`chimerai add <component>\` — Add features
|
|
1855
|
-
- \`chimerai setup <service>\` — Configure integrations
|
|
1856
|
-
- \`chimerai doctor\` — Health check
|
|
1857
|
-
- \`chimerai update --diff\` — Check for template updates
|
|
1852
|
+
const quickstart = `# Quick Start
|
|
1853
|
+
|
|
1854
|
+
## Prerequisites
|
|
1855
|
+
- Node.js 20+
|
|
1856
|
+
- Docker Desktop
|
|
1857
|
+
- pnpm (recommended)
|
|
1858
|
+
|
|
1859
|
+
## Setup
|
|
1860
|
+
1. \`pnpm install\`
|
|
1861
|
+
2. \`docker-compose up -d\`
|
|
1862
|
+
3. \`pnpm db:push\`
|
|
1863
|
+
4. \`pnpm db:seed\`
|
|
1864
|
+
5. \`pnpm dev\`
|
|
1865
|
+
|
|
1866
|
+
## Default Login
|
|
1867
|
+
${features.includes('auth') ? '- Email: admin@example.com\n- Password: admin123' : '- No auth configured. Run: `chimerai add auth`'}
|
|
1868
|
+
|
|
1869
|
+
## CLI Commands
|
|
1870
|
+
- \`chimerai add <component>\` — Add features
|
|
1871
|
+
- \`chimerai setup <service>\` — Configure integrations
|
|
1872
|
+
- \`chimerai doctor\` — Health check
|
|
1873
|
+
- \`chimerai update --diff\` — Check for template updates
|
|
1858
1874
|
`;
|
|
1859
1875
|
await fs_extra_1.default.writeFile(path_1.default.join(docsDir, 'QUICKSTART.md'), quickstart);
|
|
1860
1876
|
console.log(chalk_1.default.green(' ✓ docs/QUICKSTART.md'));
|