@chimerai/cli 0.2.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +293 -0
  3. package/dist/cli.d.ts +7 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +317 -0
  6. package/dist/commands/add.d.ts +11 -0
  7. package/dist/commands/add.d.ts.map +1 -0
  8. package/dist/commands/add.js +2126 -0
  9. package/dist/commands/create.d.ts +12 -0
  10. package/dist/commands/create.d.ts.map +1 -0
  11. package/dist/commands/create.js +1703 -0
  12. package/dist/commands/deploy.d.ts +11 -0
  13. package/dist/commands/deploy.d.ts.map +1 -0
  14. package/dist/commands/deploy.js +219 -0
  15. package/dist/commands/dev.d.ts +17 -0
  16. package/dist/commands/dev.d.ts.map +1 -0
  17. package/dist/commands/dev.js +206 -0
  18. package/dist/commands/doctor.d.ts +11 -0
  19. package/dist/commands/doctor.d.ts.map +1 -0
  20. package/dist/commands/doctor.js +728 -0
  21. package/dist/commands/generate.d.ts +19 -0
  22. package/dist/commands/generate.d.ts.map +1 -0
  23. package/dist/commands/generate.js +429 -0
  24. package/dist/commands/init.d.ts +11 -0
  25. package/dist/commands/init.d.ts.map +1 -0
  26. package/dist/commands/init.js +269 -0
  27. package/dist/commands/list.d.ts +12 -0
  28. package/dist/commands/list.d.ts.map +1 -0
  29. package/dist/commands/list.js +328 -0
  30. package/dist/commands/migrate.d.ts +14 -0
  31. package/dist/commands/migrate.d.ts.map +1 -0
  32. package/dist/commands/migrate.js +197 -0
  33. package/dist/commands/plugin.d.ts +10 -0
  34. package/dist/commands/plugin.d.ts.map +1 -0
  35. package/dist/commands/plugin.js +239 -0
  36. package/dist/commands/remove.d.ts +11 -0
  37. package/dist/commands/remove.d.ts.map +1 -0
  38. package/dist/commands/remove.js +472 -0
  39. package/dist/commands/secret.d.ts +12 -0
  40. package/dist/commands/secret.d.ts.map +1 -0
  41. package/dist/commands/secret.js +102 -0
  42. package/dist/commands/setup.d.ts +9 -0
  43. package/dist/commands/setup.d.ts.map +1 -0
  44. package/dist/commands/setup.js +788 -0
  45. package/dist/commands/update.d.ts +14 -0
  46. package/dist/commands/update.d.ts.map +1 -0
  47. package/dist/commands/update.js +211 -0
  48. package/dist/commands/use.d.ts +9 -0
  49. package/dist/commands/use.d.ts.map +1 -0
  50. package/dist/commands/use.js +51 -0
  51. package/dist/index.d.ts +22 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +45 -0
  54. package/dist/license.d.ts +55 -0
  55. package/dist/license.d.ts.map +1 -0
  56. package/dist/license.js +258 -0
  57. package/dist/scanner.d.ts +31 -0
  58. package/dist/scanner.d.ts.map +1 -0
  59. package/dist/scanner.js +113 -0
  60. package/dist/schema-manager.d.ts +26 -0
  61. package/dist/schema-manager.d.ts.map +1 -0
  62. package/dist/schema-manager.js +132 -0
  63. package/dist/templates/admin.d.ts +49 -0
  64. package/dist/templates/admin.d.ts.map +1 -0
  65. package/dist/templates/admin.js +1358 -0
  66. package/dist/templates/ai-routes.d.ts +17 -0
  67. package/dist/templates/ai-routes.d.ts.map +1 -0
  68. package/dist/templates/ai-routes.js +1130 -0
  69. package/dist/templates/ai-service-tools.d.ts +22 -0
  70. package/dist/templates/ai-service-tools.d.ts.map +1 -0
  71. package/dist/templates/ai-service-tools.js +1424 -0
  72. package/dist/templates/ai-service.d.ts +66 -0
  73. package/dist/templates/ai-service.d.ts.map +1 -0
  74. package/dist/templates/ai-service.js +2202 -0
  75. package/dist/templates/api-routes.d.ts +108 -0
  76. package/dist/templates/api-routes.d.ts.map +1 -0
  77. package/dist/templates/api-routes.js +1219 -0
  78. package/dist/templates/auth.d.ts +48 -0
  79. package/dist/templates/auth.d.ts.map +1 -0
  80. package/dist/templates/auth.js +381 -0
  81. package/dist/templates/billing.d.ts +44 -0
  82. package/dist/templates/billing.d.ts.map +1 -0
  83. package/dist/templates/billing.js +551 -0
  84. package/dist/templates/chat.d.ts +63 -0
  85. package/dist/templates/chat.d.ts.map +1 -0
  86. package/dist/templates/chat.js +1979 -0
  87. package/dist/templates/components.d.ts +22 -0
  88. package/dist/templates/components.d.ts.map +1 -0
  89. package/dist/templates/components.js +672 -0
  90. package/dist/templates/config.d.ts +6 -0
  91. package/dist/templates/config.d.ts.map +1 -0
  92. package/dist/templates/config.js +86 -0
  93. package/dist/templates/docker.d.ts +25 -0
  94. package/dist/templates/docker.d.ts.map +1 -0
  95. package/dist/templates/docker.js +165 -0
  96. package/dist/templates/gdpr.d.ts +16 -0
  97. package/dist/templates/gdpr.d.ts.map +1 -0
  98. package/dist/templates/gdpr.js +259 -0
  99. package/dist/templates/index.d.ts +77 -0
  100. package/dist/templates/index.d.ts.map +1 -0
  101. package/dist/templates/index.js +339 -0
  102. package/dist/templates/layout.d.ts +67 -0
  103. package/dist/templates/layout.d.ts.map +1 -0
  104. package/dist/templates/layout.js +670 -0
  105. package/dist/templates/mfa.d.ts +23 -0
  106. package/dist/templates/mfa.d.ts.map +1 -0
  107. package/dist/templates/mfa.js +353 -0
  108. package/dist/templates/middleware.d.ts +12 -0
  109. package/dist/templates/middleware.d.ts.map +1 -0
  110. package/dist/templates/middleware.js +116 -0
  111. package/dist/templates/prisma.d.ts +35 -0
  112. package/dist/templates/prisma.d.ts.map +1 -0
  113. package/dist/templates/prisma.js +724 -0
  114. package/dist/templates/provider-routes.d.ts +21 -0
  115. package/dist/templates/provider-routes.d.ts.map +1 -0
  116. package/dist/templates/provider-routes.js +1203 -0
  117. package/dist/templates/rag.d.ts +48 -0
  118. package/dist/templates/rag.d.ts.map +1 -0
  119. package/dist/templates/rag.js +532 -0
  120. package/dist/templates/widget.d.ts +64 -0
  121. package/dist/templates/widget.d.ts.map +1 -0
  122. package/dist/templates/widget.js +1360 -0
  123. package/dist/utils/provider-db.d.ts +63 -0
  124. package/dist/utils/provider-db.d.ts.map +1 -0
  125. package/dist/utils/provider-db.js +300 -0
  126. package/dist/utils.d.ts +78 -0
  127. package/dist/utils.d.ts.map +1 -0
  128. package/dist/utils.js +330 -0
  129. package/package.json +60 -0
@@ -0,0 +1,724 @@
1
+ "use strict";
2
+ /**
3
+ * Prisma and library templates
4
+ * Generates Prisma client initialization, encryption utilities, and API key auth
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.generatePrismaLib = generatePrismaLib;
8
+ exports.generateEncryptionLib = generateEncryptionLib;
9
+ exports.generateApiKeyAuthLib = generateApiKeyAuthLib;
10
+ exports.generateApiProtectionLib = generateApiProtectionLib;
11
+ exports.generatePrismaSchema = generatePrismaSchema;
12
+ /**
13
+ * Generates the Prisma client singleton
14
+ * Uses global singleton pattern to prevent connection exhaustion in serverless environments
15
+ * @returns TypeScript content for lib/prisma.ts
16
+ */
17
+ function generatePrismaLib() {
18
+ return `// @chimerai component=PrismaLib version=1.0
19
+ import { PrismaClient } from '@prisma/client';
20
+
21
+ const globalForPrisma = globalThis as unknown as {
22
+ prisma: PrismaClient | undefined;
23
+ };
24
+
25
+ export const prisma = globalForPrisma.prisma ?? new PrismaClient();
26
+
27
+ if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma;
28
+ `;
29
+ }
30
+ /**
31
+ * Generates the encryption utility for API key storage
32
+ * Uses AES-256-GCM for authenticated symmetric encryption
33
+ * @returns TypeScript content for lib/encryption.ts
34
+ */
35
+ function generateEncryptionLib() {
36
+ return `// @chimerai component=EncryptionLib version=1.0
37
+ import crypto from 'crypto';
38
+
39
+ const ALGORITHM = 'aes-256-gcm';
40
+ const IV_LENGTH = 16; // 128 bits — must match CLI and @chimerai/model-providers
41
+ const AUTH_TAG_LENGTH = 16;
42
+
43
+ function getKey(): Buffer {
44
+ const key = process.env.PROVIDER_ENCRYPTION_KEY;
45
+ if (!key) {
46
+ throw new Error('PROVIDER_ENCRYPTION_KEY environment variable is required');
47
+ }
48
+ // If key is hex (64 chars = 32 bytes), use as-is; otherwise hash it
49
+ if (key.length === 64 && /^[0-9a-fA-F]+$/.test(key)) {
50
+ return Buffer.from(key, 'hex');
51
+ }
52
+ return crypto.createHash('sha256').update(key).digest();
53
+ }
54
+
55
+ export function encrypt(text: string): string {
56
+ if (!text) return '';
57
+ const key = getKey();
58
+ const iv = crypto.randomBytes(IV_LENGTH);
59
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
60
+ let encrypted = cipher.update(text, 'utf8', 'base64');
61
+ encrypted += cipher.final('base64');
62
+ const authTag = cipher.getAuthTag();
63
+ return iv.toString('base64') + ':' + authTag.toString('base64') + ':' + encrypted;
64
+ }
65
+
66
+ export function decrypt(text: string): string {
67
+ if (!text) return '';
68
+ const key = getKey();
69
+ const parts = text.split(':');
70
+ if (parts.length !== 3) {
71
+ throw new Error('Invalid encrypted text format');
72
+ }
73
+ const iv = Buffer.from(parts[0], 'base64');
74
+ const authTag = Buffer.from(parts[1], 'base64');
75
+ const encryptedText = parts[2];
76
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv, { authTagLength: AUTH_TAG_LENGTH });
77
+ decipher.setAuthTag(authTag);
78
+ let decrypted = decipher.update(encryptedText, 'base64', 'utf8');
79
+ decrypted += decipher.final('utf8');
80
+ return decrypted;
81
+ }
82
+
83
+ /**
84
+ * Encrypt an API key for storage (alias for encrypt)
85
+ */
86
+ export function encryptApiKey(apiKey: string): string {
87
+ return encrypt(apiKey);
88
+ }
89
+
90
+ /**
91
+ * Decrypt an API key from storage (alias for decrypt)
92
+ */
93
+ export function decryptApiKey(encryptedKey: string): string {
94
+ return decrypt(encryptedKey);
95
+ }
96
+ `;
97
+ }
98
+ /**
99
+ * Generates the API key authentication middleware
100
+ * Supports API key-based auth for standalone widgets and external integrations
101
+ * @returns TypeScript content for lib/api-key-auth.ts
102
+ */
103
+ function generateApiKeyAuthLib() {
104
+ return `// @chimerai component=ApiKeyAuthLib version=1.1
105
+ /**
106
+ * API Key Authentication Middleware for ChimerAI
107
+ */
108
+
109
+ import { NextRequest } from 'next/server';
110
+ import { prisma } from './prisma';
111
+ import { createHash } from 'crypto';
112
+
113
+ export interface ApiKeyResult {
114
+ valid: boolean;
115
+ userId?: string;
116
+ email?: string;
117
+ error?: string;
118
+ keyName?: string;
119
+ scopes?: string[];
120
+ }
121
+
122
+ export function hashApiKey(apiKey: string): string {
123
+ return createHash('sha256').update(apiKey).digest('hex');
124
+ }
125
+
126
+ export function generateApiKey(): string {
127
+ const randomBytes = createHash('sha256')
128
+ .update(Math.random().toString() + Date.now().toString())
129
+ .digest('hex')
130
+ .slice(0, 32);
131
+
132
+ return \`sk_live_\${randomBytes}\`;
133
+ }
134
+
135
+ /**
136
+ * Checks if the given scopes include the required scope.
137
+ * Supports wildcards: ['*'] matches everything, ['chat:*'] matches 'chat:send'.
138
+ * Empty scopes array = unrestricted (backward-compatible).
139
+ */
140
+ export function hasScope(scopes: string[], required: string): boolean {
141
+ if (scopes.length === 0) return true; // No scope = everything allowed
142
+ if (scopes.includes('*')) return true; // Super-wildcard
143
+ if (scopes.includes(required)) return true; // Exact match
144
+
145
+ // Category wildcard: 'chat:*' matches 'chat:send'
146
+ const requiredParts = required.split(':');
147
+ for (const scope of scopes) {
148
+ if (scope.endsWith(':*')) {
149
+ const prefix = scope.slice(0, -1);
150
+ if (required.startsWith(prefix)) return true;
151
+ }
152
+ }
153
+
154
+ return false;
155
+ }
156
+
157
+ export async function verifyApiKey(request: NextRequest): Promise<ApiKeyResult> {
158
+ const authHeader = request.headers.get('authorization');
159
+ const apiKeyHeader = request.headers.get('x-api-key');
160
+
161
+ let apiKey: string | null = null;
162
+
163
+ if (authHeader?.startsWith('Bearer ')) {
164
+ apiKey = authHeader.slice(7);
165
+ } else if (apiKeyHeader) {
166
+ apiKey = apiKeyHeader;
167
+ }
168
+
169
+ if (!apiKey) {
170
+ return {
171
+ valid: false,
172
+ error: 'No API key provided',
173
+ };
174
+ }
175
+
176
+ if (!apiKey.startsWith('sk_live_') && !apiKey.startsWith('sk_test_')) {
177
+ return {
178
+ valid: false,
179
+ error: 'Invalid API key format',
180
+ };
181
+ }
182
+
183
+ try {
184
+ const keyHash = hashApiKey(apiKey);
185
+ const dbKey = await (prisma as any).apiKey.findUnique({
186
+ where: { keyHash },
187
+ include: {
188
+ user: {
189
+ select: {
190
+ id: true,
191
+ email: true,
192
+ name: true,
193
+ },
194
+ },
195
+ },
196
+ });
197
+
198
+ if (!dbKey) {
199
+ return {
200
+ valid: false,
201
+ error: 'Invalid API key',
202
+ };
203
+ }
204
+
205
+ if (dbKey.revoked) {
206
+ return {
207
+ valid: false,
208
+ error: 'API key has been revoked',
209
+ };
210
+ }
211
+
212
+ // Check expiry
213
+ if (dbKey.expiresAt && new Date(dbKey.expiresAt) < new Date()) {
214
+ return {
215
+ valid: false,
216
+ error: 'API key has expired',
217
+ };
218
+ }
219
+
220
+ // Update last used timestamp
221
+ await (prisma as any).apiKey.update({
222
+ where: { keyHash },
223
+ data: { lastUsedAt: new Date() },
224
+ });
225
+
226
+ // Parse scopes: stored as comma-separated string in SQLite
227
+ const rawScopes = dbKey.scopes || '';
228
+ const scopeList = typeof rawScopes === 'string'
229
+ ? (rawScopes ? rawScopes.split(',').map((s: string) => s.trim()) : [])
230
+ : rawScopes;
231
+
232
+ return {
233
+ valid: true,
234
+ userId: dbKey.user.id,
235
+ email: dbKey.user.email || '',
236
+ keyName: dbKey.name,
237
+ scopes: scopeList,
238
+ };
239
+ } catch (error) {
240
+ console.error('API key verification error:', error);
241
+ return {
242
+ valid: false,
243
+ error: 'API key verification failed',
244
+ };
245
+ }
246
+ }
247
+ `;
248
+ }
249
+ /**
250
+ * Generates the API protection middleware
251
+ * Handles session/auth checks, permission verification, and usage tracking
252
+ * @returns TypeScript content for lib/api-protection.ts
253
+ */
254
+ function generateApiProtectionLib() {
255
+ return `// @chimerai component=ApiProtectionLib version=1.0
256
+ /**
257
+ * API Protection & Authorization Utilities
258
+ */
259
+
260
+ import { NextRequest, NextResponse } from 'next/server';
261
+ import { getServerSession } from 'next-auth';
262
+ import { authOptions } from './auth';
263
+ import { prisma } from './prisma';
264
+
265
+ export interface AuthResult {
266
+ authorized: boolean;
267
+ user?: { id: string; email: string };
268
+ error?: { code: string; message: string; statusCode: number };
269
+ }
270
+
271
+ export async function requireAuth(request: NextRequest): Promise<AuthResult> {
272
+ const session = await getServerSession(authOptions);
273
+
274
+ if (!session?.user?.id) {
275
+ return {
276
+ authorized: false,
277
+ error: {
278
+ code: 'UNAUTHORIZED',
279
+ message: 'Authentication required',
280
+ statusCode: 401,
281
+ },
282
+ };
283
+ }
284
+
285
+ return {
286
+ authorized: true,
287
+ user: {
288
+ id: session.user.id as string,
289
+ email: session.user.email as string,
290
+ },
291
+ };
292
+ }
293
+
294
+ export async function requireModelPermission(
295
+ request: NextRequest,
296
+ model: string
297
+ ): Promise<AuthResult> {
298
+ const session = await getServerSession(authOptions);
299
+
300
+ if (!session?.user?.id) {
301
+ return {
302
+ authorized: false,
303
+ error: {
304
+ code: 'UNAUTHORIZED',
305
+ message: 'Authentication required',
306
+ statusCode: 401,
307
+ },
308
+ };
309
+ }
310
+
311
+ // Check if user has access to this model
312
+ try {
313
+ const access = await (prisma as any).modelAccess.findFirst({
314
+ where: {
315
+ userId: session.user.id,
316
+ model,
317
+ enabled: true,
318
+ },
319
+ });
320
+
321
+ if (!access) {
322
+ return {
323
+ authorized: false,
324
+ error: {
325
+ code: 'FORBIDDEN',
326
+ message: \`Access to model '\${model}' is not allowed\`,
327
+ statusCode: 403,
328
+ },
329
+ };
330
+ }
331
+ } catch (error) {
332
+ console.error('Permission check error:', error);
333
+ }
334
+
335
+ return {
336
+ authorized: true,
337
+ user: {
338
+ id: session.user.id as string,
339
+ email: session.user.email as string,
340
+ },
341
+ };
342
+ }
343
+
344
+ export async function requireCredits(
345
+ request: NextRequest,
346
+ estimatedTokens: number
347
+ ): Promise<AuthResult> {
348
+ const session = await getServerSession(authOptions);
349
+
350
+ if (!session?.user?.id) {
351
+ return {
352
+ authorized: false,
353
+ error: {
354
+ code: 'UNAUTHORIZED',
355
+ message: 'Authentication required',
356
+ statusCode: 401,
357
+ },
358
+ };
359
+ }
360
+
361
+ try {
362
+ const user = await (prisma as any).user.findUnique({
363
+ where: { id: session.user.id },
364
+ select: { credits: true },
365
+ });
366
+
367
+ const requiredCredits = Math.ceil(estimatedTokens / 100);
368
+
369
+ if (!user || user.credits < requiredCredits) {
370
+ return {
371
+ authorized: false,
372
+ error: {
373
+ code: 'INSUFFICIENT_CREDITS',
374
+ message: 'Insufficient credits for this operation',
375
+ statusCode: 402,
376
+ },
377
+ };
378
+ }
379
+ } catch (error) {
380
+ console.error('Credits check error:', error);
381
+ }
382
+
383
+ return {
384
+ authorized: true,
385
+ user: {
386
+ id: session.user.id as string,
387
+ email: session.user.email as string,
388
+ },
389
+ };
390
+ }
391
+
392
+ export function createErrorResponse(authResult: AuthResult) {
393
+ return NextResponse.json(
394
+ {
395
+ error: {
396
+ code: authResult.error?.code || 'ERROR',
397
+ message: authResult.error?.message || 'Unknown error',
398
+ },
399
+ },
400
+ { status: authResult.error?.statusCode || 500 }
401
+ );
402
+ }
403
+
404
+ export async function trackApiUsage(
405
+ userId: string,
406
+ endpoint: string,
407
+ model: string,
408
+ inputTokens: number,
409
+ outputTokens: number,
410
+ success: boolean,
411
+ errorMessage?: string
412
+ ) {
413
+ try {
414
+ await (prisma as any).apiUsageLog.create({
415
+ data: {
416
+ userId,
417
+ endpoint,
418
+ model,
419
+ inputTokens,
420
+ outputTokens,
421
+ success,
422
+ errorMessage,
423
+ timestamp: new Date(),
424
+ },
425
+ });
426
+ } catch (error) {
427
+ console.error('Failed to track API usage:', error);
428
+ }
429
+ }
430
+ `;
431
+ }
432
+ /**
433
+ * Generates the Prisma schema for all features
434
+ * Includes User, Role, Provider, Model, Conversation, Message, AuditLog, and SystemSetting models
435
+ * @returns TypeScript content for prisma/schema.prisma (content)
436
+ */
437
+ function generatePrismaSchema() {
438
+ return `// @chimerai component=PrismaSchema version=2.0
439
+ generator client {
440
+ provider = "prisma-client-js"
441
+ }
442
+
443
+ datasource db {
444
+ provider = "postgresql"
445
+ url = env("DATABASE_URL")
446
+ }
447
+
448
+ model User {
449
+ id String @id @default(cuid())
450
+ name String?
451
+ email String? @unique
452
+ emailVerified DateTime?
453
+ image String?
454
+ password String?
455
+ createdAt DateTime @default(now())
456
+ updatedAt DateTime @updatedAt
457
+
458
+ accounts Account[]
459
+ sessions Session[]
460
+ roles UserRole[]
461
+ providers Provider[] @relation("CreatedProviders")
462
+ apiUsage ApiUsage[]
463
+ apiKeys ApiKey[]
464
+ modelAccess ModelAccess[]
465
+ auditLogs AuditLog[]
466
+ conversations Conversation[]
467
+ }
468
+
469
+ model Account {
470
+ id String @id @default(cuid())
471
+ userId String
472
+ type String
473
+ provider String
474
+ providerAccountId String
475
+ refresh_token String? @db.Text
476
+ access_token String? @db.Text
477
+ expires_at Int?
478
+ token_type String?
479
+ scope String?
480
+ id_token String? @db.Text
481
+ session_state String?
482
+
483
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
484
+
485
+ @@unique([provider, providerAccountId])
486
+ }
487
+
488
+ model Session {
489
+ id String @id @default(cuid())
490
+ sessionToken String @unique
491
+ userId String
492
+ expires DateTime
493
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
494
+ }
495
+
496
+ model VerificationToken {
497
+ identifier String
498
+ token String @unique
499
+ expires DateTime
500
+
501
+ @@unique([identifier, token])
502
+ }
503
+
504
+ model ApiKey {
505
+ id String @id @default(cuid())
506
+ name String
507
+ keyHash String @unique
508
+ userId String
509
+ scopes String[] @default([])
510
+ revoked Boolean @default(false)
511
+ lastUsedAt DateTime?
512
+ expiresAt DateTime?
513
+ createdAt DateTime @default(now())
514
+ updatedAt DateTime @updatedAt
515
+
516
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
517
+
518
+ @@index([userId])
519
+ }
520
+
521
+ model Role {
522
+ id String @id @default(cuid())
523
+ name String @unique
524
+ description String?
525
+ permissions String[]
526
+ createdAt DateTime @default(now())
527
+ updatedAt DateTime @updatedAt
528
+
529
+ users UserRole[]
530
+ }
531
+
532
+ model UserRole {
533
+ userId String
534
+ roleId String
535
+
536
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
537
+ role Role @relation(fields: [roleId], references: [id], onDelete: Cascade)
538
+
539
+ @@id([userId, roleId])
540
+ }
541
+
542
+ // === Provider Management (Core Infrastructure) ===
543
+
544
+ model Provider {
545
+ id String @id @default(cuid())
546
+ name String
547
+ type String // "openai", "anthropic", "ollama", "google", "custom"
548
+ description String?
549
+ baseUrl String?
550
+ apiKey String? @db.Text // AES-256-GCM encrypted, null for keyless providers (e.g. Ollama)
551
+ config Json @default("{}")
552
+ status String @default("active")
553
+ isDefault Boolean @default(false)
554
+ priority Int @default(0)
555
+ tags String[]
556
+ createdAt DateTime @default(now())
557
+ updatedAt DateTime @updatedAt
558
+ createdBy String?
559
+
560
+ models Model[]
561
+ health ProviderHealth?
562
+ apiUsage ApiUsage[]
563
+ conversations Conversation[]
564
+ creator User? @relation("CreatedProviders", fields: [createdBy], references: [id])
565
+
566
+ @@index([type])
567
+ @@index([status])
568
+ }
569
+
570
+ model Model {
571
+ id String @id @default(cuid())
572
+ providerId String
573
+ modelId String // e.g. "gpt-4", "claude-3-sonnet"
574
+ name String
575
+ description String?
576
+ capabilities String[] // ["chat", "embedding", "image", "vision"]
577
+ contextWindow Int @default(4096)
578
+ maxOutputTokens Int?
579
+ inputCost Float @default(0) // $ per 1M tokens
580
+ outputCost Float @default(0)
581
+ isAvailable Boolean @default(true)
582
+ isDeprecated Boolean @default(false)
583
+
584
+ provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
585
+
586
+ @@unique([providerId, modelId])
587
+ @@index([providerId])
588
+ }
589
+
590
+ model ProviderHealth {
591
+ id String @id @default(cuid())
592
+ providerId String @unique
593
+ status String @default("unknown") // "healthy", "degraded", "unhealthy"
594
+ responseTime Int? // ms
595
+ lastCheck DateTime @default(now())
596
+ errorMessage String?
597
+ modelsAvailable Int @default(0)
598
+ chatAvailable Boolean @default(false)
599
+ embeddingAvailable Boolean @default(false)
600
+ apiKeyValid Boolean @default(false)
601
+
602
+ provider Provider @relation(fields: [providerId], references: [id], onDelete: Cascade)
603
+ }
604
+
605
+ model ApiUsage {
606
+ id String @id @default(cuid())
607
+ userId String
608
+ providerId String?
609
+ model String
610
+ endpoint String
611
+ promptTokens Int @default(0)
612
+ completionTokens Int @default(0)
613
+ totalTokens Int @default(0)
614
+ tokensUsed Int @default(0)
615
+ creditsUsed Int @default(0)
616
+ cost Float @default(0)
617
+ success Boolean @default(true)
618
+ errorMessage String?
619
+ responseTime Int @default(0) // ms
620
+ createdAt DateTime @default(now())
621
+
622
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
623
+ provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
624
+
625
+ @@index([userId])
626
+ @@index([providerId])
627
+ @@index([createdAt])
628
+ }
629
+
630
+ model ModelAccess {
631
+ id String @id @default(cuid())
632
+ userId String
633
+ modelId String
634
+ granted Boolean @default(true)
635
+ createdAt DateTime @default(now())
636
+ updatedAt DateTime @updatedAt
637
+
638
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
639
+
640
+ @@unique([userId, modelId])
641
+ @@index([userId])
642
+ @@index([modelId])
643
+ }
644
+
645
+ model AuditLog {
646
+ id String @id @default(cuid())
647
+ action String
648
+ userId String
649
+ targetType String?
650
+ targetId String?
651
+ metadata Json?
652
+ ipAddress String?
653
+ createdAt DateTime @default(now())
654
+
655
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
656
+
657
+ @@index([userId])
658
+ @@index([action])
659
+ @@index([createdAt])
660
+ }
661
+
662
+ model PromptTemplate {
663
+ id String @id @default(cuid())
664
+ name String @unique
665
+ category String
666
+ description String?
667
+ content String @db.Text
668
+ variables String[]
669
+ language String @default("en")
670
+ version Int @default(1)
671
+ isActive Boolean @default(true)
672
+ isDefault Boolean @default(false)
673
+ tags String[]
674
+ metadata Json?
675
+ createdBy String?
676
+ createdAt DateTime @default(now())
677
+ updatedAt DateTime @updatedAt
678
+
679
+ @@index([category])
680
+ @@index([isActive])
681
+ @@index([isDefault])
682
+ }
683
+
684
+ model Conversation {
685
+ id String @id @default(cuid())
686
+ userId String
687
+ title String @default("New Chat")
688
+ model String?
689
+ providerId String?
690
+ metadata Json?
691
+ archived Boolean @default(false)
692
+ createdAt DateTime @default(now())
693
+ updatedAt DateTime @updatedAt
694
+
695
+ user User @relation(fields: [userId], references: [id], onDelete: Cascade)
696
+ provider Provider? @relation(fields: [providerId], references: [id], onDelete: SetNull)
697
+ messages Message[]
698
+
699
+ @@index([userId])
700
+ @@index([archived])
701
+ @@index([providerId])
702
+ }
703
+
704
+ model Message {
705
+ id String @id @default(cuid())
706
+ conversationId String
707
+ role String
708
+ content String @db.Text
709
+ model String?
710
+ tokens Int?
711
+ createdAt DateTime @default(now())
712
+
713
+ conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
714
+
715
+ @@index([conversationId])
716
+ }
717
+
718
+ model SystemSetting {
719
+ key String @id
720
+ value String @db.Text
721
+ updatedAt DateTime @updatedAt
722
+ }
723
+ `;
724
+ }