@chimerai/cli 0.2.93 → 0.2.96
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/create.js
CHANGED
|
@@ -61,8 +61,13 @@ function stripAuthFromGeneratedCode(code) {
|
|
|
61
61
|
code = code.replace(/^import \{ getServerSession \} from 'next-auth';\n/m, '');
|
|
62
62
|
code = code.replace(/^import \{ authOptions \} from '@\/lib\/auth';\n/m, '');
|
|
63
63
|
code = code.replace(/^import \{ logAuditAction \} from '@\/lib\/audit';\n/m, '');
|
|
64
|
-
// Remove inline session check blocks
|
|
64
|
+
// Remove inline session check blocks — 4-space indentation (provider/admin routes)
|
|
65
65
|
code = code.replace(/\n {4}const session = await getServerSession\(authOptions\);\n {4}if \(!session\?\.user\) \{\n {6}return NextResponse\.json\(\{ error: 'Unauthorized' \}, \{ status: 401 \}\);\n {4}\}\n/g, '\n');
|
|
66
|
+
// Remove inline session check blocks — 2-space indentation (models/prompts routes)
|
|
67
|
+
code = code.replace(/\n {2}const session = await getServerSession\(authOptions\);\n {2}if \(!session\?\.user\) \{\n {4}return NextResponse\.json\(\{ error: 'Unauthorized' \}, \{ status: 401 \}\);\n {2}\}\n/g, '\n');
|
|
68
|
+
// Also catch orphaned getServerSession calls (import already removed but call remains)
|
|
69
|
+
code = code.replace(/^ {2}const session = await getServerSession\(authOptions\);\n/m, '');
|
|
70
|
+
code = code.replace(/^ {2}if \(!session\?\.user\) \{\n {4}return NextResponse\.json\(\{ error: 'Unauthorized' \}, \{ status: 401 \}\);\n {2}\}\n/m, '');
|
|
66
71
|
// Replace session.user.id with local-dev fallback
|
|
67
72
|
code = code.replace(/session\.user\.id/g, "'local-dev'");
|
|
68
73
|
code = code.replace(/session\?\.user\?\.id/g, "'local-dev'");
|
|
@@ -473,322 +478,322 @@ async function createTsConfig(targetDir) {
|
|
|
473
478
|
}
|
|
474
479
|
async function createPrismaSchema(targetDir, features, sqlite) {
|
|
475
480
|
const dbProvider = sqlite ? 'sqlite' : 'postgresql';
|
|
476
|
-
let schemaContent = `generator client {
|
|
477
|
-
provider = "prisma-client-js"
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
datasource db {
|
|
481
|
-
provider = "${dbProvider}"
|
|
482
|
-
url = env("DATABASE_URL")
|
|
483
|
-
}
|
|
484
|
-
|
|
481
|
+
let schemaContent = `generator client {
|
|
482
|
+
provider = "prisma-client-js"
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
datasource db {
|
|
486
|
+
provider = "${dbProvider}"
|
|
487
|
+
url = env("DATABASE_URL")
|
|
488
|
+
}
|
|
489
|
+
|
|
485
490
|
`;
|
|
486
491
|
// Add User model if auth is included
|
|
487
492
|
if (features.includes('auth')) {
|
|
488
|
-
schemaContent += `model User {
|
|
489
|
-
id String @id @default(cuid())
|
|
490
|
-
name String?
|
|
491
|
-
email String? @unique
|
|
492
|
-
emailVerified DateTime?
|
|
493
|
-
image String?
|
|
494
|
-
password String?
|
|
495
|
-
createdAt DateTime @default(now())
|
|
496
|
-
updatedAt DateTime @updatedAt
|
|
497
|
-
|
|
498
|
-
accounts Account[]
|
|
499
|
-
sessions Session[]
|
|
493
|
+
schemaContent += `model User {
|
|
494
|
+
id String @id @default(cuid())
|
|
495
|
+
name String?
|
|
496
|
+
email String? @unique
|
|
497
|
+
emailVerified DateTime?
|
|
498
|
+
image String?
|
|
499
|
+
password String?
|
|
500
|
+
createdAt DateTime @default(now())
|
|
501
|
+
updatedAt DateTime @updatedAt
|
|
502
|
+
|
|
503
|
+
accounts Account[]
|
|
504
|
+
sessions Session[]
|
|
500
505
|
`;
|
|
501
506
|
if (features.includes('rbac')) {
|
|
502
|
-
schemaContent += ` roles UserRole[]
|
|
507
|
+
schemaContent += ` roles UserRole[]
|
|
503
508
|
`;
|
|
504
509
|
}
|
|
505
510
|
// Provider relations (ALWAYS included)
|
|
506
|
-
schemaContent += ` providers Provider[] @relation("CreatedProviders")
|
|
507
|
-
apiUsage ApiUsage[]
|
|
508
|
-
apiKeys ApiKey[]
|
|
511
|
+
schemaContent += ` providers Provider[] @relation("CreatedProviders")
|
|
512
|
+
apiUsage ApiUsage[]
|
|
513
|
+
apiKeys ApiKey[]
|
|
509
514
|
`;
|
|
510
515
|
if (features.includes('rbac')) {
|
|
511
|
-
schemaContent += ` modelAccess ModelAccess[]
|
|
512
|
-
auditLogs AuditLog[]
|
|
516
|
+
schemaContent += ` modelAccess ModelAccess[]
|
|
517
|
+
auditLogs AuditLog[]
|
|
513
518
|
`;
|
|
514
519
|
}
|
|
515
520
|
if (features.includes('ai-chat')) {
|
|
516
|
-
schemaContent += ` conversations Conversation[]
|
|
521
|
+
schemaContent += ` conversations Conversation[]
|
|
517
522
|
`;
|
|
518
523
|
}
|
|
519
|
-
schemaContent += `}
|
|
520
|
-
|
|
521
|
-
model Account {
|
|
522
|
-
id String @id @default(cuid())
|
|
523
|
-
userId String
|
|
524
|
-
type String
|
|
525
|
-
provider String
|
|
526
|
-
providerAccountId String
|
|
527
|
-
refresh_token String? @db.Text
|
|
528
|
-
access_token String? @db.Text
|
|
529
|
-
expires_at Int?
|
|
530
|
-
token_type String?
|
|
531
|
-
scope String?
|
|
532
|
-
id_token String? @db.Text
|
|
533
|
-
session_state String?
|
|
534
|
-
|
|
535
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
536
|
-
|
|
537
|
-
@@unique([provider, providerAccountId])
|
|
538
|
-
}
|
|
539
|
-
|
|
540
|
-
model Session {
|
|
541
|
-
id String @id @default(cuid())
|
|
542
|
-
sessionToken String @unique
|
|
543
|
-
userId String
|
|
544
|
-
expires DateTime
|
|
545
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
model VerificationToken {
|
|
549
|
-
identifier String
|
|
550
|
-
token String @unique
|
|
551
|
-
expires DateTime
|
|
552
|
-
|
|
553
|
-
@@unique([identifier, token])
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
model ApiKey {
|
|
557
|
-
id String @id @default(cuid())
|
|
558
|
-
name String
|
|
559
|
-
keyHash String @unique
|
|
560
|
-
userId String
|
|
561
|
-
scopes String[] @default([])
|
|
562
|
-
revoked Boolean @default(false)
|
|
563
|
-
lastUsedAt DateTime?
|
|
564
|
-
expiresAt DateTime?
|
|
565
|
-
createdAt DateTime @default(now())
|
|
566
|
-
updatedAt DateTime @updatedAt
|
|
567
|
-
|
|
568
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
569
|
-
|
|
570
|
-
@@index([userId])
|
|
571
|
-
}
|
|
572
|
-
|
|
524
|
+
schemaContent += `}
|
|
525
|
+
|
|
526
|
+
model Account {
|
|
527
|
+
id String @id @default(cuid())
|
|
528
|
+
userId String
|
|
529
|
+
type String
|
|
530
|
+
provider String
|
|
531
|
+
providerAccountId String
|
|
532
|
+
refresh_token String? @db.Text
|
|
533
|
+
access_token String? @db.Text
|
|
534
|
+
expires_at Int?
|
|
535
|
+
token_type String?
|
|
536
|
+
scope String?
|
|
537
|
+
id_token String? @db.Text
|
|
538
|
+
session_state String?
|
|
539
|
+
|
|
540
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
541
|
+
|
|
542
|
+
@@unique([provider, providerAccountId])
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
model Session {
|
|
546
|
+
id String @id @default(cuid())
|
|
547
|
+
sessionToken String @unique
|
|
548
|
+
userId String
|
|
549
|
+
expires DateTime
|
|
550
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
model VerificationToken {
|
|
554
|
+
identifier String
|
|
555
|
+
token String @unique
|
|
556
|
+
expires DateTime
|
|
557
|
+
|
|
558
|
+
@@unique([identifier, token])
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
model ApiKey {
|
|
562
|
+
id String @id @default(cuid())
|
|
563
|
+
name String
|
|
564
|
+
keyHash String @unique
|
|
565
|
+
userId String
|
|
566
|
+
scopes String[] @default([])
|
|
567
|
+
revoked Boolean @default(false)
|
|
568
|
+
lastUsedAt DateTime?
|
|
569
|
+
expiresAt DateTime?
|
|
570
|
+
createdAt DateTime @default(now())
|
|
571
|
+
updatedAt DateTime @updatedAt
|
|
572
|
+
|
|
573
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
574
|
+
|
|
575
|
+
@@index([userId])
|
|
576
|
+
}
|
|
577
|
+
|
|
573
578
|
`;
|
|
574
579
|
}
|
|
575
580
|
// Add RBAC models
|
|
576
581
|
if (features.includes('rbac')) {
|
|
577
|
-
schemaContent += `model Role {
|
|
578
|
-
id String @id @default(cuid())
|
|
579
|
-
name String @unique
|
|
580
|
-
description String?
|
|
581
|
-
permissions String[]
|
|
582
|
-
createdAt DateTime @default(now())
|
|
583
|
-
updatedAt DateTime @updatedAt
|
|
584
|
-
|
|
585
|
-
users UserRole[]
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
model UserRole {
|
|
589
|
-
userId String
|
|
590
|
-
roleId String
|
|
591
|
-
|
|
592
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
593
|
-
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
594
|
-
|
|
595
|
-
@@id([userId, roleId])
|
|
596
|
-
}
|
|
597
|
-
|
|
582
|
+
schemaContent += `model Role {
|
|
583
|
+
id String @id @default(cuid())
|
|
584
|
+
name String @unique
|
|
585
|
+
description String?
|
|
586
|
+
permissions String[]
|
|
587
|
+
createdAt DateTime @default(now())
|
|
588
|
+
updatedAt DateTime @updatedAt
|
|
589
|
+
|
|
590
|
+
users UserRole[]
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
model UserRole {
|
|
594
|
+
userId String
|
|
595
|
+
roleId String
|
|
596
|
+
|
|
597
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
598
|
+
role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
|
|
599
|
+
|
|
600
|
+
@@id([userId, roleId])
|
|
601
|
+
}
|
|
602
|
+
|
|
598
603
|
`;
|
|
599
604
|
}
|
|
600
605
|
// ── PROVIDER MODELS (ALWAYS included — core infrastructure) ──────────
|
|
601
|
-
schemaContent += `// === Provider Management (Core Infrastructure) ===
|
|
602
|
-
|
|
603
|
-
model Provider {
|
|
604
|
-
id String @id @default(cuid())
|
|
605
|
-
name String
|
|
606
|
-
type String // "openai", "anthropic", "ollama", "google", "custom"
|
|
607
|
-
description String?
|
|
608
|
-
baseUrl String?
|
|
609
|
-
apiKey String? @db.Text // AES-256-GCM encrypted, null for keyless providers (e.g. Ollama)
|
|
610
|
-
config Json @default("{}")
|
|
611
|
-
status String @default("active")
|
|
612
|
-
isDefault Boolean @default(false)
|
|
613
|
-
priority Int @default(0)
|
|
614
|
-
tags String[]
|
|
615
|
-
createdAt DateTime @default(now())
|
|
616
|
-
updatedAt DateTime @updatedAt
|
|
617
|
-
createdBy String?
|
|
618
|
-
|
|
619
|
-
models Model[]
|
|
620
|
-
health ProviderHealth?
|
|
621
|
-
apiUsage ApiUsage[]
|
|
622
|
-
${features.includes('ai-chat') ? ' conversations Conversation[]\n' : ''}${features.includes('auth') ? ' creator User? @relation("CreatedProviders", fields: [createdBy], references: [id])\n' : ''}
|
|
623
|
-
@@index([type])
|
|
624
|
-
@@index([status])
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
model Model {
|
|
628
|
-
id String @id @default(cuid())
|
|
629
|
-
providerId String
|
|
630
|
-
modelId String // e.g. "gpt-4", "claude-3-sonnet"
|
|
631
|
-
name String
|
|
632
|
-
description String?
|
|
633
|
-
capabilities String[] // ["chat", "embedding", "image", "vision"]
|
|
634
|
-
contextWindow Int @default(4096)
|
|
635
|
-
maxOutputTokens Int?
|
|
636
|
-
inputCost Float @default(0) // $ per 1M tokens
|
|
637
|
-
outputCost Float @default(0)
|
|
638
|
-
isAvailable Boolean @default(true)
|
|
639
|
-
isDeprecated Boolean @default(false)
|
|
640
|
-
|
|
641
|
-
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
|
642
|
-
|
|
643
|
-
@@unique([providerId, modelId])
|
|
644
|
-
@@index([providerId])
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
model ProviderHealth {
|
|
648
|
-
id String @id @default(cuid())
|
|
649
|
-
providerId String @unique
|
|
650
|
-
status String @default("unknown") // "healthy", "degraded", "unhealthy"
|
|
651
|
-
responseTime Int? // ms
|
|
652
|
-
lastCheck DateTime @default(now())
|
|
653
|
-
errorMessage String?
|
|
654
|
-
modelsAvailable Int @default(0)
|
|
655
|
-
chatAvailable Boolean @default(false)
|
|
656
|
-
embeddingAvailable Boolean @default(false)
|
|
657
|
-
apiKeyValid Boolean @default(false)
|
|
658
|
-
|
|
659
|
-
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
model ApiUsage {
|
|
663
|
-
id String @id @default(cuid())
|
|
664
|
-
userId String
|
|
665
|
-
providerId String?
|
|
666
|
-
model String
|
|
667
|
-
endpoint String
|
|
668
|
-
promptTokens Int @default(0)
|
|
669
|
-
completionTokens Int @default(0)
|
|
670
|
-
totalTokens Int @default(0)
|
|
671
|
-
tokensUsed Int @default(0)
|
|
672
|
-
creditsUsed Int @default(0)
|
|
673
|
-
cost Float @default(0)
|
|
674
|
-
success Boolean @default(true)
|
|
675
|
-
errorMessage String?
|
|
676
|
-
responseTime Int @default(0) // ms
|
|
677
|
-
createdAt DateTime @default(now())
|
|
678
|
-
|
|
679
|
-
${features.includes('auth') ? ' user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n' : ''} provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
|
|
680
|
-
|
|
681
|
-
@@index([userId])
|
|
682
|
-
@@index([providerId])
|
|
683
|
-
@@index([createdAt])
|
|
684
|
-
}
|
|
685
|
-
|
|
606
|
+
schemaContent += `// === Provider Management (Core Infrastructure) ===
|
|
607
|
+
|
|
608
|
+
model Provider {
|
|
609
|
+
id String @id @default(cuid())
|
|
610
|
+
name String
|
|
611
|
+
type String // "openai", "anthropic", "ollama", "google", "custom"
|
|
612
|
+
description String?
|
|
613
|
+
baseUrl String?
|
|
614
|
+
apiKey String? @db.Text // AES-256-GCM encrypted, null for keyless providers (e.g. Ollama)
|
|
615
|
+
config Json @default("{}")
|
|
616
|
+
status String @default("active")
|
|
617
|
+
isDefault Boolean @default(false)
|
|
618
|
+
priority Int @default(0)
|
|
619
|
+
tags String[]
|
|
620
|
+
createdAt DateTime @default(now())
|
|
621
|
+
updatedAt DateTime @updatedAt
|
|
622
|
+
createdBy String?
|
|
623
|
+
|
|
624
|
+
models Model[]
|
|
625
|
+
health ProviderHealth?
|
|
626
|
+
apiUsage ApiUsage[]
|
|
627
|
+
${features.includes('ai-chat') ? ' conversations Conversation[]\n' : ''}${features.includes('auth') ? ' creator User? @relation("CreatedProviders", fields: [createdBy], references: [id])\n' : ''}
|
|
628
|
+
@@index([type])
|
|
629
|
+
@@index([status])
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
model Model {
|
|
633
|
+
id String @id @default(cuid())
|
|
634
|
+
providerId String
|
|
635
|
+
modelId String // e.g. "gpt-4", "claude-3-sonnet"
|
|
636
|
+
name String
|
|
637
|
+
description String?
|
|
638
|
+
capabilities String[] // ["chat", "embedding", "image", "vision"]
|
|
639
|
+
contextWindow Int @default(4096)
|
|
640
|
+
maxOutputTokens Int?
|
|
641
|
+
inputCost Float @default(0) // $ per 1M tokens
|
|
642
|
+
outputCost Float @default(0)
|
|
643
|
+
isAvailable Boolean @default(true)
|
|
644
|
+
isDeprecated Boolean @default(false)
|
|
645
|
+
|
|
646
|
+
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
|
647
|
+
|
|
648
|
+
@@unique([providerId, modelId])
|
|
649
|
+
@@index([providerId])
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
model ProviderHealth {
|
|
653
|
+
id String @id @default(cuid())
|
|
654
|
+
providerId String @unique
|
|
655
|
+
status String @default("unknown") // "healthy", "degraded", "unhealthy"
|
|
656
|
+
responseTime Int? // ms
|
|
657
|
+
lastCheck DateTime @default(now())
|
|
658
|
+
errorMessage String?
|
|
659
|
+
modelsAvailable Int @default(0)
|
|
660
|
+
chatAvailable Boolean @default(false)
|
|
661
|
+
embeddingAvailable Boolean @default(false)
|
|
662
|
+
apiKeyValid Boolean @default(false)
|
|
663
|
+
|
|
664
|
+
provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
model ApiUsage {
|
|
668
|
+
id String @id @default(cuid())
|
|
669
|
+
userId String
|
|
670
|
+
providerId String?
|
|
671
|
+
model String
|
|
672
|
+
endpoint String
|
|
673
|
+
promptTokens Int @default(0)
|
|
674
|
+
completionTokens Int @default(0)
|
|
675
|
+
totalTokens Int @default(0)
|
|
676
|
+
tokensUsed Int @default(0)
|
|
677
|
+
creditsUsed Int @default(0)
|
|
678
|
+
cost Float @default(0)
|
|
679
|
+
success Boolean @default(true)
|
|
680
|
+
errorMessage String?
|
|
681
|
+
responseTime Int @default(0) // ms
|
|
682
|
+
createdAt DateTime @default(now())
|
|
683
|
+
|
|
684
|
+
${features.includes('auth') ? ' user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n' : ''} provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
|
|
685
|
+
|
|
686
|
+
@@index([userId])
|
|
687
|
+
@@index([providerId])
|
|
688
|
+
@@index([createdAt])
|
|
689
|
+
}
|
|
690
|
+
|
|
686
691
|
`;
|
|
687
692
|
// Add ModelAccess when auth + rbac are enabled (for requireModelPermission)
|
|
688
693
|
if (features.includes('auth') && features.includes('rbac')) {
|
|
689
|
-
schemaContent += `model ModelAccess {
|
|
690
|
-
id String @id @default(cuid())
|
|
691
|
-
userId String
|
|
692
|
-
modelId String
|
|
693
|
-
granted Boolean @default(true)
|
|
694
|
-
createdAt DateTime @default(now())
|
|
695
|
-
updatedAt DateTime @updatedAt
|
|
696
|
-
|
|
697
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
698
|
-
|
|
699
|
-
@@unique([userId, modelId])
|
|
700
|
-
@@index([userId])
|
|
701
|
-
@@index([modelId])
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
model AuditLog {
|
|
705
|
-
id String @id @default(cuid())
|
|
706
|
-
action String
|
|
707
|
-
userId String
|
|
708
|
-
targetType String?
|
|
709
|
-
targetId String?
|
|
710
|
-
metadata Json?
|
|
711
|
-
ipAddress String?
|
|
712
|
-
createdAt DateTime @default(now())
|
|
713
|
-
|
|
714
|
-
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
715
|
-
|
|
716
|
-
@@index([userId])
|
|
717
|
-
@@index([action])
|
|
718
|
-
@@index([createdAt])
|
|
719
|
-
}
|
|
720
|
-
|
|
694
|
+
schemaContent += `model ModelAccess {
|
|
695
|
+
id String @id @default(cuid())
|
|
696
|
+
userId String
|
|
697
|
+
modelId String
|
|
698
|
+
granted Boolean @default(true)
|
|
699
|
+
createdAt DateTime @default(now())
|
|
700
|
+
updatedAt DateTime @updatedAt
|
|
701
|
+
|
|
702
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
703
|
+
|
|
704
|
+
@@unique([userId, modelId])
|
|
705
|
+
@@index([userId])
|
|
706
|
+
@@index([modelId])
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
model AuditLog {
|
|
710
|
+
id String @id @default(cuid())
|
|
711
|
+
action String
|
|
712
|
+
userId String
|
|
713
|
+
targetType String?
|
|
714
|
+
targetId String?
|
|
715
|
+
metadata Json?
|
|
716
|
+
ipAddress String?
|
|
717
|
+
createdAt DateTime @default(now())
|
|
718
|
+
|
|
719
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
720
|
+
|
|
721
|
+
@@index([userId])
|
|
722
|
+
@@index([action])
|
|
723
|
+
@@index([createdAt])
|
|
724
|
+
}
|
|
725
|
+
|
|
721
726
|
`;
|
|
722
727
|
}
|
|
723
728
|
// Add Prompt Templates
|
|
724
729
|
if (features.includes('prompt-management')) {
|
|
725
|
-
schemaContent += `model PromptTemplate {
|
|
726
|
-
id String @id @default(cuid())
|
|
727
|
-
name String @unique
|
|
728
|
-
category String
|
|
729
|
-
description String?
|
|
730
|
-
content String @db.Text
|
|
731
|
-
variables String[]
|
|
732
|
-
language String @default("en")
|
|
733
|
-
version Int @default(1)
|
|
734
|
-
isActive Boolean @default(true)
|
|
735
|
-
isDefault Boolean @default(false)
|
|
736
|
-
tags String[]
|
|
737
|
-
metadata Json?
|
|
738
|
-
createdBy String?
|
|
739
|
-
createdAt DateTime @default(now())
|
|
740
|
-
updatedAt DateTime @updatedAt
|
|
741
|
-
|
|
742
|
-
@@index([category])
|
|
743
|
-
@@index([isActive])
|
|
744
|
-
@@index([isDefault])
|
|
745
|
-
}
|
|
746
|
-
|
|
730
|
+
schemaContent += `model PromptTemplate {
|
|
731
|
+
id String @id @default(cuid())
|
|
732
|
+
name String @unique
|
|
733
|
+
category String
|
|
734
|
+
description String?
|
|
735
|
+
content String @db.Text
|
|
736
|
+
variables String[]
|
|
737
|
+
language String @default("en")
|
|
738
|
+
version Int @default(1)
|
|
739
|
+
isActive Boolean @default(true)
|
|
740
|
+
isDefault Boolean @default(false)
|
|
741
|
+
tags String[]
|
|
742
|
+
metadata Json?
|
|
743
|
+
createdBy String?
|
|
744
|
+
createdAt DateTime @default(now())
|
|
745
|
+
updatedAt DateTime @updatedAt
|
|
746
|
+
|
|
747
|
+
@@index([category])
|
|
748
|
+
@@index([isActive])
|
|
749
|
+
@@index([isDefault])
|
|
750
|
+
}
|
|
751
|
+
|
|
747
752
|
`;
|
|
748
753
|
}
|
|
749
754
|
// Add Conversation & Message models for chat feature
|
|
750
755
|
if (features.includes('ai-chat')) {
|
|
751
|
-
schemaContent += `model Conversation {
|
|
752
|
-
id String @id @default(cuid())
|
|
753
|
-
userId String
|
|
754
|
-
title String @default("New Chat")
|
|
755
|
-
model String?
|
|
756
|
-
providerId String?
|
|
757
|
-
metadata Json?
|
|
758
|
-
archived Boolean @default(false)
|
|
759
|
-
createdAt DateTime @default(now())
|
|
760
|
-
updatedAt DateTime @updatedAt
|
|
761
|
-
|
|
762
|
-
${features.includes('auth') ? ' user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n' : ''} provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
|
|
763
|
-
messages Message[]
|
|
764
|
-
|
|
765
|
-
@@index([userId])
|
|
766
|
-
@@index([archived])
|
|
767
|
-
@@index([providerId])
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
model Message {
|
|
771
|
-
id String @id @default(cuid())
|
|
772
|
-
conversationId String
|
|
773
|
-
role String
|
|
774
|
-
content String @db.Text
|
|
775
|
-
model String?
|
|
776
|
-
tokens Int?
|
|
777
|
-
createdAt DateTime @default(now())
|
|
778
|
-
|
|
779
|
-
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
780
|
-
|
|
781
|
-
@@index([conversationId])
|
|
782
|
-
}
|
|
783
|
-
|
|
756
|
+
schemaContent += `model Conversation {
|
|
757
|
+
id String @id @default(cuid())
|
|
758
|
+
userId String
|
|
759
|
+
title String @default("New Chat")
|
|
760
|
+
model String?
|
|
761
|
+
providerId String?
|
|
762
|
+
metadata Json?
|
|
763
|
+
archived Boolean @default(false)
|
|
764
|
+
createdAt DateTime @default(now())
|
|
765
|
+
updatedAt DateTime @updatedAt
|
|
766
|
+
|
|
767
|
+
${features.includes('auth') ? ' user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n' : ''} provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
|
|
768
|
+
messages Message[]
|
|
769
|
+
|
|
770
|
+
@@index([userId])
|
|
771
|
+
@@index([archived])
|
|
772
|
+
@@index([providerId])
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
model Message {
|
|
776
|
+
id String @id @default(cuid())
|
|
777
|
+
conversationId String
|
|
778
|
+
role String
|
|
779
|
+
content String @db.Text
|
|
780
|
+
model String?
|
|
781
|
+
tokens Int?
|
|
782
|
+
createdAt DateTime @default(now())
|
|
783
|
+
|
|
784
|
+
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
|
785
|
+
|
|
786
|
+
@@index([conversationId])
|
|
787
|
+
}
|
|
788
|
+
|
|
784
789
|
`;
|
|
785
790
|
}
|
|
786
791
|
// SystemSetting is always included (used by admin settings + app-settings API)
|
|
787
|
-
schemaContent += `model SystemSetting {
|
|
788
|
-
key String @id
|
|
789
|
-
value String @db.Text
|
|
790
|
-
updatedAt DateTime @updatedAt
|
|
791
|
-
}
|
|
792
|
+
schemaContent += `model SystemSetting {
|
|
793
|
+
key String @id
|
|
794
|
+
value String @db.Text
|
|
795
|
+
updatedAt DateTime @updatedAt
|
|
796
|
+
}
|
|
792
797
|
`;
|
|
793
798
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'prisma/schema.prisma'), schemaContent);
|
|
794
799
|
// Apply SQLite compatibility transform if needed
|
|
@@ -844,55 +849,55 @@ async function createEnvFiles(targetDir, features, sqlite) {
|
|
|
844
849
|
// Generate a random NextAuth secret (32 bytes hex = 64 chars)
|
|
845
850
|
const nextAuthSecret = Array.from({ length: 64 }, () => '0123456789abcdef'.charAt(Math.floor(Math.random() * 16))).join('');
|
|
846
851
|
const dbUrl = sqlite ? 'file:./dev.db' : 'postgresql://postgres:postgres@localhost:5432/myapp_db';
|
|
847
|
-
let envContent = `# Database${sqlite ? ' (SQLite — no Docker needed)' : ''}
|
|
848
|
-
DATABASE_URL=${dbUrl}
|
|
849
|
-
|
|
852
|
+
let envContent = `# Database${sqlite ? ' (SQLite — no Docker needed)' : ''}
|
|
853
|
+
DATABASE_URL=${dbUrl}
|
|
854
|
+
|
|
850
855
|
`;
|
|
851
856
|
if (features.includes('auth')) {
|
|
852
|
-
envContent += `# Authentication
|
|
853
|
-
NEXTAUTH_SECRET=${nextAuthSecret}
|
|
854
|
-
NEXTAUTH_URL=http://localhost:3000
|
|
855
|
-
|
|
857
|
+
envContent += `# Authentication
|
|
858
|
+
NEXTAUTH_SECRET=${nextAuthSecret}
|
|
859
|
+
NEXTAUTH_URL=http://localhost:3000
|
|
860
|
+
|
|
856
861
|
`;
|
|
857
862
|
}
|
|
858
863
|
// ── Provider Infrastructure (ALWAYS included — core) ──────────
|
|
859
|
-
envContent += `# Provider Encryption (AES-256-GCM for API key storage)
|
|
860
|
-
PROVIDER_ENCRYPTION_KEY=${encryptionKey}
|
|
861
|
-
|
|
862
|
-
# Internal API Token (AI-Service ↔ Frontend communication)
|
|
863
|
-
INTERNAL_API_TOKEN=${internalToken}
|
|
864
|
-
|
|
865
|
-
# AI Service URL
|
|
866
|
-
AI_SERVICE_URL=http://localhost:8002
|
|
867
|
-
|
|
868
|
-
# AI Providers (Optional — can also be added via Provider Management UI)
|
|
869
|
-
OPENAI_API_KEY=
|
|
870
|
-
ANTHROPIC_API_KEY=
|
|
871
|
-
AZURE_OPENAI_API_KEY=
|
|
872
|
-
AZURE_OPENAI_ENDPOINT=
|
|
873
|
-
|
|
864
|
+
envContent += `# Provider Encryption (AES-256-GCM for API key storage)
|
|
865
|
+
PROVIDER_ENCRYPTION_KEY=${encryptionKey}
|
|
866
|
+
|
|
867
|
+
# Internal API Token (AI-Service ↔ Frontend communication)
|
|
868
|
+
INTERNAL_API_TOKEN=${internalToken}
|
|
869
|
+
|
|
870
|
+
# AI Service URL
|
|
871
|
+
AI_SERVICE_URL=http://localhost:8002
|
|
872
|
+
|
|
873
|
+
# AI Providers (Optional — can also be added via Provider Management UI)
|
|
874
|
+
OPENAI_API_KEY=
|
|
875
|
+
ANTHROPIC_API_KEY=
|
|
876
|
+
AZURE_OPENAI_API_KEY=
|
|
877
|
+
AZURE_OPENAI_ENDPOINT=
|
|
878
|
+
|
|
874
879
|
`;
|
|
875
880
|
if (features.includes('billing')) {
|
|
876
|
-
envContent += `# Stripe
|
|
877
|
-
STRIPE_PUBLISHABLE_KEY=
|
|
878
|
-
STRIPE_SECRET_KEY=
|
|
879
|
-
STRIPE_WEBHOOK_SECRET=
|
|
880
|
-
|
|
881
|
+
envContent += `# Stripe
|
|
882
|
+
STRIPE_PUBLISHABLE_KEY=
|
|
883
|
+
STRIPE_SECRET_KEY=
|
|
884
|
+
STRIPE_WEBHOOK_SECRET=
|
|
885
|
+
|
|
881
886
|
`;
|
|
882
887
|
}
|
|
883
888
|
// Widget / External Integration
|
|
884
|
-
envContent += `# CORS / Widget Configuration (External Chat Embedding)
|
|
885
|
-
# Comma-separated list of allowed origins for cross-origin requests (e.g. Blazor, Vue, React embeds)
|
|
886
|
-
# Use * to allow all origins (development only!)
|
|
887
|
-
# Example: CORS_ALLOWED_ORIGINS=https://my-blog.com,https://my-shop.com
|
|
888
|
-
CORS_ALLOWED_ORIGINS=
|
|
889
|
-
# Legacy alias (still supported): WIDGET_ALLOWED_ORIGINS
|
|
890
|
-
|
|
891
|
-
# Upstash Redis (optional — for rate-limiting in serverless/multi-instance)
|
|
892
|
-
# Without this, rate-limiting uses in-memory fallback (fine for single-instance)
|
|
893
|
-
# UPSTASH_REDIS_REST_URL=
|
|
894
|
-
# UPSTASH_REDIS_REST_TOKEN=
|
|
895
|
-
|
|
889
|
+
envContent += `# CORS / Widget Configuration (External Chat Embedding)
|
|
890
|
+
# Comma-separated list of allowed origins for cross-origin requests (e.g. Blazor, Vue, React embeds)
|
|
891
|
+
# Use * to allow all origins (development only!)
|
|
892
|
+
# Example: CORS_ALLOWED_ORIGINS=https://my-blog.com,https://my-shop.com
|
|
893
|
+
CORS_ALLOWED_ORIGINS=
|
|
894
|
+
# Legacy alias (still supported): WIDGET_ALLOWED_ORIGINS
|
|
895
|
+
|
|
896
|
+
# Upstash Redis (optional — for rate-limiting in serverless/multi-instance)
|
|
897
|
+
# Without this, rate-limiting uses in-memory fallback (fine for single-instance)
|
|
898
|
+
# UPSTASH_REDIS_REST_URL=
|
|
899
|
+
# UPSTASH_REDIS_REST_TOKEN=
|
|
900
|
+
|
|
896
901
|
`;
|
|
897
902
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, '.env'), envContent);
|
|
898
903
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, '.env.example'), envContent
|
|
@@ -983,6 +988,13 @@ async function copyFeatureFiles(targetDir, features) {
|
|
|
983
988
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib/auth'));
|
|
984
989
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/auth/resolve-auth.ts'), resolveAuthStub);
|
|
985
990
|
}
|
|
991
|
+
// Shared no-auth helper — used for all route generators below
|
|
992
|
+
const _withAuth = features.includes('auth');
|
|
993
|
+
const _strip = (code) => (_withAuth ? code : stripAuthFromGeneratedCode(code));
|
|
994
|
+
// useAppName hook — always generated (used by chat page, dashboard layout, etc.)
|
|
995
|
+
const useAppNameHook = templates.generateUseAppNameHook();
|
|
996
|
+
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib'));
|
|
997
|
+
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/use-app-name.ts'), useAppNameHook);
|
|
986
998
|
// ── Widget Infrastructure (embeddable chat for external apps) ──────
|
|
987
999
|
// Rate limiter (supports both session and API-key tiers)
|
|
988
1000
|
const rateLimiter = templates.generateRateLimiter();
|
|
@@ -995,10 +1007,10 @@ async function copyFeatureFiles(targetDir, features) {
|
|
|
995
1007
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'public/widget/loader.js'), widgetLoader);
|
|
996
1008
|
// API-Key management routes
|
|
997
1009
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/v1/api-keys'));
|
|
998
|
-
const apiKeysRoute = templates.generateApiKeysRoute();
|
|
1010
|
+
const apiKeysRoute = _strip(templates.generateApiKeysRoute());
|
|
999
1011
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/v1/api-keys/route.ts'), apiKeysRoute);
|
|
1000
1012
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/v1/api-keys/[id]'));
|
|
1001
|
-
const apiKeyIdRoute = templates.generateApiKeyIdRoute();
|
|
1013
|
+
const apiKeyIdRoute = _strip(templates.generateApiKeyIdRoute());
|
|
1002
1014
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/v1/api-keys/[id]/route.ts'), apiKeyIdRoute);
|
|
1003
1015
|
// API-Key management page (settings UI)
|
|
1004
1016
|
if (features.includes('auth')) {
|
|
@@ -1009,9 +1021,6 @@ async function copyFeatureFiles(targetDir, features) {
|
|
|
1009
1021
|
// Notify provider change utility
|
|
1010
1022
|
const notifyLib = templates.generateNotifyProviderChangeLib();
|
|
1011
1023
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/notify-provider-change.ts'), notifyLib);
|
|
1012
|
-
// When auth is not enabled, strip next-auth imports/checks from provider routes
|
|
1013
|
-
const _withAuth = features.includes('auth');
|
|
1014
|
-
const _strip = (code) => (_withAuth ? code : stripAuthFromGeneratedCode(code));
|
|
1015
1024
|
// Provider CRUD route — /api/providers
|
|
1016
1025
|
const providerCrudRoute = _strip(templates.generateProviderCrudRoute());
|
|
1017
1026
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/providers'));
|
|
@@ -1102,136 +1111,132 @@ async function copyFeatureFiles(targetDir, features) {
|
|
|
1102
1111
|
const appSettingsRoute = templates.generateAppSettingsRoute();
|
|
1103
1112
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/app-settings'));
|
|
1104
1113
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/app-settings/route.ts'), appSettingsRoute);
|
|
1105
|
-
// useAppName client hook
|
|
1106
|
-
const useAppNameHook = templates.generateUseAppNameHook();
|
|
1107
|
-
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib'));
|
|
1108
|
-
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/use-app-name.ts'), useAppNameHook);
|
|
1109
1114
|
// ── RBAC Permission utilities (required by admin API routes) ───
|
|
1110
|
-
const permissionsLib = `/**
|
|
1111
|
-
* Permission utility functions
|
|
1112
|
-
* Handles permission checks and role-based access control
|
|
1113
|
-
*/
|
|
1114
|
-
|
|
1115
|
-
export const AVAILABLE_PERMISSIONS = [
|
|
1116
|
-
'users:read',
|
|
1117
|
-
'users:write',
|
|
1118
|
-
'users:delete',
|
|
1119
|
-
'roles:read',
|
|
1120
|
-
'roles:write',
|
|
1121
|
-
'roles:delete',
|
|
1122
|
-
'settings:read',
|
|
1123
|
-
'settings:write',
|
|
1124
|
-
'admin:*',
|
|
1125
|
-
] as const;
|
|
1126
|
-
|
|
1127
|
-
export type Permission = typeof AVAILABLE_PERMISSIONS[number];
|
|
1128
|
-
|
|
1129
|
-
interface User {
|
|
1130
|
-
id: string;
|
|
1131
|
-
email: string;
|
|
1132
|
-
roles?: Array<{ permissions: string[] }>;
|
|
1133
|
-
}
|
|
1134
|
-
|
|
1135
|
-
export function hasPermission(user: User | null, permission: string): boolean {
|
|
1136
|
-
if (!user || !user.roles) return false;
|
|
1137
|
-
const allPermissions = user.roles.flatMap(role => role.permissions || []);
|
|
1138
|
-
|
|
1139
|
-
// Tier 1: Super-Wildcard — '*' matcht ALLES
|
|
1140
|
-
if (allPermissions.includes('*')) return true;
|
|
1141
|
-
|
|
1142
|
-
// Tier 2: Kategorie-Wildcard — 'admin:*' matcht 'admin:users:read', 'admin:roles:write', etc.
|
|
1143
|
-
for (const perm of allPermissions) {
|
|
1144
|
-
if (perm.endsWith(':*')) {
|
|
1145
|
-
const prefix = perm.slice(0, -1); // 'admin:*' → 'admin:'
|
|
1146
|
-
if (permission.startsWith(prefix)) return true;
|
|
1147
|
-
}
|
|
1148
|
-
}
|
|
1149
|
-
|
|
1150
|
-
// Tier 3: Exakter Match
|
|
1151
|
-
return allPermissions.includes(permission);
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
export function hasAnyPermission(user: User | null, permissions: string[]): boolean {
|
|
1155
|
-
if (!user) return false;
|
|
1156
|
-
return permissions.some(permission => hasPermission(user, permission));
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
export function hasAllPermissions(user: User | null, permissions: string[]): boolean {
|
|
1160
|
-
if (!user) return false;
|
|
1161
|
-
return permissions.every(permission => hasPermission(user, permission));
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
export function getUserPermissions(user: User | null): string[] {
|
|
1165
|
-
if (!user || !user.roles) return [];
|
|
1166
|
-
return [...new Set(user.roles.flatMap(role => role.permissions || []))];
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
export function isValidPermission(permission: string): boolean {
|
|
1170
|
-
return AVAILABLE_PERMISSIONS.includes(permission as Permission);
|
|
1171
|
-
}
|
|
1115
|
+
const permissionsLib = `/**
|
|
1116
|
+
* Permission utility functions
|
|
1117
|
+
* Handles permission checks and role-based access control
|
|
1118
|
+
*/
|
|
1119
|
+
|
|
1120
|
+
export const AVAILABLE_PERMISSIONS = [
|
|
1121
|
+
'users:read',
|
|
1122
|
+
'users:write',
|
|
1123
|
+
'users:delete',
|
|
1124
|
+
'roles:read',
|
|
1125
|
+
'roles:write',
|
|
1126
|
+
'roles:delete',
|
|
1127
|
+
'settings:read',
|
|
1128
|
+
'settings:write',
|
|
1129
|
+
'admin:*',
|
|
1130
|
+
] as const;
|
|
1131
|
+
|
|
1132
|
+
export type Permission = typeof AVAILABLE_PERMISSIONS[number];
|
|
1133
|
+
|
|
1134
|
+
interface User {
|
|
1135
|
+
id: string;
|
|
1136
|
+
email: string;
|
|
1137
|
+
roles?: Array<{ permissions: string[] }>;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
export function hasPermission(user: User | null, permission: string): boolean {
|
|
1141
|
+
if (!user || !user.roles) return false;
|
|
1142
|
+
const allPermissions = user.roles.flatMap(role => role.permissions || []);
|
|
1143
|
+
|
|
1144
|
+
// Tier 1: Super-Wildcard — '*' matcht ALLES
|
|
1145
|
+
if (allPermissions.includes('*')) return true;
|
|
1146
|
+
|
|
1147
|
+
// Tier 2: Kategorie-Wildcard — 'admin:*' matcht 'admin:users:read', 'admin:roles:write', etc.
|
|
1148
|
+
for (const perm of allPermissions) {
|
|
1149
|
+
if (perm.endsWith(':*')) {
|
|
1150
|
+
const prefix = perm.slice(0, -1); // 'admin:*' → 'admin:'
|
|
1151
|
+
if (permission.startsWith(prefix)) return true;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
// Tier 3: Exakter Match
|
|
1156
|
+
return allPermissions.includes(permission);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
export function hasAnyPermission(user: User | null, permissions: string[]): boolean {
|
|
1160
|
+
if (!user) return false;
|
|
1161
|
+
return permissions.some(permission => hasPermission(user, permission));
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
export function hasAllPermissions(user: User | null, permissions: string[]): boolean {
|
|
1165
|
+
if (!user) return false;
|
|
1166
|
+
return permissions.every(permission => hasPermission(user, permission));
|
|
1167
|
+
}
|
|
1168
|
+
|
|
1169
|
+
export function getUserPermissions(user: User | null): string[] {
|
|
1170
|
+
if (!user || !user.roles) return [];
|
|
1171
|
+
return [...new Set(user.roles.flatMap(role => role.permissions || []))];
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
export function isValidPermission(permission: string): boolean {
|
|
1175
|
+
return AVAILABLE_PERMISSIONS.includes(permission as Permission);
|
|
1176
|
+
}
|
|
1172
1177
|
`;
|
|
1173
1178
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/permissions.ts'), permissionsLib);
|
|
1174
|
-
const requirePermissionLib = `import { getServerSession } from 'next-auth';
|
|
1175
|
-
import { NextResponse } from 'next/server';
|
|
1176
|
-
import { authOptions } from '@/lib/auth';
|
|
1177
|
-
import { hasPermission } from '@/lib/permissions';
|
|
1178
|
-
|
|
1179
|
-
/**
|
|
1180
|
-
* Server-side permission check for API routes
|
|
1181
|
-
* Returns NextResponse with 401/403 if check fails, null if OK
|
|
1182
|
-
*/
|
|
1183
|
-
export async function requirePermission(permission: string) {
|
|
1184
|
-
const session = await getServerSession(authOptions);
|
|
1185
|
-
|
|
1186
|
-
if (!session || !session.user) {
|
|
1187
|
-
return NextResponse.json(
|
|
1188
|
-
{ error: 'Unauthorized - Please sign in' },
|
|
1189
|
-
{ status: 401 }
|
|
1190
|
-
);
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
const user = await getServerSessionWithPermissions();
|
|
1194
|
-
|
|
1195
|
-
if (!user || !hasPermission(user as any, permission)) {
|
|
1196
|
-
return NextResponse.json(
|
|
1197
|
-
{ error: \`Forbidden - Required permission: \${permission}\` },
|
|
1198
|
-
{ status: 403 }
|
|
1199
|
-
);
|
|
1200
|
-
}
|
|
1201
|
-
|
|
1202
|
-
return null;
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
async function getServerSessionWithPermissions() {
|
|
1206
|
-
const session = await getServerSession(authOptions);
|
|
1207
|
-
if (!session?.user?.email) return null;
|
|
1208
|
-
|
|
1209
|
-
const { prisma } = await import('@/lib/prisma');
|
|
1210
|
-
const user = await prisma.user.findUnique({
|
|
1211
|
-
where: { email: session.user.email },
|
|
1212
|
-
include: {
|
|
1213
|
-
roles: {
|
|
1214
|
-
select: {
|
|
1215
|
-
role: {
|
|
1216
|
-
select: {
|
|
1217
|
-
id: true,
|
|
1218
|
-
name: true,
|
|
1219
|
-
permissions: true
|
|
1220
|
-
}
|
|
1221
|
-
}
|
|
1222
|
-
}
|
|
1223
|
-
}
|
|
1224
|
-
}
|
|
1225
|
-
});
|
|
1226
|
-
|
|
1227
|
-
if (!user) return null;
|
|
1228
|
-
|
|
1229
|
-
// Flatten UserRole[] → { permissions: string[] }[]
|
|
1230
|
-
return {
|
|
1231
|
-
...user,
|
|
1232
|
-
roles: (user.roles as any[]).map((ur: any) => ur.role),
|
|
1233
|
-
};
|
|
1234
|
-
}
|
|
1179
|
+
const requirePermissionLib = `import { getServerSession } from 'next-auth';
|
|
1180
|
+
import { NextResponse } from 'next/server';
|
|
1181
|
+
import { authOptions } from '@/lib/auth';
|
|
1182
|
+
import { hasPermission } from '@/lib/permissions';
|
|
1183
|
+
|
|
1184
|
+
/**
|
|
1185
|
+
* Server-side permission check for API routes
|
|
1186
|
+
* Returns NextResponse with 401/403 if check fails, null if OK
|
|
1187
|
+
*/
|
|
1188
|
+
export async function requirePermission(permission: string) {
|
|
1189
|
+
const session = await getServerSession(authOptions);
|
|
1190
|
+
|
|
1191
|
+
if (!session || !session.user) {
|
|
1192
|
+
return NextResponse.json(
|
|
1193
|
+
{ error: 'Unauthorized - Please sign in' },
|
|
1194
|
+
{ status: 401 }
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
const user = await getServerSessionWithPermissions();
|
|
1199
|
+
|
|
1200
|
+
if (!user || !hasPermission(user as any, permission)) {
|
|
1201
|
+
return NextResponse.json(
|
|
1202
|
+
{ error: \`Forbidden - Required permission: \${permission}\` },
|
|
1203
|
+
{ status: 403 }
|
|
1204
|
+
);
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
return null;
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
async function getServerSessionWithPermissions() {
|
|
1211
|
+
const session = await getServerSession(authOptions);
|
|
1212
|
+
if (!session?.user?.email) return null;
|
|
1213
|
+
|
|
1214
|
+
const { prisma } = await import('@/lib/prisma');
|
|
1215
|
+
const user = await prisma.user.findUnique({
|
|
1216
|
+
where: { email: session.user.email },
|
|
1217
|
+
include: {
|
|
1218
|
+
roles: {
|
|
1219
|
+
select: {
|
|
1220
|
+
role: {
|
|
1221
|
+
select: {
|
|
1222
|
+
id: true,
|
|
1223
|
+
name: true,
|
|
1224
|
+
permissions: true
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
});
|
|
1231
|
+
|
|
1232
|
+
if (!user) return null;
|
|
1233
|
+
|
|
1234
|
+
// Flatten UserRole[] → { permissions: string[] }[]
|
|
1235
|
+
return {
|
|
1236
|
+
...user,
|
|
1237
|
+
roles: (user.roles as any[]).map((ur: any) => ur.role),
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1235
1240
|
`;
|
|
1236
1241
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'lib/auth'));
|
|
1237
1242
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'lib/auth/require-permission.ts'), requirePermissionLib);
|
|
@@ -1241,10 +1246,10 @@ async function getServerSessionWithPermissions() {
|
|
|
1241
1246
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/dashboard/prompts'));
|
|
1242
1247
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/dashboard/prompts/page.tsx'), promptsPage);
|
|
1243
1248
|
// Prompts API routes
|
|
1244
|
-
const promptsRoute = templates.generatePromptsRoute();
|
|
1249
|
+
const promptsRoute = _strip(templates.generatePromptsRoute());
|
|
1245
1250
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/prompts'));
|
|
1246
1251
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/prompts/route.ts'), promptsRoute);
|
|
1247
|
-
const promptsIdRoute = templates.generatePromptsIdRoute();
|
|
1252
|
+
const promptsIdRoute = _strip(templates.generatePromptsIdRoute());
|
|
1248
1253
|
await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, 'app/api/prompts/[id]'));
|
|
1249
1254
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'app/api/prompts/[id]/route.ts'), promptsIdRoute);
|
|
1250
1255
|
}
|
|
@@ -1327,215 +1332,215 @@ async function getServerSessionWithPermissions() {
|
|
|
1327
1332
|
async function createSeedScript(targetDir, features, sqlite) {
|
|
1328
1333
|
// Without auth there are no users/providers to seed — write a minimal placeholder
|
|
1329
1334
|
if (!features.includes('auth')) {
|
|
1330
|
-
const minimalSeed = `import { PrismaClient } from '@prisma/client';
|
|
1331
|
-
const prisma = new PrismaClient();
|
|
1332
|
-
async function main() {
|
|
1333
|
-
console.log('🌱 No auth feature selected — nothing to seed yet.');
|
|
1334
|
-
console.log(' Run: chimerai add auth to add authentication.');
|
|
1335
|
-
}
|
|
1336
|
-
main().catch(console.error).finally(() => prisma.$disconnect());
|
|
1335
|
+
const minimalSeed = `import { PrismaClient } from '@prisma/client';
|
|
1336
|
+
const prisma = new PrismaClient();
|
|
1337
|
+
async function main() {
|
|
1338
|
+
console.log('🌱 No auth feature selected — nothing to seed yet.');
|
|
1339
|
+
console.log(' Run: chimerai add auth to add authentication.');
|
|
1340
|
+
}
|
|
1341
|
+
main().catch(console.error).finally(() => prisma.$disconnect());
|
|
1337
1342
|
`;
|
|
1338
1343
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'prisma/seed.ts'), minimalSeed);
|
|
1339
1344
|
return;
|
|
1340
1345
|
}
|
|
1341
1346
|
// Generate seed script with feature-specific configuration
|
|
1342
|
-
const seedScript = `import { PrismaClient } from '@prisma/client';
|
|
1343
|
-
import * as bcrypt from 'bcryptjs';
|
|
1344
|
-
import crypto from 'crypto';
|
|
1345
|
-
|
|
1346
|
-
const prisma = new PrismaClient();
|
|
1347
|
-
|
|
1348
|
-
// ── Encryption helpers (same as lib/encryption.ts) ──────────────
|
|
1349
|
-
function getKey(): Buffer {
|
|
1350
|
-
const key = process.env.PROVIDER_ENCRYPTION_KEY;
|
|
1351
|
-
if (!key) throw new Error('PROVIDER_ENCRYPTION_KEY required for seeding');
|
|
1352
|
-
if (key.length === 64 && /^[0-9a-fA-F]+$/.test(key)) {
|
|
1353
|
-
return Buffer.from(key, 'hex');
|
|
1354
|
-
}
|
|
1355
|
-
return crypto.createHash('sha256').update(key).digest();
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
function encrypt(text: string): string {
|
|
1359
|
-
if (!text) return '';
|
|
1360
|
-
const key = getKey();
|
|
1361
|
-
const iv = crypto.randomBytes(16);
|
|
1362
|
-
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv, { authTagLength: 16 });
|
|
1363
|
-
let encrypted = cipher.update(text, 'utf8', 'base64');
|
|
1364
|
-
encrypted += cipher.final('base64');
|
|
1365
|
-
const authTag = cipher.getAuthTag();
|
|
1366
|
-
return iv.toString('base64') + ':' + authTag.toString('base64') + ':' + encrypted;
|
|
1367
|
-
}
|
|
1368
|
-
// ────────────────────────────────────────────────────────────────
|
|
1369
|
-
|
|
1370
|
-
async function main() {
|
|
1371
|
-
console.log('🌱 Seeding database...');
|
|
1372
|
-
|
|
1373
|
-
// Create default admin user (next-auth is always a core dependency)
|
|
1374
|
-
const hashedPassword = await bcrypt.hash('admin123', 10);
|
|
1375
|
-
const admin = await prisma.user.upsert({
|
|
1376
|
-
where: { email: 'admin@example.com' },
|
|
1377
|
-
update: {},
|
|
1378
|
-
create: {
|
|
1379
|
-
email: 'admin@example.com',
|
|
1380
|
-
name: 'Admin User',
|
|
1381
|
-
password: hashedPassword,
|
|
1382
|
-
},
|
|
1383
|
-
});
|
|
1384
|
-
|
|
1385
|
-
console.log('✅ Admin user created: admin@example.com / admin123');
|
|
1347
|
+
const seedScript = `import { PrismaClient } from '@prisma/client';
|
|
1348
|
+
import * as bcrypt from 'bcryptjs';
|
|
1349
|
+
import crypto from 'crypto';
|
|
1350
|
+
|
|
1351
|
+
const prisma = new PrismaClient();
|
|
1352
|
+
|
|
1353
|
+
// ── Encryption helpers (same as lib/encryption.ts) ──────────────
|
|
1354
|
+
function getKey(): Buffer {
|
|
1355
|
+
const key = process.env.PROVIDER_ENCRYPTION_KEY;
|
|
1356
|
+
if (!key) throw new Error('PROVIDER_ENCRYPTION_KEY required for seeding');
|
|
1357
|
+
if (key.length === 64 && /^[0-9a-fA-F]+$/.test(key)) {
|
|
1358
|
+
return Buffer.from(key, 'hex');
|
|
1359
|
+
}
|
|
1360
|
+
return crypto.createHash('sha256').update(key).digest();
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
function encrypt(text: string): string {
|
|
1364
|
+
if (!text) return '';
|
|
1365
|
+
const key = getKey();
|
|
1366
|
+
const iv = crypto.randomBytes(16);
|
|
1367
|
+
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv, { authTagLength: 16 });
|
|
1368
|
+
let encrypted = cipher.update(text, 'utf8', 'base64');
|
|
1369
|
+
encrypted += cipher.final('base64');
|
|
1370
|
+
const authTag = cipher.getAuthTag();
|
|
1371
|
+
return iv.toString('base64') + ':' + authTag.toString('base64') + ':' + encrypted;
|
|
1372
|
+
}
|
|
1373
|
+
// ────────────────────────────────────────────────────────────────
|
|
1374
|
+
|
|
1375
|
+
async function main() {
|
|
1376
|
+
console.log('🌱 Seeding database...');
|
|
1377
|
+
|
|
1378
|
+
// Create default admin user (next-auth is always a core dependency)
|
|
1379
|
+
const hashedPassword = await bcrypt.hash('admin123', 10);
|
|
1380
|
+
const admin = await prisma.user.upsert({
|
|
1381
|
+
where: { email: 'admin@example.com' },
|
|
1382
|
+
update: {},
|
|
1383
|
+
create: {
|
|
1384
|
+
email: 'admin@example.com',
|
|
1385
|
+
name: 'Admin User',
|
|
1386
|
+
password: hashedPassword,
|
|
1387
|
+
},
|
|
1388
|
+
});
|
|
1389
|
+
|
|
1390
|
+
console.log('✅ Admin user created: admin@example.com / admin123');
|
|
1386
1391
|
${features.includes('rbac')
|
|
1387
|
-
? `
|
|
1392
|
+
? `
|
|
1388
1393
|
${sqlite
|
|
1389
|
-
? ` // Create default roles (SQLite-compatible: individual upserts)
|
|
1390
|
-
for (const role of [
|
|
1391
|
-
{ name: 'admin', description: 'Full system access', permissions: JSON.stringify(['*']) },
|
|
1392
|
-
{ name: 'user', description: 'Regular user access', permissions: JSON.stringify(['chat:read', 'chat:write', 'profile:read']) },
|
|
1393
|
-
{ name: 'viewer', description: 'Read-only access', permissions: JSON.stringify(['chat:read']) },
|
|
1394
|
-
]) {
|
|
1395
|
-
await prisma.role.upsert({
|
|
1396
|
-
where: { name: role.name },
|
|
1397
|
-
update: {},
|
|
1398
|
-
create: role,
|
|
1399
|
-
});
|
|
1394
|
+
? ` // Create default roles (SQLite-compatible: individual upserts)
|
|
1395
|
+
for (const role of [
|
|
1396
|
+
{ name: 'admin', description: 'Full system access', permissions: JSON.stringify(['*']) },
|
|
1397
|
+
{ name: 'user', description: 'Regular user access', permissions: JSON.stringify(['chat:read', 'chat:write', 'profile:read']) },
|
|
1398
|
+
{ name: 'viewer', description: 'Read-only access', permissions: JSON.stringify(['chat:read']) },
|
|
1399
|
+
]) {
|
|
1400
|
+
await prisma.role.upsert({
|
|
1401
|
+
where: { name: role.name },
|
|
1402
|
+
update: {},
|
|
1403
|
+
create: role,
|
|
1404
|
+
});
|
|
1400
1405
|
}`
|
|
1401
|
-
: ` // Create default roles
|
|
1402
|
-
await prisma.role.createMany({
|
|
1403
|
-
data: [
|
|
1404
|
-
{
|
|
1405
|
-
name: 'admin',
|
|
1406
|
-
description: 'Full system access',
|
|
1407
|
-
permissions: ['*'],
|
|
1408
|
-
},
|
|
1409
|
-
{
|
|
1410
|
-
name: 'user',
|
|
1411
|
-
description: 'Regular user access',
|
|
1412
|
-
permissions: ['chat:read', 'chat:write', 'profile:read'],
|
|
1413
|
-
},
|
|
1414
|
-
{
|
|
1415
|
-
name: 'viewer',
|
|
1416
|
-
description: 'Read-only access',
|
|
1417
|
-
permissions: ['chat:read'],
|
|
1418
|
-
},
|
|
1419
|
-
],
|
|
1420
|
-
skipDuplicates: true,
|
|
1421
|
-
});`}
|
|
1422
|
-
|
|
1423
|
-
console.log('✅ Default roles created');
|
|
1424
|
-
|
|
1425
|
-
// Assign admin role to admin user
|
|
1426
|
-
const adminRole = await prisma.role.findUnique({ where: { name: 'admin' } });
|
|
1427
|
-
if (adminRole) {
|
|
1428
|
-
await prisma.userRole.upsert({
|
|
1429
|
-
where: { userId_roleId: { userId: admin.id, roleId: adminRole.id } },
|
|
1430
|
-
update: {},
|
|
1431
|
-
create: { userId: admin.id, roleId: adminRole.id },
|
|
1432
|
-
});
|
|
1433
|
-
console.log('✅ Admin user assigned admin role');
|
|
1434
|
-
}
|
|
1406
|
+
: ` // Create default roles
|
|
1407
|
+
await prisma.role.createMany({
|
|
1408
|
+
data: [
|
|
1409
|
+
{
|
|
1410
|
+
name: 'admin',
|
|
1411
|
+
description: 'Full system access',
|
|
1412
|
+
permissions: ['*'],
|
|
1413
|
+
},
|
|
1414
|
+
{
|
|
1415
|
+
name: 'user',
|
|
1416
|
+
description: 'Regular user access',
|
|
1417
|
+
permissions: ['chat:read', 'chat:write', 'profile:read'],
|
|
1418
|
+
},
|
|
1419
|
+
{
|
|
1420
|
+
name: 'viewer',
|
|
1421
|
+
description: 'Read-only access',
|
|
1422
|
+
permissions: ['chat:read'],
|
|
1423
|
+
},
|
|
1424
|
+
],
|
|
1425
|
+
skipDuplicates: true,
|
|
1426
|
+
});`}
|
|
1427
|
+
|
|
1428
|
+
console.log('✅ Default roles created');
|
|
1429
|
+
|
|
1430
|
+
// Assign admin role to admin user
|
|
1431
|
+
const adminRole = await prisma.role.findUnique({ where: { name: 'admin' } });
|
|
1432
|
+
if (adminRole) {
|
|
1433
|
+
await prisma.userRole.upsert({
|
|
1434
|
+
where: { userId_roleId: { userId: admin.id, roleId: adminRole.id } },
|
|
1435
|
+
update: {},
|
|
1436
|
+
create: { userId: admin.id, roleId: adminRole.id },
|
|
1437
|
+
});
|
|
1438
|
+
console.log('✅ Admin user assigned admin role');
|
|
1439
|
+
}
|
|
1435
1440
|
`
|
|
1436
|
-
: ''}
|
|
1437
|
-
// ── Seed Providers (if API keys are in .env) ────────────────────
|
|
1438
|
-
const openaiKey = process.env.OPENAI_API_KEY;
|
|
1439
|
-
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
1440
|
-
|
|
1441
|
-
if (openaiKey) {
|
|
1442
|
-
const provider = await prisma.provider.upsert({
|
|
1443
|
-
where: { id: 'seed-openai' },
|
|
1444
|
-
update: {},
|
|
1445
|
-
create: {
|
|
1446
|
-
id: 'seed-openai',
|
|
1447
|
-
name: 'OpenAI',
|
|
1448
|
-
type: 'openai',
|
|
1449
|
-
description: 'OpenAI API (seeded from .env)',
|
|
1450
|
-
baseUrl: 'https://api.openai.com/v1',
|
|
1451
|
-
apiKey: encrypt(openaiKey),
|
|
1452
|
-
config: ${sqlite ? `JSON.stringify({ defaultModel: 'gpt-4o-mini' })` : `{ defaultModel: 'gpt-4o-mini' }`},
|
|
1453
|
-
status: 'active',
|
|
1454
|
-
isDefault: true,
|
|
1455
|
-
priority: 0,
|
|
1456
|
-
createdBy: admin.id,
|
|
1457
|
-
},
|
|
1458
|
-
});
|
|
1459
|
-
|
|
1460
|
-
// Create default OpenAI models
|
|
1441
|
+
: ''}
|
|
1442
|
+
// ── Seed Providers (if API keys are in .env) ────────────────────
|
|
1443
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
1444
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
1445
|
+
|
|
1446
|
+
if (openaiKey) {
|
|
1447
|
+
const provider = await prisma.provider.upsert({
|
|
1448
|
+
where: { id: 'seed-openai' },
|
|
1449
|
+
update: {},
|
|
1450
|
+
create: {
|
|
1451
|
+
id: 'seed-openai',
|
|
1452
|
+
name: 'OpenAI',
|
|
1453
|
+
type: 'openai',
|
|
1454
|
+
description: 'OpenAI API (seeded from .env)',
|
|
1455
|
+
baseUrl: 'https://api.openai.com/v1',
|
|
1456
|
+
apiKey: encrypt(openaiKey),
|
|
1457
|
+
config: ${sqlite ? `JSON.stringify({ defaultModel: 'gpt-4o-mini' })` : `{ defaultModel: 'gpt-4o-mini' }`},
|
|
1458
|
+
status: 'active',
|
|
1459
|
+
isDefault: true,
|
|
1460
|
+
priority: 0,
|
|
1461
|
+
createdBy: admin.id,
|
|
1462
|
+
},
|
|
1463
|
+
});
|
|
1464
|
+
|
|
1465
|
+
// Create default OpenAI models
|
|
1461
1466
|
${sqlite
|
|
1462
|
-
? ` for (const m of [
|
|
1463
|
-
{ providerId: provider.id, modelId: 'gpt-4o', name: 'GPT-4o', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 128000, inputCost: 2.5, outputCost: 10 },
|
|
1464
|
-
{ providerId: provider.id, modelId: 'gpt-4o-mini', name: 'GPT-4o Mini', capabilities: JSON.stringify(['chat']), contextWindow: 128000, inputCost: 0.15, outputCost: 0.6 },
|
|
1465
|
-
{ providerId: provider.id, modelId: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 128000, inputCost: 10, outputCost: 30 },
|
|
1466
|
-
{ providerId: provider.id, modelId: 'text-embedding-3-small', name: 'Embedding 3 Small', capabilities: JSON.stringify(['embedding']), contextWindow: 8191, inputCost: 0.02, outputCost: 0 },
|
|
1467
|
-
{ providerId: provider.id, modelId: 'text-embedding-3-large', name: 'Embedding 3 Large', capabilities: JSON.stringify(['embedding']), contextWindow: 8191, inputCost: 0.13, outputCost: 0 },
|
|
1468
|
-
]) {
|
|
1469
|
-
try { await prisma.model.create({ data: m }); } catch { /* skip duplicate */ }
|
|
1467
|
+
? ` for (const m of [
|
|
1468
|
+
{ providerId: provider.id, modelId: 'gpt-4o', name: 'GPT-4o', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 128000, inputCost: 2.5, outputCost: 10 },
|
|
1469
|
+
{ providerId: provider.id, modelId: 'gpt-4o-mini', name: 'GPT-4o Mini', capabilities: JSON.stringify(['chat']), contextWindow: 128000, inputCost: 0.15, outputCost: 0.6 },
|
|
1470
|
+
{ providerId: provider.id, modelId: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 128000, inputCost: 10, outputCost: 30 },
|
|
1471
|
+
{ providerId: provider.id, modelId: 'text-embedding-3-small', name: 'Embedding 3 Small', capabilities: JSON.stringify(['embedding']), contextWindow: 8191, inputCost: 0.02, outputCost: 0 },
|
|
1472
|
+
{ providerId: provider.id, modelId: 'text-embedding-3-large', name: 'Embedding 3 Large', capabilities: JSON.stringify(['embedding']), contextWindow: 8191, inputCost: 0.13, outputCost: 0 },
|
|
1473
|
+
]) {
|
|
1474
|
+
try { await prisma.model.create({ data: m }); } catch { /* skip duplicate */ }
|
|
1470
1475
|
}`
|
|
1471
|
-
: ` await prisma.model.createMany({
|
|
1472
|
-
data: [
|
|
1473
|
-
{ providerId: provider.id, modelId: 'gpt-4o', name: 'GPT-4o', capabilities: ['chat', 'vision'], contextWindow: 128000, inputCost: 2.5, outputCost: 10 },
|
|
1474
|
-
{ providerId: provider.id, modelId: 'gpt-4o-mini', name: 'GPT-4o Mini', capabilities: ['chat'], contextWindow: 128000, inputCost: 0.15, outputCost: 0.6 },
|
|
1475
|
-
{ providerId: provider.id, modelId: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: ['chat', 'vision'], contextWindow: 128000, inputCost: 10, outputCost: 30 },
|
|
1476
|
-
{ providerId: provider.id, modelId: 'text-embedding-3-small', name: 'Embedding 3 Small', capabilities: ['embedding'], contextWindow: 8191, inputCost: 0.02, outputCost: 0 },
|
|
1477
|
-
{ providerId: provider.id, modelId: 'text-embedding-3-large', name: 'Embedding 3 Large', capabilities: ['embedding'], contextWindow: 8191, inputCost: 0.13, outputCost: 0 },
|
|
1478
|
-
],
|
|
1479
|
-
skipDuplicates: true,
|
|
1480
|
-
});`}
|
|
1481
|
-
|
|
1482
|
-
console.log('✅ OpenAI provider seeded with models');
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
if (anthropicKey) {
|
|
1486
|
-
const provider = await prisma.provider.upsert({
|
|
1487
|
-
where: { id: 'seed-anthropic' },
|
|
1488
|
-
update: {},
|
|
1489
|
-
create: {
|
|
1490
|
-
id: 'seed-anthropic',
|
|
1491
|
-
name: 'Anthropic',
|
|
1492
|
-
type: 'anthropic',
|
|
1493
|
-
description: 'Anthropic Claude API (seeded from .env)',
|
|
1494
|
-
baseUrl: 'https://api.anthropic.com/v1',
|
|
1495
|
-
apiKey: encrypt(anthropicKey),
|
|
1496
|
-
config: ${sqlite ? `JSON.stringify({ defaultModel: 'claude-sonnet-4-20250514' })` : `{ defaultModel: 'claude-sonnet-4-20250514' }`},
|
|
1497
|
-
status: 'active',
|
|
1498
|
-
isDefault: false,
|
|
1499
|
-
priority: 1,
|
|
1500
|
-
createdBy: admin.id,
|
|
1501
|
-
},
|
|
1502
|
-
});
|
|
1503
|
-
|
|
1476
|
+
: ` await prisma.model.createMany({
|
|
1477
|
+
data: [
|
|
1478
|
+
{ providerId: provider.id, modelId: 'gpt-4o', name: 'GPT-4o', capabilities: ['chat', 'vision'], contextWindow: 128000, inputCost: 2.5, outputCost: 10 },
|
|
1479
|
+
{ providerId: provider.id, modelId: 'gpt-4o-mini', name: 'GPT-4o Mini', capabilities: ['chat'], contextWindow: 128000, inputCost: 0.15, outputCost: 0.6 },
|
|
1480
|
+
{ providerId: provider.id, modelId: 'gpt-4-turbo', name: 'GPT-4 Turbo', capabilities: ['chat', 'vision'], contextWindow: 128000, inputCost: 10, outputCost: 30 },
|
|
1481
|
+
{ providerId: provider.id, modelId: 'text-embedding-3-small', name: 'Embedding 3 Small', capabilities: ['embedding'], contextWindow: 8191, inputCost: 0.02, outputCost: 0 },
|
|
1482
|
+
{ providerId: provider.id, modelId: 'text-embedding-3-large', name: 'Embedding 3 Large', capabilities: ['embedding'], contextWindow: 8191, inputCost: 0.13, outputCost: 0 },
|
|
1483
|
+
],
|
|
1484
|
+
skipDuplicates: true,
|
|
1485
|
+
});`}
|
|
1486
|
+
|
|
1487
|
+
console.log('✅ OpenAI provider seeded with models');
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
if (anthropicKey) {
|
|
1491
|
+
const provider = await prisma.provider.upsert({
|
|
1492
|
+
where: { id: 'seed-anthropic' },
|
|
1493
|
+
update: {},
|
|
1494
|
+
create: {
|
|
1495
|
+
id: 'seed-anthropic',
|
|
1496
|
+
name: 'Anthropic',
|
|
1497
|
+
type: 'anthropic',
|
|
1498
|
+
description: 'Anthropic Claude API (seeded from .env)',
|
|
1499
|
+
baseUrl: 'https://api.anthropic.com/v1',
|
|
1500
|
+
apiKey: encrypt(anthropicKey),
|
|
1501
|
+
config: ${sqlite ? `JSON.stringify({ defaultModel: 'claude-sonnet-4-20250514' })` : `{ defaultModel: 'claude-sonnet-4-20250514' }`},
|
|
1502
|
+
status: 'active',
|
|
1503
|
+
isDefault: false,
|
|
1504
|
+
priority: 1,
|
|
1505
|
+
createdBy: admin.id,
|
|
1506
|
+
},
|
|
1507
|
+
});
|
|
1508
|
+
|
|
1504
1509
|
${sqlite
|
|
1505
|
-
? ` for (const m of [
|
|
1506
|
-
{ providerId: provider.id, modelId: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1507
|
-
{ 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 },
|
|
1508
|
-
{ providerId: provider.id, modelId: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', capabilities: JSON.stringify(['chat']), contextWindow: 200000, inputCost: 0.25, outputCost: 1.25 },
|
|
1509
|
-
]) {
|
|
1510
|
-
try { await prisma.model.create({ data: m }); } catch { /* skip duplicate */ }
|
|
1510
|
+
? ` for (const m of [
|
|
1511
|
+
{ providerId: provider.id, modelId: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', capabilities: JSON.stringify(['chat', 'vision']), contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1512
|
+
{ 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 },
|
|
1513
|
+
{ providerId: provider.id, modelId: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', capabilities: JSON.stringify(['chat']), contextWindow: 200000, inputCost: 0.25, outputCost: 1.25 },
|
|
1514
|
+
]) {
|
|
1515
|
+
try { await prisma.model.create({ data: m }); } catch { /* skip duplicate */ }
|
|
1511
1516
|
}`
|
|
1512
|
-
: ` await prisma.model.createMany({
|
|
1513
|
-
data: [
|
|
1514
|
-
{ providerId: provider.id, modelId: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', capabilities: ['chat', 'vision'], contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1515
|
-
{ providerId: provider.id, modelId: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', capabilities: ['chat', 'vision'], contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1516
|
-
{ providerId: provider.id, modelId: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', capabilities: ['chat'], contextWindow: 200000, inputCost: 0.25, outputCost: 1.25 },
|
|
1517
|
-
],
|
|
1518
|
-
skipDuplicates: true,
|
|
1519
|
-
});`}
|
|
1520
|
-
|
|
1521
|
-
console.log('✅ Anthropic provider seeded with models');
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
if (!openaiKey && !anthropicKey) {
|
|
1525
|
-
console.log('ℹ️ No API keys in .env — skip provider seeding. Add via Provider Management UI.');
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
console.log('🎉 Seeding completed!');
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
main()
|
|
1532
|
-
.catch((e) => {
|
|
1533
|
-
console.error('❌ Seeding failed:', e);
|
|
1534
|
-
process.exit(1);
|
|
1535
|
-
})
|
|
1536
|
-
.finally(async () => {
|
|
1537
|
-
await prisma.$disconnect();
|
|
1538
|
-
});
|
|
1517
|
+
: ` await prisma.model.createMany({
|
|
1518
|
+
data: [
|
|
1519
|
+
{ providerId: provider.id, modelId: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4', capabilities: ['chat', 'vision'], contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1520
|
+
{ providerId: provider.id, modelId: 'claude-3-5-sonnet-20241022', name: 'Claude 3.5 Sonnet', capabilities: ['chat', 'vision'], contextWindow: 200000, inputCost: 3, outputCost: 15 },
|
|
1521
|
+
{ providerId: provider.id, modelId: 'claude-3-haiku-20240307', name: 'Claude 3 Haiku', capabilities: ['chat'], contextWindow: 200000, inputCost: 0.25, outputCost: 1.25 },
|
|
1522
|
+
],
|
|
1523
|
+
skipDuplicates: true,
|
|
1524
|
+
});`}
|
|
1525
|
+
|
|
1526
|
+
console.log('✅ Anthropic provider seeded with models');
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
if (!openaiKey && !anthropicKey) {
|
|
1530
|
+
console.log('ℹ️ No API keys in .env — skip provider seeding. Add via Provider Management UI.');
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
console.log('🎉 Seeding completed!');
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
main()
|
|
1537
|
+
.catch((e) => {
|
|
1538
|
+
console.error('❌ Seeding failed:', e);
|
|
1539
|
+
process.exit(1);
|
|
1540
|
+
})
|
|
1541
|
+
.finally(async () => {
|
|
1542
|
+
await prisma.$disconnect();
|
|
1543
|
+
});
|
|
1539
1544
|
`;
|
|
1540
1545
|
const seedDest = path_1.default.join(targetDir, 'prisma/seed.ts');
|
|
1541
1546
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(seedDest));
|
|
@@ -1548,208 +1553,208 @@ async function createDockerCompose(targetDir) {
|
|
|
1548
1553
|
async function createInstallScripts(targetDir, features, sqlite) {
|
|
1549
1554
|
// Windows install.bat
|
|
1550
1555
|
const installBat = sqlite
|
|
1551
|
-
? `@echo off
|
|
1552
|
-
REM Installation Script for ChimerAI Project (SQLite)
|
|
1553
|
-
echo.
|
|
1554
|
-
echo ================================================
|
|
1555
|
-
echo ChimerAI Project Setup (SQLite - No Docker needed)
|
|
1556
|
-
echo ================================================
|
|
1557
|
-
echo.
|
|
1558
|
-
|
|
1559
|
-
REM Check Node.js
|
|
1560
|
-
where node >nul 2>nul
|
|
1561
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1562
|
-
echo [ERROR] Node.js is not installed
|
|
1563
|
-
pause
|
|
1564
|
-
exit /b 1
|
|
1565
|
-
)
|
|
1566
|
-
|
|
1567
|
-
echo [1/3] Installing dependencies...
|
|
1568
|
-
call npm install
|
|
1569
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1570
|
-
echo [ERROR] Failed to install dependencies
|
|
1571
|
-
pause
|
|
1572
|
-
exit /b 1
|
|
1573
|
-
)
|
|
1574
|
-
|
|
1575
|
-
echo [2/3] Setting up database...
|
|
1576
|
-
call npm run db:push
|
|
1577
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1578
|
-
echo [ERROR] Failed to setup database
|
|
1579
|
-
pause
|
|
1580
|
-
exit /b 1
|
|
1581
|
-
)
|
|
1582
|
-
|
|
1583
|
-
echo [3/3] Seeding database...
|
|
1584
|
-
call npm run db:seed
|
|
1585
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1586
|
-
echo [WARNING] Seeding failed, but you can continue
|
|
1587
|
-
)
|
|
1588
|
-
|
|
1589
|
-
echo.
|
|
1590
|
-
echo ================================================
|
|
1591
|
-
echo Setup completed successfully!
|
|
1592
|
-
echo ================================================
|
|
1593
|
-
echo.
|
|
1594
|
-
echo Next steps:
|
|
1595
|
-
echo npm run dev
|
|
1596
|
-
echo Open: http://localhost:3001
|
|
1597
|
-
echo.
|
|
1598
|
-
${features.includes('auth') ? 'echo Login with:\necho Email: admin@example.com\necho Password: admin123\necho.' : ''}
|
|
1599
|
-
pause
|
|
1556
|
+
? `@echo off
|
|
1557
|
+
REM Installation Script for ChimerAI Project (SQLite)
|
|
1558
|
+
echo.
|
|
1559
|
+
echo ================================================
|
|
1560
|
+
echo ChimerAI Project Setup (SQLite - No Docker needed)
|
|
1561
|
+
echo ================================================
|
|
1562
|
+
echo.
|
|
1563
|
+
|
|
1564
|
+
REM Check Node.js
|
|
1565
|
+
where node >nul 2>nul
|
|
1566
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1567
|
+
echo [ERROR] Node.js is not installed
|
|
1568
|
+
pause
|
|
1569
|
+
exit /b 1
|
|
1570
|
+
)
|
|
1571
|
+
|
|
1572
|
+
echo [1/3] Installing dependencies...
|
|
1573
|
+
call npm install
|
|
1574
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1575
|
+
echo [ERROR] Failed to install dependencies
|
|
1576
|
+
pause
|
|
1577
|
+
exit /b 1
|
|
1578
|
+
)
|
|
1579
|
+
|
|
1580
|
+
echo [2/3] Setting up database...
|
|
1581
|
+
call npm run db:push
|
|
1582
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1583
|
+
echo [ERROR] Failed to setup database
|
|
1584
|
+
pause
|
|
1585
|
+
exit /b 1
|
|
1586
|
+
)
|
|
1587
|
+
|
|
1588
|
+
echo [3/3] Seeding database...
|
|
1589
|
+
call npm run db:seed
|
|
1590
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1591
|
+
echo [WARNING] Seeding failed, but you can continue
|
|
1592
|
+
)
|
|
1593
|
+
|
|
1594
|
+
echo.
|
|
1595
|
+
echo ================================================
|
|
1596
|
+
echo Setup completed successfully!
|
|
1597
|
+
echo ================================================
|
|
1598
|
+
echo.
|
|
1599
|
+
echo Next steps:
|
|
1600
|
+
echo npm run dev
|
|
1601
|
+
echo Open: http://localhost:3001
|
|
1602
|
+
echo.
|
|
1603
|
+
${features.includes('auth') ? 'echo Login with:\necho Email: admin@example.com\necho Password: admin123\necho.' : ''}
|
|
1604
|
+
pause
|
|
1600
1605
|
`
|
|
1601
|
-
: `@echo off
|
|
1602
|
-
REM Installation Script for ChimerAI Project
|
|
1603
|
-
echo.
|
|
1604
|
-
echo ================================================
|
|
1605
|
-
echo ChimerAI Project Setup
|
|
1606
|
-
echo ================================================
|
|
1607
|
-
echo.
|
|
1608
|
-
|
|
1609
|
-
REM Check Node.js
|
|
1610
|
-
where node >nul 2>nul
|
|
1611
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1612
|
-
echo [ERROR] Node.js is not installed
|
|
1613
|
-
pause
|
|
1614
|
-
exit /b 1
|
|
1615
|
-
)
|
|
1616
|
-
|
|
1617
|
-
REM Check Docker
|
|
1618
|
-
where docker >nul 2>nul
|
|
1619
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1620
|
-
echo [ERROR] Docker is not installed
|
|
1621
|
-
pause
|
|
1622
|
-
exit /b 1
|
|
1623
|
-
)
|
|
1624
|
-
|
|
1625
|
-
echo [1/5] Installing dependencies...
|
|
1626
|
-
call npm install
|
|
1627
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1628
|
-
echo [ERROR] Failed to install dependencies
|
|
1629
|
-
pause
|
|
1630
|
-
exit /b 1
|
|
1631
|
-
)
|
|
1632
|
-
|
|
1633
|
-
echo [2/5] Starting Docker containers...
|
|
1634
|
-
call docker-compose up -d
|
|
1635
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1636
|
-
echo [ERROR] Failed to start Docker
|
|
1637
|
-
pause
|
|
1638
|
-
exit /b 1
|
|
1639
|
-
)
|
|
1640
|
-
|
|
1641
|
-
echo [3/5] Waiting for database...
|
|
1642
|
-
timeout /t 5 /nobreak >nul
|
|
1643
|
-
|
|
1644
|
-
echo [4/5] Setting up database...
|
|
1645
|
-
call npm run db:push
|
|
1646
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1647
|
-
echo [ERROR] Failed to setup database
|
|
1648
|
-
pause
|
|
1649
|
-
exit /b 1
|
|
1650
|
-
)
|
|
1651
|
-
|
|
1652
|
-
echo [5/5] Seeding database...
|
|
1653
|
-
call npm run db:seed
|
|
1654
|
-
if %ERRORLEVEL% NEQ 0 (
|
|
1655
|
-
echo [WARNING] Seeding failed, but you can continue
|
|
1656
|
-
)
|
|
1657
|
-
|
|
1658
|
-
echo.
|
|
1659
|
-
echo ================================================
|
|
1660
|
-
echo Setup completed successfully!
|
|
1661
|
-
echo ================================================
|
|
1662
|
-
echo.
|
|
1663
|
-
echo Next steps:
|
|
1664
|
-
echo npm run dev
|
|
1665
|
-
echo Open: http://localhost:3001
|
|
1666
|
-
echo.
|
|
1667
|
-
${features.includes('auth') ? 'echo Login with:\necho Email: admin@example.com\necho Password: admin123\necho.' : ''}
|
|
1668
|
-
pause
|
|
1606
|
+
: `@echo off
|
|
1607
|
+
REM Installation Script for ChimerAI Project
|
|
1608
|
+
echo.
|
|
1609
|
+
echo ================================================
|
|
1610
|
+
echo ChimerAI Project Setup
|
|
1611
|
+
echo ================================================
|
|
1612
|
+
echo.
|
|
1613
|
+
|
|
1614
|
+
REM Check Node.js
|
|
1615
|
+
where node >nul 2>nul
|
|
1616
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1617
|
+
echo [ERROR] Node.js is not installed
|
|
1618
|
+
pause
|
|
1619
|
+
exit /b 1
|
|
1620
|
+
)
|
|
1621
|
+
|
|
1622
|
+
REM Check Docker
|
|
1623
|
+
where docker >nul 2>nul
|
|
1624
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1625
|
+
echo [ERROR] Docker is not installed
|
|
1626
|
+
pause
|
|
1627
|
+
exit /b 1
|
|
1628
|
+
)
|
|
1629
|
+
|
|
1630
|
+
echo [1/5] Installing dependencies...
|
|
1631
|
+
call npm install
|
|
1632
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1633
|
+
echo [ERROR] Failed to install dependencies
|
|
1634
|
+
pause
|
|
1635
|
+
exit /b 1
|
|
1636
|
+
)
|
|
1637
|
+
|
|
1638
|
+
echo [2/5] Starting Docker containers...
|
|
1639
|
+
call docker-compose up -d
|
|
1640
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1641
|
+
echo [ERROR] Failed to start Docker
|
|
1642
|
+
pause
|
|
1643
|
+
exit /b 1
|
|
1644
|
+
)
|
|
1645
|
+
|
|
1646
|
+
echo [3/5] Waiting for database...
|
|
1647
|
+
timeout /t 5 /nobreak >nul
|
|
1648
|
+
|
|
1649
|
+
echo [4/5] Setting up database...
|
|
1650
|
+
call npm run db:push
|
|
1651
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1652
|
+
echo [ERROR] Failed to setup database
|
|
1653
|
+
pause
|
|
1654
|
+
exit /b 1
|
|
1655
|
+
)
|
|
1656
|
+
|
|
1657
|
+
echo [5/5] Seeding database...
|
|
1658
|
+
call npm run db:seed
|
|
1659
|
+
if %ERRORLEVEL% NEQ 0 (
|
|
1660
|
+
echo [WARNING] Seeding failed, but you can continue
|
|
1661
|
+
)
|
|
1662
|
+
|
|
1663
|
+
echo.
|
|
1664
|
+
echo ================================================
|
|
1665
|
+
echo Setup completed successfully!
|
|
1666
|
+
echo ================================================
|
|
1667
|
+
echo.
|
|
1668
|
+
echo Next steps:
|
|
1669
|
+
echo npm run dev
|
|
1670
|
+
echo Open: http://localhost:3001
|
|
1671
|
+
echo.
|
|
1672
|
+
${features.includes('auth') ? 'echo Login with:\necho Email: admin@example.com\necho Password: admin123\necho.' : ''}
|
|
1673
|
+
pause
|
|
1669
1674
|
`;
|
|
1670
1675
|
// Linux/macOS install.sh
|
|
1671
1676
|
const installSh = sqlite
|
|
1672
|
-
? `#!/bin/bash
|
|
1673
|
-
set -e
|
|
1674
|
-
|
|
1675
|
-
echo ""
|
|
1676
|
-
echo "================================================"
|
|
1677
|
-
echo " ChimerAI Project Setup (SQLite - No Docker needed)"
|
|
1678
|
-
echo "================================================"
|
|
1679
|
-
echo ""
|
|
1680
|
-
|
|
1681
|
-
# Check Node.js
|
|
1682
|
-
if ! command -v node &> /dev/null; then
|
|
1683
|
-
echo "[ERROR] Node.js is not installed"
|
|
1684
|
-
exit 1
|
|
1685
|
-
fi
|
|
1686
|
-
|
|
1687
|
-
echo "[1/3] Installing dependencies..."
|
|
1688
|
-
npm install
|
|
1689
|
-
|
|
1690
|
-
echo "[2/3] Setting up database..."
|
|
1691
|
-
npm run db:push
|
|
1692
|
-
|
|
1693
|
-
echo "[3/3] Seeding database..."
|
|
1694
|
-
npm run db:seed || echo "[WARNING] Seeding failed, but you can continue"
|
|
1695
|
-
|
|
1696
|
-
echo ""
|
|
1697
|
-
echo "================================================"
|
|
1698
|
-
echo " Setup completed successfully!"
|
|
1699
|
-
echo "================================================"
|
|
1700
|
-
echo ""
|
|
1701
|
-
echo "Next steps:"
|
|
1702
|
-
echo " npm run dev"
|
|
1703
|
-
echo " Open: http://localhost:3001"
|
|
1704
|
-
echo ""
|
|
1705
|
-
${features.includes('auth') ? 'echo "Login with:"\necho " Email: admin@example.com"\necho " Password: admin123"\necho ""' : ''}
|
|
1677
|
+
? `#!/bin/bash
|
|
1678
|
+
set -e
|
|
1679
|
+
|
|
1680
|
+
echo ""
|
|
1681
|
+
echo "================================================"
|
|
1682
|
+
echo " ChimerAI Project Setup (SQLite - No Docker needed)"
|
|
1683
|
+
echo "================================================"
|
|
1684
|
+
echo ""
|
|
1685
|
+
|
|
1686
|
+
# Check Node.js
|
|
1687
|
+
if ! command -v node &> /dev/null; then
|
|
1688
|
+
echo "[ERROR] Node.js is not installed"
|
|
1689
|
+
exit 1
|
|
1690
|
+
fi
|
|
1691
|
+
|
|
1692
|
+
echo "[1/3] Installing dependencies..."
|
|
1693
|
+
npm install
|
|
1694
|
+
|
|
1695
|
+
echo "[2/3] Setting up database..."
|
|
1696
|
+
npm run db:push
|
|
1697
|
+
|
|
1698
|
+
echo "[3/3] Seeding database..."
|
|
1699
|
+
npm run db:seed || echo "[WARNING] Seeding failed, but you can continue"
|
|
1700
|
+
|
|
1701
|
+
echo ""
|
|
1702
|
+
echo "================================================"
|
|
1703
|
+
echo " Setup completed successfully!"
|
|
1704
|
+
echo "================================================"
|
|
1705
|
+
echo ""
|
|
1706
|
+
echo "Next steps:"
|
|
1707
|
+
echo " npm run dev"
|
|
1708
|
+
echo " Open: http://localhost:3001"
|
|
1709
|
+
echo ""
|
|
1710
|
+
${features.includes('auth') ? 'echo "Login with:"\necho " Email: admin@example.com"\necho " Password: admin123"\necho ""' : ''}
|
|
1706
1711
|
`
|
|
1707
|
-
: `#!/bin/bash
|
|
1708
|
-
set -e
|
|
1709
|
-
|
|
1710
|
-
echo ""
|
|
1711
|
-
echo "================================================"
|
|
1712
|
-
echo " ChimerAI Project Setup"
|
|
1713
|
-
echo "================================================"
|
|
1714
|
-
echo ""
|
|
1715
|
-
|
|
1716
|
-
# Check Node.js
|
|
1717
|
-
if ! command -v node &> /dev/null; then
|
|
1718
|
-
echo "[ERROR] Node.js is not installed"
|
|
1719
|
-
exit 1
|
|
1720
|
-
fi
|
|
1721
|
-
|
|
1722
|
-
# Check Docker
|
|
1723
|
-
if ! command -v docker &> /dev/null; then
|
|
1724
|
-
echo "[ERROR] Docker is not installed"
|
|
1725
|
-
exit 1
|
|
1726
|
-
fi
|
|
1727
|
-
|
|
1728
|
-
echo "[1/5] Installing dependencies..."
|
|
1729
|
-
npm install
|
|
1730
|
-
|
|
1731
|
-
echo "[2/5] Starting Docker containers..."
|
|
1732
|
-
docker-compose up -d
|
|
1733
|
-
|
|
1734
|
-
echo "[3/5] Waiting for database..."
|
|
1735
|
-
sleep 5
|
|
1736
|
-
|
|
1737
|
-
echo "[4/5] Setting up database..."
|
|
1738
|
-
npm run db:push
|
|
1739
|
-
|
|
1740
|
-
echo "[5/5] Seeding database..."
|
|
1741
|
-
npm run db:seed || echo "[WARNING] Seeding failed, but you can continue"
|
|
1742
|
-
|
|
1743
|
-
echo ""
|
|
1744
|
-
echo "================================================"
|
|
1745
|
-
echo " Setup completed successfully!"
|
|
1746
|
-
echo "================================================"
|
|
1747
|
-
echo ""
|
|
1748
|
-
echo "Next steps:"
|
|
1749
|
-
echo " npm run dev"
|
|
1750
|
-
echo " Open: http://localhost:3001"
|
|
1751
|
-
echo ""
|
|
1752
|
-
${features.includes('auth') ? 'echo "Login with:"\necho " Email: admin@example.com"\necho " Password: admin123"\necho ""' : ''}
|
|
1712
|
+
: `#!/bin/bash
|
|
1713
|
+
set -e
|
|
1714
|
+
|
|
1715
|
+
echo ""
|
|
1716
|
+
echo "================================================"
|
|
1717
|
+
echo " ChimerAI Project Setup"
|
|
1718
|
+
echo "================================================"
|
|
1719
|
+
echo ""
|
|
1720
|
+
|
|
1721
|
+
# Check Node.js
|
|
1722
|
+
if ! command -v node &> /dev/null; then
|
|
1723
|
+
echo "[ERROR] Node.js is not installed"
|
|
1724
|
+
exit 1
|
|
1725
|
+
fi
|
|
1726
|
+
|
|
1727
|
+
# Check Docker
|
|
1728
|
+
if ! command -v docker &> /dev/null; then
|
|
1729
|
+
echo "[ERROR] Docker is not installed"
|
|
1730
|
+
exit 1
|
|
1731
|
+
fi
|
|
1732
|
+
|
|
1733
|
+
echo "[1/5] Installing dependencies..."
|
|
1734
|
+
npm install
|
|
1735
|
+
|
|
1736
|
+
echo "[2/5] Starting Docker containers..."
|
|
1737
|
+
docker-compose up -d
|
|
1738
|
+
|
|
1739
|
+
echo "[3/5] Waiting for database..."
|
|
1740
|
+
sleep 5
|
|
1741
|
+
|
|
1742
|
+
echo "[4/5] Setting up database..."
|
|
1743
|
+
npm run db:push
|
|
1744
|
+
|
|
1745
|
+
echo "[5/5] Seeding database..."
|
|
1746
|
+
npm run db:seed || echo "[WARNING] Seeding failed, but you can continue"
|
|
1747
|
+
|
|
1748
|
+
echo ""
|
|
1749
|
+
echo "================================================"
|
|
1750
|
+
echo " Setup completed successfully!"
|
|
1751
|
+
echo "================================================"
|
|
1752
|
+
echo ""
|
|
1753
|
+
echo "Next steps:"
|
|
1754
|
+
echo " npm run dev"
|
|
1755
|
+
echo " Open: http://localhost:3001"
|
|
1756
|
+
echo ""
|
|
1757
|
+
${features.includes('auth') ? 'echo "Login with:"\necho " Email: admin@example.com"\necho " Password: admin123"\necho ""' : ''}
|
|
1753
1758
|
`;
|
|
1754
1759
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'install.bat'), installBat);
|
|
1755
1760
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'install.sh'), installSh);
|
|
@@ -1762,12 +1767,12 @@ ${features.includes('auth') ? 'echo "Login with:"\necho " Email: admin@example.
|
|
|
1762
1767
|
}
|
|
1763
1768
|
}
|
|
1764
1769
|
async function createReadme(targetDir, projectName, features) {
|
|
1765
|
-
let readme = `# ${projectName}
|
|
1766
|
-
|
|
1767
|
-
Built with ChimerAI Kickstart
|
|
1768
|
-
|
|
1769
|
-
## Features
|
|
1770
|
-
|
|
1770
|
+
let readme = `# ${projectName}
|
|
1771
|
+
|
|
1772
|
+
Built with ChimerAI Kickstart
|
|
1773
|
+
|
|
1774
|
+
## Features
|
|
1775
|
+
|
|
1771
1776
|
`;
|
|
1772
1777
|
const featureDescriptions = {
|
|
1773
1778
|
auth: '- 🔐 Authentication with NextAuth',
|
|
@@ -1785,95 +1790,95 @@ Built with ChimerAI Kickstart
|
|
|
1785
1790
|
readme += featureDescriptions[f] + '\n';
|
|
1786
1791
|
}
|
|
1787
1792
|
});
|
|
1788
|
-
readme += `
|
|
1789
|
-
|
|
1790
|
-
## Getting Started
|
|
1791
|
-
|
|
1792
|
-
### Prerequisites
|
|
1793
|
-
|
|
1794
|
-
- Node.js 20+
|
|
1795
|
-
- Docker Desktop
|
|
1796
|
-
- npm
|
|
1797
|
-
|
|
1798
|
-
### Quick Start (Recommended)
|
|
1799
|
-
|
|
1800
|
-
**Windows:**
|
|
1801
|
-
\`\`\`bash
|
|
1802
|
-
install.bat
|
|
1803
|
-
\`\`\`
|
|
1804
|
-
|
|
1805
|
-
**Linux/macOS:**
|
|
1806
|
-
\`\`\`bash
|
|
1807
|
-
./install.sh
|
|
1808
|
-
\`\`\`
|
|
1809
|
-
|
|
1810
|
-
The install script will automatically:
|
|
1811
|
-
- Install dependencies
|
|
1812
|
-
- Start Docker containers
|
|
1813
|
-
- Setup and seed the database
|
|
1814
|
-
- Show you the next steps
|
|
1815
|
-
|
|
1816
|
-
### Manual Installation
|
|
1817
|
-
|
|
1818
|
-
If you prefer to run commands manually:
|
|
1819
|
-
|
|
1820
|
-
\`\`\`bash
|
|
1821
|
-
# Install dependencies
|
|
1822
|
-
npm install
|
|
1823
|
-
|
|
1824
|
-
# Start Docker services
|
|
1825
|
-
docker-compose up -d
|
|
1826
|
-
|
|
1827
|
-
# Setup database
|
|
1828
|
-
npm run db:push
|
|
1829
|
-
npm run db:seed
|
|
1830
|
-
|
|
1831
|
-
# Start development server
|
|
1832
|
-
npm run dev
|
|
1833
|
-
\`\`\`
|
|
1834
|
-
|
|
1835
|
-
Open [http://localhost:3001](http://localhost:3001) in your browser.
|
|
1836
|
-
|
|
1837
|
-
${features.includes('auth') ? `### Default Admin Credentials\n\n- Email: admin@example.com\n- Password: admin123\n\n⚠️ Change these in production!` : ''}
|
|
1838
|
-
|
|
1839
|
-
## Available Scripts
|
|
1840
|
-
|
|
1841
|
-
- \`pnpm dev\` - Start development server
|
|
1842
|
-
- \`pnpm build\` - Build for production
|
|
1843
|
-
- \`pnpm start\` - Start production server
|
|
1844
|
-
- \`pnpm lint\` - Run linter
|
|
1845
|
-
- \`pnpm db:push\` - Push database schema
|
|
1846
|
-
- \`pnpm db:seed\` - Seed database
|
|
1847
|
-
- \`pnpm db:studio\` - Open Prisma Studio
|
|
1848
|
-
|
|
1849
|
-
## Tech Stack
|
|
1850
|
-
|
|
1851
|
-
- **Framework**: Next.js 15
|
|
1852
|
-
- **Language**: TypeScript
|
|
1853
|
-
- **Database**: PostgreSQL + Prisma
|
|
1854
|
-
- **Auth**: NextAuth.js
|
|
1855
|
-
- **Styling**: Tailwind CSS
|
|
1856
|
-
- **UI Components**: Radix UI
|
|
1857
|
-
|
|
1858
|
-
## Project Structure
|
|
1859
|
-
|
|
1860
|
-
\`\`\`
|
|
1861
|
-
├── app/ # Next.js app directory
|
|
1862
|
-
├── components/ # React components
|
|
1863
|
-
├── lib/ # Utility functions
|
|
1864
|
-
├── prisma/ # Database schema
|
|
1865
|
-
└── public/ # Static assets
|
|
1866
|
-
\`\`\`
|
|
1867
|
-
|
|
1868
|
-
## Learn More
|
|
1869
|
-
|
|
1870
|
-
- [ChimerAI Documentation](https://chimerai.dev)
|
|
1871
|
-
- [Next.js Documentation](https://nextjs.org/docs)
|
|
1872
|
-
- [Prisma Documentation](https://www.prisma.io/docs)
|
|
1873
|
-
|
|
1874
|
-
## License
|
|
1875
|
-
|
|
1876
|
-
Commercial License - See LICENSE file
|
|
1793
|
+
readme += `
|
|
1794
|
+
|
|
1795
|
+
## Getting Started
|
|
1796
|
+
|
|
1797
|
+
### Prerequisites
|
|
1798
|
+
|
|
1799
|
+
- Node.js 20+
|
|
1800
|
+
- Docker Desktop
|
|
1801
|
+
- npm
|
|
1802
|
+
|
|
1803
|
+
### Quick Start (Recommended)
|
|
1804
|
+
|
|
1805
|
+
**Windows:**
|
|
1806
|
+
\`\`\`bash
|
|
1807
|
+
install.bat
|
|
1808
|
+
\`\`\`
|
|
1809
|
+
|
|
1810
|
+
**Linux/macOS:**
|
|
1811
|
+
\`\`\`bash
|
|
1812
|
+
./install.sh
|
|
1813
|
+
\`\`\`
|
|
1814
|
+
|
|
1815
|
+
The install script will automatically:
|
|
1816
|
+
- Install dependencies
|
|
1817
|
+
- Start Docker containers
|
|
1818
|
+
- Setup and seed the database
|
|
1819
|
+
- Show you the next steps
|
|
1820
|
+
|
|
1821
|
+
### Manual Installation
|
|
1822
|
+
|
|
1823
|
+
If you prefer to run commands manually:
|
|
1824
|
+
|
|
1825
|
+
\`\`\`bash
|
|
1826
|
+
# Install dependencies
|
|
1827
|
+
npm install
|
|
1828
|
+
|
|
1829
|
+
# Start Docker services
|
|
1830
|
+
docker-compose up -d
|
|
1831
|
+
|
|
1832
|
+
# Setup database
|
|
1833
|
+
npm run db:push
|
|
1834
|
+
npm run db:seed
|
|
1835
|
+
|
|
1836
|
+
# Start development server
|
|
1837
|
+
npm run dev
|
|
1838
|
+
\`\`\`
|
|
1839
|
+
|
|
1840
|
+
Open [http://localhost:3001](http://localhost:3001) in your browser.
|
|
1841
|
+
|
|
1842
|
+
${features.includes('auth') ? `### Default Admin Credentials\n\n- Email: admin@example.com\n- Password: admin123\n\n⚠️ Change these in production!` : ''}
|
|
1843
|
+
|
|
1844
|
+
## Available Scripts
|
|
1845
|
+
|
|
1846
|
+
- \`pnpm dev\` - Start development server
|
|
1847
|
+
- \`pnpm build\` - Build for production
|
|
1848
|
+
- \`pnpm start\` - Start production server
|
|
1849
|
+
- \`pnpm lint\` - Run linter
|
|
1850
|
+
- \`pnpm db:push\` - Push database schema
|
|
1851
|
+
- \`pnpm db:seed\` - Seed database
|
|
1852
|
+
- \`pnpm db:studio\` - Open Prisma Studio
|
|
1853
|
+
|
|
1854
|
+
## Tech Stack
|
|
1855
|
+
|
|
1856
|
+
- **Framework**: Next.js 15
|
|
1857
|
+
- **Language**: TypeScript
|
|
1858
|
+
- **Database**: PostgreSQL + Prisma
|
|
1859
|
+
- **Auth**: NextAuth.js
|
|
1860
|
+
- **Styling**: Tailwind CSS
|
|
1861
|
+
- **UI Components**: Radix UI
|
|
1862
|
+
|
|
1863
|
+
## Project Structure
|
|
1864
|
+
|
|
1865
|
+
\`\`\`
|
|
1866
|
+
├── app/ # Next.js app directory
|
|
1867
|
+
├── components/ # React components
|
|
1868
|
+
├── lib/ # Utility functions
|
|
1869
|
+
├── prisma/ # Database schema
|
|
1870
|
+
└── public/ # Static assets
|
|
1871
|
+
\`\`\`
|
|
1872
|
+
|
|
1873
|
+
## Learn More
|
|
1874
|
+
|
|
1875
|
+
- [ChimerAI Documentation](https://chimerai.dev)
|
|
1876
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
1877
|
+
- [Prisma Documentation](https://www.prisma.io/docs)
|
|
1878
|
+
|
|
1879
|
+
## License
|
|
1880
|
+
|
|
1881
|
+
Commercial License - See LICENSE file
|
|
1877
1882
|
`;
|
|
1878
1883
|
await fs_extra_1.default.writeFile(path_1.default.join(targetDir, 'README.md'), readme);
|
|
1879
1884
|
// Create docs directory with inline documentation
|
|
@@ -1881,28 +1886,28 @@ Commercial License - See LICENSE file
|
|
|
1881
1886
|
const docsDir = path_1.default.join(targetDir, 'docs');
|
|
1882
1887
|
await fs_extra_1.default.ensureDir(docsDir);
|
|
1883
1888
|
// Generate inline quickstart guide
|
|
1884
|
-
const quickstart = `# Quick Start
|
|
1885
|
-
|
|
1886
|
-
## Prerequisites
|
|
1887
|
-
- Node.js 20+
|
|
1888
|
-
- Docker Desktop
|
|
1889
|
-
- pnpm (recommended)
|
|
1890
|
-
|
|
1891
|
-
## Setup
|
|
1892
|
-
1. \`pnpm install\`
|
|
1893
|
-
2. \`docker-compose up -d\`
|
|
1894
|
-
3. \`pnpm db:push\`
|
|
1895
|
-
4. \`pnpm db:seed\`
|
|
1896
|
-
5. \`pnpm dev\`
|
|
1897
|
-
|
|
1898
|
-
## Default Login
|
|
1899
|
-
${features.includes('auth') ? '- Email: admin@example.com\n- Password: admin123' : '- No auth configured. Run: `chimerai add auth`'}
|
|
1900
|
-
|
|
1901
|
-
## CLI Commands
|
|
1902
|
-
- \`chimerai add <component>\` — Add features
|
|
1903
|
-
- \`chimerai setup <service>\` — Configure integrations
|
|
1904
|
-
- \`chimerai doctor\` — Health check
|
|
1905
|
-
- \`chimerai update --diff\` — Check for template updates
|
|
1889
|
+
const quickstart = `# Quick Start
|
|
1890
|
+
|
|
1891
|
+
## Prerequisites
|
|
1892
|
+
- Node.js 20+
|
|
1893
|
+
- Docker Desktop
|
|
1894
|
+
- pnpm (recommended)
|
|
1895
|
+
|
|
1896
|
+
## Setup
|
|
1897
|
+
1. \`pnpm install\`
|
|
1898
|
+
2. \`docker-compose up -d\`
|
|
1899
|
+
3. \`pnpm db:push\`
|
|
1900
|
+
4. \`pnpm db:seed\`
|
|
1901
|
+
5. \`pnpm dev\`
|
|
1902
|
+
|
|
1903
|
+
## Default Login
|
|
1904
|
+
${features.includes('auth') ? '- Email: admin@example.com\n- Password: admin123' : '- No auth configured. Run: `chimerai add auth`'}
|
|
1905
|
+
|
|
1906
|
+
## CLI Commands
|
|
1907
|
+
- \`chimerai add <component>\` — Add features
|
|
1908
|
+
- \`chimerai setup <service>\` — Configure integrations
|
|
1909
|
+
- \`chimerai doctor\` — Health check
|
|
1910
|
+
- \`chimerai update --diff\` — Check for template updates
|
|
1906
1911
|
`;
|
|
1907
1912
|
await fs_extra_1.default.writeFile(path_1.default.join(docsDir, 'QUICKSTART.md'), quickstart);
|
|
1908
1913
|
console.log(chalk_1.default.green(' ✓ docs/QUICKSTART.md'));
|