@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.
- package/LICENSE +21 -0
- package/README.md +293 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +317 -0
- package/dist/commands/add.d.ts +11 -0
- package/dist/commands/add.d.ts.map +1 -0
- package/dist/commands/add.js +2126 -0
- package/dist/commands/create.d.ts +12 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +1703 -0
- package/dist/commands/deploy.d.ts +11 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +219 -0
- package/dist/commands/dev.d.ts +17 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +206 -0
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +728 -0
- package/dist/commands/generate.d.ts +19 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +429 -0
- package/dist/commands/init.d.ts +11 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +269 -0
- package/dist/commands/list.d.ts +12 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +328 -0
- package/dist/commands/migrate.d.ts +14 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +197 -0
- package/dist/commands/plugin.d.ts +10 -0
- package/dist/commands/plugin.d.ts.map +1 -0
- package/dist/commands/plugin.js +239 -0
- package/dist/commands/remove.d.ts +11 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +472 -0
- package/dist/commands/secret.d.ts +12 -0
- package/dist/commands/secret.d.ts.map +1 -0
- package/dist/commands/secret.js +102 -0
- package/dist/commands/setup.d.ts +9 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +788 -0
- package/dist/commands/update.d.ts +14 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +211 -0
- package/dist/commands/use.d.ts +9 -0
- package/dist/commands/use.d.ts.map +1 -0
- package/dist/commands/use.js +51 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/license.d.ts +55 -0
- package/dist/license.d.ts.map +1 -0
- package/dist/license.js +258 -0
- package/dist/scanner.d.ts +31 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +113 -0
- package/dist/schema-manager.d.ts +26 -0
- package/dist/schema-manager.d.ts.map +1 -0
- package/dist/schema-manager.js +132 -0
- package/dist/templates/admin.d.ts +49 -0
- package/dist/templates/admin.d.ts.map +1 -0
- package/dist/templates/admin.js +1358 -0
- package/dist/templates/ai-routes.d.ts +17 -0
- package/dist/templates/ai-routes.d.ts.map +1 -0
- package/dist/templates/ai-routes.js +1130 -0
- package/dist/templates/ai-service-tools.d.ts +22 -0
- package/dist/templates/ai-service-tools.d.ts.map +1 -0
- package/dist/templates/ai-service-tools.js +1424 -0
- package/dist/templates/ai-service.d.ts +66 -0
- package/dist/templates/ai-service.d.ts.map +1 -0
- package/dist/templates/ai-service.js +2202 -0
- package/dist/templates/api-routes.d.ts +108 -0
- package/dist/templates/api-routes.d.ts.map +1 -0
- package/dist/templates/api-routes.js +1219 -0
- package/dist/templates/auth.d.ts +48 -0
- package/dist/templates/auth.d.ts.map +1 -0
- package/dist/templates/auth.js +381 -0
- package/dist/templates/billing.d.ts +44 -0
- package/dist/templates/billing.d.ts.map +1 -0
- package/dist/templates/billing.js +551 -0
- package/dist/templates/chat.d.ts +63 -0
- package/dist/templates/chat.d.ts.map +1 -0
- package/dist/templates/chat.js +1979 -0
- package/dist/templates/components.d.ts +22 -0
- package/dist/templates/components.d.ts.map +1 -0
- package/dist/templates/components.js +672 -0
- package/dist/templates/config.d.ts +6 -0
- package/dist/templates/config.d.ts.map +1 -0
- package/dist/templates/config.js +86 -0
- package/dist/templates/docker.d.ts +25 -0
- package/dist/templates/docker.d.ts.map +1 -0
- package/dist/templates/docker.js +165 -0
- package/dist/templates/gdpr.d.ts +16 -0
- package/dist/templates/gdpr.d.ts.map +1 -0
- package/dist/templates/gdpr.js +259 -0
- package/dist/templates/index.d.ts +77 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +339 -0
- package/dist/templates/layout.d.ts +67 -0
- package/dist/templates/layout.d.ts.map +1 -0
- package/dist/templates/layout.js +670 -0
- package/dist/templates/mfa.d.ts +23 -0
- package/dist/templates/mfa.d.ts.map +1 -0
- package/dist/templates/mfa.js +353 -0
- package/dist/templates/middleware.d.ts +12 -0
- package/dist/templates/middleware.d.ts.map +1 -0
- package/dist/templates/middleware.js +116 -0
- package/dist/templates/prisma.d.ts +35 -0
- package/dist/templates/prisma.d.ts.map +1 -0
- package/dist/templates/prisma.js +724 -0
- package/dist/templates/provider-routes.d.ts +21 -0
- package/dist/templates/provider-routes.d.ts.map +1 -0
- package/dist/templates/provider-routes.js +1203 -0
- package/dist/templates/rag.d.ts +48 -0
- package/dist/templates/rag.d.ts.map +1 -0
- package/dist/templates/rag.js +532 -0
- package/dist/templates/widget.d.ts +64 -0
- package/dist/templates/widget.d.ts.map +1 -0
- package/dist/templates/widget.js +1360 -0
- package/dist/utils/provider-db.d.ts +63 -0
- package/dist/utils/provider-db.d.ts.map +1 -0
- package/dist/utils/provider-db.js +300 -0
- package/dist/utils.d.ts +78 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +330 -0
- 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"}
|