@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.
@@ -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'));