@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,670 @@
1
+ "use strict";
2
+ /**
3
+ * Layout component templates for Next.js applications
4
+ * Generates root layouts with optional session providers and page routing
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.generateAppLayout = generateAppLayout;
8
+ exports.generateAppLayoutWithAuth = generateAppLayoutWithAuth;
9
+ exports.generateAppPage = generateAppPage;
10
+ exports.generateAuthBasicLayout = generateAuthBasicLayout;
11
+ exports.generateDashboardLayout = generateDashboardLayout;
12
+ exports.generateDashboardPage = generateDashboardPage;
13
+ exports.generateProfilePage = generateProfilePage;
14
+ exports.generateUserProfileRoute = generateUserProfileRoute;
15
+ exports.generateSettingsPage = generateSettingsPage;
16
+ exports.generateUseAppNameHook = generateUseAppNameHook;
17
+ /**
18
+ * Generates the root layout component for apps without auth
19
+ * Used as the base layout template when no session provider is needed
20
+ * @param projectName - Project name for page title
21
+ * @returns TypeScript/JSX content for app/layout.tsx
22
+ */
23
+ function generateAppLayout(projectName = 'ChimerAI App') {
24
+ return `// @chimerai component=AppLayout version=1.0
25
+ import './globals.css';
26
+ import type { Metadata } from 'next';
27
+ import { Inter } from 'next/font/google';
28
+
29
+ const inter = Inter({ subsets: ['latin'] });
30
+
31
+ export const metadata: Metadata = {
32
+ title: '${projectName}',
33
+ description: 'Built with ChimerAI Kickstart',
34
+ };
35
+
36
+ export default function RootLayout({
37
+ children,
38
+ }: {
39
+ children: React.ReactNode;
40
+ }) {
41
+ return (
42
+ <html lang="en">
43
+ <body className={inter.className}>{children}</body>
44
+ </html>
45
+ );
46
+ }
47
+ `;
48
+ }
49
+ /**
50
+ * Generates the root layout component for apps with auth/session provider
51
+ * Wraps the app with SessionProvider for NextAuth integration
52
+ * @param projectName - Project name for page title
53
+ * @returns TypeScript/JSX content for app/layout.tsx with SessionProvider
54
+ */
55
+ function generateAppLayoutWithAuth(projectName = 'ChimerAI App') {
56
+ return `// @chimerai component=AppLayoutWithAuth version=1.0
57
+ import type { Metadata } from 'next';
58
+ import { Inter } from 'next/font/google';
59
+ import './globals.css';
60
+ import { SessionProvider } from '@/components/SessionProvider';
61
+ import { ThemeProvider } from 'next-themes';
62
+ import { Toaster } from 'sonner';
63
+
64
+ const inter = Inter({ subsets: ['latin'] });
65
+
66
+ export const metadata: Metadata = {
67
+ title: '${projectName}',
68
+ description: 'AI Chat Platform powered by ChimerAI',
69
+ };
70
+
71
+ export default function RootLayout({
72
+ children,
73
+ }: {
74
+ children: React.ReactNode;
75
+ }) {
76
+ return (
77
+ <html lang="en" suppressHydrationWarning>
78
+ <body className={inter.className}>
79
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
80
+ <SessionProvider>
81
+ {children}
82
+ </SessionProvider>
83
+ <Toaster richColors position="top-right" />
84
+ </ThemeProvider>
85
+ </body>
86
+ </html>
87
+ );
88
+ }
89
+ `;
90
+ }
91
+ /**
92
+ * Generates the home page component that handles session-based routing
93
+ * Redirects unauthenticated users to /login, authenticated users to /dashboard
94
+ * Includes loading state with spinner animation
95
+ * @returns TypeScript/JSX content for app/page.tsx
96
+ */
97
+ function generateAppPage() {
98
+ return `// @chimerai component=AppPage version=1.0
99
+ 'use client';
100
+
101
+ import { useSession } from 'next-auth/react';
102
+ import { useRouter } from 'next/navigation';
103
+ import { useEffect } from 'react';
104
+
105
+ export default function Home() {
106
+ const { data: session, status } = useSession();
107
+ const router = useRouter();
108
+
109
+ useEffect(() => {
110
+ if (status === 'loading') return;
111
+
112
+ if (!session) {
113
+ router.push('/auth/signin');
114
+ } else {
115
+ router.push('/dashboard');
116
+ }
117
+ }, [session, status, router]);
118
+
119
+ if (status === 'loading') {
120
+ return (
121
+ <main className="flex min-h-screen flex-col items-center justify-center p-24">
122
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
123
+ </main>
124
+ );
125
+ }
126
+
127
+ return null;
128
+ }
129
+ `;
130
+ }
131
+ /**
132
+ * Generates a basic root layout for auth routes (login, register)
133
+ * Used for layouts that don't need session providers (public routes)
134
+ * @returns TypeScript/JSX content for app/(auth)/layout.tsx
135
+ */
136
+ function generateAuthBasicLayout() {
137
+ return `// @chimerai component=AuthBasicLayout version=1.0
138
+ import type { Metadata } from 'next';
139
+ import { Inter } from 'next/font/google';
140
+ import '../globals.css';
141
+
142
+ const inter = Inter({ subsets: ['latin'] });
143
+
144
+ export const metadata: Metadata = {
145
+ title: 'Authentication',
146
+ description: 'Sign in to your account',
147
+ };
148
+
149
+ export default function AuthLayout({
150
+ children,
151
+ }: {
152
+ children: React.ReactNode;
153
+ }) {
154
+ return (
155
+ <html lang="en">
156
+ <body className={inter.className}>
157
+ {children}
158
+ </body>
159
+ </html>
160
+ );
161
+ }
162
+ `;
163
+ }
164
+ /**
165
+ * Generates a dashboard layout with sidebar and main content area
166
+ * Provides structure for authenticated application pages
167
+ * @returns TypeScript/JSX content for app/(dashboard)/layout.tsx
168
+ */
169
+ function generateDashboardLayout() {
170
+ return `// @chimerai component=DashboardLayout version=1.2
171
+ 'use client';
172
+
173
+ import { useSession, signOut } from 'next-auth/react';
174
+ import { redirect } from 'next/navigation';
175
+ import { useEffect, useState } from 'react';
176
+ import Link from 'next/link';
177
+ import { useAppName } from '@/lib/use-app-name';
178
+
179
+ export default function DashboardLayout({
180
+ children,
181
+ }: {
182
+ children: React.ReactNode;
183
+ }) {
184
+ const { data: session, status } = useSession();
185
+ const [isLoading, setIsLoading] = useState(true);
186
+ const [sidebarOpen, setSidebarOpen] = useState(false);
187
+ const appName = useAppName();
188
+
189
+ useEffect(() => {
190
+ if (status === 'loading') return;
191
+ if (!session) {
192
+ redirect('/auth/signin');
193
+ }
194
+ setIsLoading(false);
195
+ }, [session, status]);
196
+
197
+ useEffect(() => {
198
+ document.title = \`Dashboard — \${appName}\`;
199
+ }, [appName]);
200
+
201
+ if (isLoading || status === 'loading') {
202
+ return (
203
+ <div className="flex min-h-screen items-center justify-center">
204
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
205
+ </div>
206
+ );
207
+ }
208
+
209
+ return (
210
+ <div className="flex min-h-screen bg-white dark:bg-gray-900">
211
+ {/* Mobile hamburger button */}
212
+ <button
213
+ onClick={() => setSidebarOpen(!sidebarOpen)}
214
+ className="fixed top-4 left-4 z-50 md:hidden p-2 rounded-lg bg-gray-100 dark:bg-gray-800 shadow"
215
+ aria-label="Toggle menu"
216
+ >
217
+ <svg className="h-5 w-5 dark:text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
218
+ {sidebarOpen ? (
219
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
220
+ ) : (
221
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
222
+ )}
223
+ </svg>
224
+ </button>
225
+
226
+ {/* Mobile overlay */}
227
+ {sidebarOpen && (
228
+ <div
229
+ className="fixed inset-0 z-30 bg-black/50 md:hidden"
230
+ onClick={() => setSidebarOpen(false)}
231
+ />
232
+ )}
233
+
234
+ {/* Sidebar */}
235
+ <aside className={\`fixed z-40 inset-y-0 left-0 w-64 border-r border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-800 p-4 flex flex-col transform transition-transform duration-200 ease-in-out md:relative md:translate-x-0 \${sidebarOpen ? 'translate-x-0' : '-translate-x-full'}\`}>
236
+ <div className="mb-4 mt-12 md:mt-0">
237
+ <h2 className="text-lg font-bold dark:text-white">{appName}</h2>
238
+ </div>
239
+ <nav className="space-y-2 flex-1">
240
+ <Link href="/dashboard" onClick={() => setSidebarOpen(false)} className="block px-4 py-2 rounded hover:bg-gray-200 dark:hover:bg-gray-700 dark:text-gray-200">
241
+ Dashboard
242
+ </Link>
243
+ <Link href="/dashboard/profile" onClick={() => setSidebarOpen(false)} className="block px-4 py-2 rounded hover:bg-gray-200 dark:hover:bg-gray-700 dark:text-gray-200">
244
+ Profile
245
+ </Link>
246
+ <Link href="/dashboard/settings" onClick={() => setSidebarOpen(false)} className="block px-4 py-2 rounded hover:bg-gray-200 dark:hover:bg-gray-700 dark:text-gray-200">
247
+ Settings
248
+ </Link>
249
+ </nav>
250
+ <div className="border-t border-gray-200 dark:border-gray-700 pt-3">
251
+ <div className="px-4 py-1 text-xs text-gray-500 dark:text-gray-400 truncate mb-1">
252
+ {session?.user?.email}
253
+ </div>
254
+ <button
255
+ onClick={() => signOut({ callbackUrl: '/auth/signin' })}
256
+ className="w-full flex items-center gap-2 px-4 py-2 rounded text-sm text-red-600 dark:text-red-400 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors"
257
+ >
258
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
259
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
260
+ </svg>
261
+ Sign out
262
+ </button>
263
+ </div>
264
+ </aside>
265
+ <main className="flex-1 p-8 pt-16 md:pt-8 dark:text-white">
266
+ {children}
267
+ </main>
268
+ </div>
269
+ );
270
+ }
271
+ `;
272
+ }
273
+ /**
274
+ * Generates the main dashboard page shown after login
275
+ * Displays welcome message, quick links to features, and system overview
276
+ * @returns TypeScript/JSX content for app/dashboard/page.tsx
277
+ */
278
+ function generateDashboardPage(features = []) {
279
+ // Build links array based on selected features
280
+ const linkEntries = [
281
+ ` { href: '/dashboard/providers', label: 'AI Providers', icon: '🔌', description: 'Manage AI model providers and API keys' },`,
282
+ ];
283
+ if (features.includes('prompts')) {
284
+ linkEntries.push(` { href: '/dashboard/prompts', label: 'Prompt Templates', icon: '📝', description: 'Create and manage system prompts' },`);
285
+ }
286
+ if (features.includes('chat')) {
287
+ linkEntries.push(` { href: '/chat', label: 'Chat', icon: '💬', description: 'AI chat conversations' },`);
288
+ }
289
+ if (features.includes('admin')) {
290
+ linkEntries.push(` { href: '/admin', label: 'Admin Panel', icon: '⚙️', description: 'User management, roles & permissions' },`);
291
+ }
292
+ // API-Key management is always available (widget infrastructure is always generated)
293
+ linkEntries.push(` { href: '/settings/api-keys', label: 'API Keys', icon: '🔑', description: 'Manage API keys for widgets & external integrations' },`);
294
+ return `// @chimerai component=DashboardPage version=1.2
295
+ 'use client';
296
+
297
+ import { useSession } from 'next-auth/react';
298
+ import Link from 'next/link';
299
+
300
+ export default function DashboardPage() {
301
+ const { data: session } = useSession();
302
+
303
+ const links = [
304
+ ${linkEntries.join('\n')}
305
+ ];
306
+
307
+ return (
308
+ <div className="container mx-auto py-8 px-4">
309
+ <div className="mb-8">
310
+ <h1 className="text-3xl font-bold mb-2 dark:text-white">
311
+ Welcome{session?.user?.name ? \`, \${session.user.name}\` : ''}!
312
+ </h1>
313
+ <p className="text-gray-600 dark:text-gray-400">Your ChimerAI Dashboard</p>
314
+ </div>
315
+
316
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
317
+ {links.map((link) => (
318
+ <Link
319
+ key={link.href}
320
+ href={link.href}
321
+ className="block p-6 bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md hover:border-blue-300 dark:hover:border-blue-600 transition-all"
322
+ >
323
+ <div className="text-3xl mb-3">{link.icon}</div>
324
+ <h2 className="text-xl font-semibold mb-2 dark:text-white">{link.label}</h2>
325
+ <p className="text-gray-600 dark:text-gray-400 text-sm">{link.description}</p>
326
+ </Link>
327
+ ))}
328
+ </div>
329
+ </div>
330
+ );
331
+ }
332
+ `;
333
+ }
334
+ /**
335
+ * Generates the user profile page for the dashboard
336
+ * Displays current user info and allows basic profile editing
337
+ * @returns TypeScript/JSX content for app/dashboard/profile/page.tsx
338
+ */
339
+ function generateProfilePage() {
340
+ return `// @chimerai component=ProfilePage version=1.0
341
+ 'use client';
342
+
343
+ import { useSession } from 'next-auth/react';
344
+ import { useState } from 'react';
345
+
346
+ export default function ProfilePage() {
347
+ const { data: session, update } = useSession();
348
+ const [name, setName] = useState(session?.user?.name || '');
349
+ const [saving, setSaving] = useState(false);
350
+ const [message, setMessage] = useState('');
351
+
352
+ async function handleSave(e: React.FormEvent) {
353
+ e.preventDefault();
354
+ setSaving(true);
355
+ setMessage('');
356
+
357
+ try {
358
+ const res = await fetch('/api/user/profile', {
359
+ method: 'PUT',
360
+ headers: { 'Content-Type': 'application/json' },
361
+ body: JSON.stringify({ name }),
362
+ });
363
+
364
+ if (res.ok) {
365
+ await update({ name });
366
+ setMessage('Profile updated successfully!');
367
+ } else {
368
+ setMessage('Failed to update profile.');
369
+ }
370
+ } catch {
371
+ setMessage('An error occurred.');
372
+ } finally {
373
+ setSaving(false);
374
+ }
375
+ }
376
+
377
+ return (
378
+ <div className="container mx-auto py-8 px-4 max-w-2xl">
379
+ <div className="mb-8">
380
+ <h1 className="text-3xl font-bold mb-2 dark:text-white">Profile</h1>
381
+ <p className="text-gray-600 dark:text-gray-400">Manage your account information</p>
382
+ </div>
383
+
384
+ <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
385
+ <form onSubmit={handleSave} className="space-y-6">
386
+ <div>
387
+ <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Email</label>
388
+ <input
389
+ type="email"
390
+ value={session?.user?.email || ''}
391
+ disabled
392
+ className="w-full border dark:border-gray-600 rounded px-3 py-2 bg-gray-50 dark:bg-gray-700 text-gray-500 dark:text-gray-400"
393
+ />
394
+ <p className="text-xs text-gray-400 mt-1">Email cannot be changed</p>
395
+ </div>
396
+
397
+ <div>
398
+ <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Name</label>
399
+ <input
400
+ type="text"
401
+ value={name}
402
+ onChange={(e) => setName(e.target.value)}
403
+ className="w-full border dark:border-gray-600 rounded px-3 py-2 dark:bg-gray-700 dark:text-white"
404
+ placeholder="Your name"
405
+ />
406
+ </div>
407
+
408
+ <div>
409
+ <label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">Role</label>
410
+ <input
411
+ type="text"
412
+ value={(session?.user?.roles as any)?.map((r: any) => r.name).join(', ') || 'user'}
413
+ disabled
414
+ className="w-full border dark:border-gray-600 rounded px-3 py-2 bg-gray-50 dark:bg-gray-700 text-gray-500 dark:text-gray-400"
415
+ />
416
+ </div>
417
+
418
+ {message && (
419
+ <p className={\`text-sm \${message.includes('success') ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}\`}>
420
+ {message}
421
+ </p>
422
+ )}
423
+
424
+ <button
425
+ type="submit"
426
+ disabled={saving}
427
+ className="px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
428
+ >
429
+ {saving ? 'Saving...' : 'Save Changes'}
430
+ </button>
431
+ </form>
432
+ </div>
433
+ </div>
434
+ );
435
+ }
436
+ `;
437
+ }
438
+ /**
439
+ * Generates the user profile API route for updating profile data
440
+ * @returns TypeScript content for app/api/user/profile/route.ts
441
+ */
442
+ function generateUserProfileRoute() {
443
+ return `// @chimerai component=UserProfileRoute version=1.0
444
+ import { NextRequest, NextResponse } from 'next/server';
445
+ import { getServerSession } from 'next-auth';
446
+ import { authOptions } from '@/lib/auth';
447
+ import { prisma } from '@/lib/prisma';
448
+
449
+ export async function PUT(req: NextRequest) {
450
+ const session = await getServerSession(authOptions);
451
+
452
+ if (!session?.user?.id) {
453
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
454
+ }
455
+
456
+ try {
457
+ const body = await req.json();
458
+
459
+ const user = await prisma.user.update({
460
+ where: { id: session.user.id },
461
+ data: { name: body.name },
462
+ });
463
+
464
+ return NextResponse.json({ name: user.name });
465
+ } catch (error: any) {
466
+ console.error('Error updating profile:', error);
467
+ return NextResponse.json({ error: 'Failed to update profile' }, { status: 500 });
468
+ }
469
+ }
470
+ `;
471
+ }
472
+ /**
473
+ * Generates the dashboard settings page
474
+ * Allows users to configure personal preferences
475
+ * @returns TypeScript/JSX content for app/dashboard/settings/page.tsx
476
+ */
477
+ function generateSettingsPage() {
478
+ return `// @chimerai component=SettingsPage version=1.1
479
+ 'use client';
480
+
481
+ import { useState, useEffect } from 'react';
482
+ import { signOut, useSession } from 'next-auth/react';
483
+ import { useTheme } from 'next-themes';
484
+
485
+ export default function SettingsPage() {
486
+ const { theme, setTheme, resolvedTheme } = useTheme();
487
+ const { data: session } = useSession();
488
+ const [mounted, setMounted] = useState(false);
489
+ const [deleteConfirm, setDeleteConfirm] = useState('');
490
+ const [deleting, setDeleting] = useState(false);
491
+ const [exporting, setExporting] = useState(false);
492
+
493
+ useEffect(() => setMounted(true), []);
494
+
495
+ const handleDataExport = async () => {
496
+ setExporting(true);
497
+ try {
498
+ const res = await fetch('/api/user/data-export');
499
+ if (res.ok) {
500
+ const blob = await res.blob();
501
+ const url = URL.createObjectURL(blob);
502
+ const a = document.createElement('a');
503
+ a.href = url;
504
+ a.download = 'my-data-export.json';
505
+ a.click();
506
+ URL.revokeObjectURL(url);
507
+ }
508
+ } catch (err) {
509
+ console.error('Export failed:', err);
510
+ } finally {
511
+ setExporting(false);
512
+ }
513
+ };
514
+
515
+ const handleDeleteAccount = async () => {
516
+ if (!session?.user?.email || deleteConfirm !== session.user.email) return;
517
+ setDeleting(true);
518
+ try {
519
+ const res = await fetch('/api/user/account', {
520
+ method: 'DELETE',
521
+ headers: { 'Content-Type': 'application/json' },
522
+ body: JSON.stringify({ confirmation: deleteConfirm }),
523
+ });
524
+ if (res.ok) {
525
+ signOut({ callbackUrl: '/auth/signin' });
526
+ }
527
+ } catch (err) {
528
+ console.error('Delete failed:', err);
529
+ } finally {
530
+ setDeleting(false);
531
+ }
532
+ };
533
+
534
+ return (
535
+ <div className="container mx-auto py-8 px-4 max-w-2xl">
536
+ <div className="mb-8">
537
+ <h1 className="text-3xl font-bold mb-2 dark:text-white">Settings</h1>
538
+ <p className="text-gray-600 dark:text-gray-400">Manage your preferences</p>
539
+ </div>
540
+
541
+ <div className="space-y-6">
542
+ {/* Appearance */}
543
+ <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
544
+ <h2 className="text-xl font-semibold mb-4 dark:text-white">Appearance</h2>
545
+ <div className="flex items-center justify-between py-3">
546
+ <div>
547
+ <p className="font-medium dark:text-white">Theme</p>
548
+ <p className="text-sm text-gray-500 dark:text-gray-400">Choose your preferred theme</p>
549
+ </div>
550
+ {mounted ? (
551
+ <select
552
+ value={theme}
553
+ onChange={(e) => setTheme(e.target.value)}
554
+ className="border rounded px-3 py-2 dark:bg-gray-700 dark:border-gray-600 dark:text-white"
555
+ >
556
+ <option value="light">Light</option>
557
+ <option value="dark">Dark</option>
558
+ <option value="system">System</option>
559
+ </select>
560
+ ) : (
561
+ <div className="w-24 h-10 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" />
562
+ )}
563
+ </div>
564
+ </div>
565
+
566
+ {/* Privacy & Data */}
567
+ <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
568
+ <h2 className="text-xl font-semibold mb-4 dark:text-white">🔒 Privacy & Data</h2>
569
+ <div className="space-y-4">
570
+ <div className="flex items-center justify-between py-3">
571
+ <div>
572
+ <p className="font-medium dark:text-white">Export My Data</p>
573
+ <p className="text-sm text-gray-500 dark:text-gray-400">Download all your data as a JSON file</p>
574
+ </div>
575
+ <button
576
+ onClick={handleDataExport}
577
+ disabled={exporting}
578
+ className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50"
579
+ >
580
+ {exporting ? 'Exporting...' : 'Export Data'}
581
+ </button>
582
+ </div>
583
+ </div>
584
+ </div>
585
+
586
+ {/* Danger Zone */}
587
+ <div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6 border border-red-200 dark:border-red-800">
588
+ <h2 className="text-xl font-semibold mb-4 text-red-600">Danger Zone</h2>
589
+ <div className="flex items-center justify-between py-3">
590
+ <div>
591
+ <p className="font-medium dark:text-white">Sign Out</p>
592
+ <p className="text-sm text-gray-500 dark:text-gray-400">Sign out of your account on this device</p>
593
+ </div>
594
+ <button
595
+ onClick={() => signOut({ callbackUrl: '/auth/signin' })}
596
+ className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
597
+ >
598
+ Sign Out
599
+ </button>
600
+ </div>
601
+ <hr className="my-4 dark:border-gray-700" />
602
+ <div className="py-3">
603
+ <p className="font-medium dark:text-white">Delete Account</p>
604
+ <p className="text-sm text-gray-500 dark:text-gray-400 mb-3">
605
+ Permanently delete your account and all associated data. This cannot be undone.
606
+ </p>
607
+ <div className="flex gap-2">
608
+ <input
609
+ type="text"
610
+ value={deleteConfirm}
611
+ onChange={(e) => setDeleteConfirm(e.target.value)}
612
+ placeholder="Type your email to confirm"
613
+ className="flex-1 px-3 py-2 border rounded dark:bg-gray-700 dark:border-gray-600 dark:text-white text-sm"
614
+ />
615
+ <button
616
+ onClick={handleDeleteAccount}
617
+ disabled={deleting || deleteConfirm !== session?.user?.email}
618
+ className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700 disabled:opacity-50"
619
+ >
620
+ {deleting ? 'Deleting...' : 'Delete Account'}
621
+ </button>
622
+ </div>
623
+ </div>
624
+ </div>
625
+ </div>
626
+ </div>
627
+ );
628
+ }
629
+ `;
630
+ }
631
+ /**
632
+ * Generates the useAppName hook.
633
+ * Client-side hook that fetches app name from /api/app-settings with module-level cache.
634
+ * @returns TypeScript content for lib/use-app-name.ts
635
+ */
636
+ function generateUseAppNameHook() {
637
+ return `// @chimerai component=UseAppName version=1.0
638
+ 'use client';
639
+
640
+ import { useState, useEffect } from 'react';
641
+
642
+ const DEFAULT_NAME = 'ChimerAI';
643
+
644
+ let cachedName: string | null = null;
645
+
646
+ export function useAppName() {
647
+ const [appName, setAppName] = useState(cachedName || DEFAULT_NAME);
648
+
649
+ useEffect(() => {
650
+ if (cachedName) {
651
+ setAppName(cachedName);
652
+ return;
653
+ }
654
+
655
+ fetch('/api/app-settings')
656
+ .then((res) => res.json())
657
+ .then((data) => {
658
+ const name = data.appName || DEFAULT_NAME;
659
+ cachedName = name;
660
+ setAppName(name);
661
+ })
662
+ .catch(() => {
663
+ setAppName(DEFAULT_NAME);
664
+ });
665
+ }, []);
666
+
667
+ return appName;
668
+ }
669
+ `;
670
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * MFA (Multi-Factor Authentication) Templates
3
+ * TOTP-based 2FA using otpauth + qrcode
4
+ */
5
+ /** lib/mfa.ts — TOTP helpers */
6
+ export declare function generateMfaLib(): string;
7
+ /** app/api/mfa/setup/route.ts — generate TOTP secret */
8
+ export declare function generateMfaSetupRoute(): string;
9
+ /** app/api/mfa/verify/route.ts — confirm TOTP code to enable MFA */
10
+ export declare function generateMfaVerifyRoute(): string;
11
+ /** app/api/mfa/disable/route.ts — disable MFA */
12
+ export declare function generateMfaDisableRoute(): string;
13
+ /** app/(app)/settings/mfa/page.tsx — MFA setup UI */
14
+ export declare function generateMfaPage(): string;
15
+ /**
16
+ * Prisma schema extension: adds mfaSecret + mfaEnabled to User model
17
+ * and creates a standalone MfaBackupCode model for recovery codes.
18
+ * SQLite-safe: no @db.Text, no Json, no arrays.
19
+ */
20
+ export declare const MFA_SCHEMA_EXTENSION = "\n mfaSecret String?\n mfaEnabled Boolean @default(false)\n\nmodel MfaBackupCode {\n id String @id @default(cuid())\n userId String\n codeHash String\n usedAt DateTime?\n createdAt DateTime @default(now())\n user User @relation(fields: [userId], references: [id], onDelete: Cascade)\n\n @@index([userId])\n}\n";
21
+ /** Fields to patch onto the User model (added by extendPrismaSchema) */
22
+ export declare const MFA_USER_FIELDS = "\n mfaSecret String?\n mfaEnabled Boolean @default(false)\n mfaBackupCodes MfaBackupCode[]\n";
23
+ //# sourceMappingURL=mfa.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mfa.d.ts","sourceRoot":"","sources":["../../src/templates/mfa.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,gCAAgC;AAChC,wBAAgB,cAAc,IAAI,MAAM,CAmDvC;AAED,wDAAwD;AACxD,wBAAgB,qBAAqB,IAAI,MAAM,CAyB9C;AAED,oEAAoE;AACpE,wBAAgB,sBAAsB,IAAI,MAAM,CAsC/C;AAED,iDAAiD;AACjD,wBAAgB,uBAAuB,IAAI,MAAM,CAsChD;AAED,qDAAqD;AACrD,wBAAgB,eAAe,IAAI,MAAM,CAyJxC;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,uWAchC,CAAC;AAEF,wEAAwE;AACxE,eAAO,MAAM,eAAe,iHAI3B,CAAC"}