@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,728 @@
1
+ "use strict";
2
+ /**
3
+ * Doctor Command - Health check for ChimerAI projects
4
+ * Validates configuration, dependencies, and environment setup
5
+ */
6
+ var __importDefault = (this && this.__importDefault) || function (mod) {
7
+ return (mod && mod.__esModule) ? mod : { "default": mod };
8
+ };
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.doctorCommand = doctorCommand;
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const ora_1 = __importDefault(require("ora"));
13
+ const fs_extra_1 = __importDefault(require("fs-extra"));
14
+ const path_1 = __importDefault(require("path"));
15
+ const utils_js_1 = require("../utils.js");
16
+ const provider_db_js_1 = require("../utils/provider-db.js");
17
+ async function doctorCommand(options) {
18
+ console.log(chalk_1.default.bold.cyan('\n🩺 ChimerAI Doctor - Project Health Check\n'));
19
+ const targetDir = (0, utils_js_1.resolveTargetDir)(options.dir);
20
+ const results = [];
21
+ const spinner = (0, ora_1.default)('Running diagnostics...').start();
22
+ try {
23
+ spinner.text = 'Checking package.json...';
24
+ results.push(await checkPackageJson(targetDir));
25
+ spinner.text = 'Checking Node.js version...';
26
+ results.push(checkNodeVersion());
27
+ spinner.text = 'Checking environment variables...';
28
+ results.push(...(await checkEnvVars(targetDir)));
29
+ spinner.text = 'Checking Prisma schema...';
30
+ results.push(await checkPrismaSchema(targetDir));
31
+ spinner.text = 'Checking project structure...';
32
+ results.push(...checkDirectories(targetDir));
33
+ spinner.text = 'Checking dependencies...';
34
+ results.push(await checkDependencies(targetDir));
35
+ spinner.text = 'Checking TypeScript configuration...';
36
+ results.push(await checkTsConfig(targetDir));
37
+ spinner.text = 'Checking for common issues...';
38
+ results.push(...(await checkCommonIssues(targetDir)));
39
+ spinner.text = 'Checking providers...';
40
+ results.push(...(await checkProviders(targetDir)));
41
+ spinner.text = 'Checking for unmigrated .env keys...';
42
+ results.push(...(await checkUnmigratedEnvKeys(targetDir)));
43
+ spinner.stop();
44
+ console.log(chalk_1.default.bold('\n📋 Diagnostic Results:\n'));
45
+ const passes = results.filter((r) => r.status === 'pass');
46
+ const warns = results.filter((r) => r.status === 'warn');
47
+ const fails = results.filter((r) => r.status === 'fail');
48
+ for (const result of results) {
49
+ const icon = result.status === 'pass'
50
+ ? chalk_1.default.green('✓')
51
+ : result.status === 'warn'
52
+ ? chalk_1.default.yellow('⚠')
53
+ : chalk_1.default.red('✗');
54
+ console.log(` ${icon} ${result.name}: ${result.message}`);
55
+ if (result.fix && result.status !== 'pass') {
56
+ console.log(chalk_1.default.gray(` Fix: ${result.fix}`));
57
+ }
58
+ }
59
+ console.log(chalk_1.default.bold('\n📊 Summary:'));
60
+ console.log(chalk_1.default.green(` ✓ ${passes.length} passed`));
61
+ if (warns.length > 0)
62
+ console.log(chalk_1.default.yellow(` ⚠ ${warns.length} warnings`));
63
+ if (fails.length > 0)
64
+ console.log(chalk_1.default.red(` ✗ ${fails.length} failed`));
65
+ if (options.fix && (warns.length > 0 || fails.length > 0)) {
66
+ console.log(chalk_1.default.bold.cyan('\n🔧 Attempting auto-fixes...\n'));
67
+ await autoFix(targetDir, results);
68
+ }
69
+ if (fails.length === 0 && warns.length === 0) {
70
+ console.log(chalk_1.default.bold.green('\n✅ Your project looks healthy! 🎉\n'));
71
+ }
72
+ else if (fails.length === 0) {
73
+ console.log(chalk_1.default.bold.yellow('\n⚠️ Project is functional but has warnings.\n'));
74
+ }
75
+ else {
76
+ console.log(chalk_1.default.bold.red('\n❌ Project has issues that need attention.\n'));
77
+ if (!options.fix) {
78
+ console.log(chalk_1.default.cyan(' Run with --fix to attempt automatic repairs'));
79
+ }
80
+ }
81
+ }
82
+ catch (error) {
83
+ spinner.fail('Diagnostics failed');
84
+ console.error(chalk_1.default.red(error.message));
85
+ }
86
+ }
87
+ /* ------------------------------------------------------------------ */
88
+ /* Individual Checks */
89
+ /* ------------------------------------------------------------------ */
90
+ async function checkPackageJson(targetDir) {
91
+ const pkgPath = path_1.default.join(targetDir, 'package.json');
92
+ if (!fs_extra_1.default.existsSync(pkgPath)) {
93
+ return {
94
+ name: 'package.json',
95
+ status: 'fail',
96
+ message: 'Not found',
97
+ fix: 'Run npm init or chimerai create',
98
+ };
99
+ }
100
+ try {
101
+ const pkg = await fs_extra_1.default.readJson(pkgPath);
102
+ if (!pkg.dependencies?.next) {
103
+ return {
104
+ name: 'package.json',
105
+ status: 'warn',
106
+ message: 'Next.js not found in dependencies',
107
+ fix: 'Run: pnpm add next',
108
+ };
109
+ }
110
+ if (!pkg.dependencies?.['@prisma/client']) {
111
+ return {
112
+ name: 'package.json',
113
+ status: 'warn',
114
+ message: '@prisma/client not found in dependencies',
115
+ fix: 'Run: pnpm add @prisma/client',
116
+ };
117
+ }
118
+ return { name: 'package.json', status: 'pass', message: 'Valid with required dependencies' };
119
+ }
120
+ catch {
121
+ return {
122
+ name: 'package.json',
123
+ status: 'fail',
124
+ message: 'Invalid JSON',
125
+ fix: 'Check package.json for syntax errors',
126
+ };
127
+ }
128
+ }
129
+ function checkNodeVersion() {
130
+ const version = process.version;
131
+ const major = parseInt(version.slice(1).split('.')[0], 10);
132
+ if (major < 18) {
133
+ return {
134
+ name: 'Node.js',
135
+ status: 'fail',
136
+ message: `v${major} (requires >= 18)`,
137
+ fix: 'Install Node.js 18 or higher',
138
+ };
139
+ }
140
+ if (major < 20) {
141
+ return {
142
+ name: 'Node.js',
143
+ status: 'warn',
144
+ message: `${version} (recommended >= 20)`,
145
+ fix: 'Consider upgrading to Node.js 20+',
146
+ };
147
+ }
148
+ return { name: 'Node.js', status: 'pass', message: `${version}` };
149
+ }
150
+ async function checkEnvVars(targetDir) {
151
+ const results = [];
152
+ const envPath = path_1.default.join(targetDir, '.env');
153
+ if (!fs_extra_1.default.existsSync(envPath)) {
154
+ results.push({
155
+ name: '.env file',
156
+ status: 'fail',
157
+ message: 'Not found',
158
+ fix: 'Copy .env.example to .env and fill in values',
159
+ });
160
+ return results;
161
+ }
162
+ const envContent = await fs_extra_1.default.readFile(envPath, 'utf-8');
163
+ if (!envContent.includes('DATABASE_URL=') || envContent.includes('DATABASE_URL=""')) {
164
+ results.push({
165
+ name: 'DATABASE_URL',
166
+ status: 'fail',
167
+ message: 'Not configured',
168
+ fix: 'Set DATABASE_URL in .env',
169
+ });
170
+ }
171
+ else {
172
+ results.push({ name: 'DATABASE_URL', status: 'pass', message: 'Configured' });
173
+ }
174
+ if (envContent.includes('NEXTAUTH_SECRET=')) {
175
+ const match = envContent.match(/NEXTAUTH_SECRET=["']?([^"'\n]*)["']?/);
176
+ if (!match?.[1] || match[1] === 'your-secret-key-change-in-production') {
177
+ results.push({
178
+ name: 'NEXTAUTH_SECRET',
179
+ status: 'warn',
180
+ message: 'Using default value - change for production',
181
+ fix: 'Generate: openssl rand -base64 32',
182
+ });
183
+ }
184
+ else {
185
+ results.push({ name: 'NEXTAUTH_SECRET', status: 'pass', message: 'Configured' });
186
+ }
187
+ }
188
+ else {
189
+ results.push({
190
+ name: 'NEXTAUTH_SECRET',
191
+ status: 'warn',
192
+ message: 'Not set (needed for auth)',
193
+ fix: 'Run: chimerai setup auth',
194
+ });
195
+ }
196
+ const hasBilling = fs_extra_1.default.existsSync(path_1.default.join(targetDir, 'app/billing')) ||
197
+ fs_extra_1.default.existsSync(path_1.default.join(targetDir, 'app/api/billing'));
198
+ if (hasBilling && !envContent.includes('STRIPE_SECRET_KEY=')) {
199
+ results.push({
200
+ name: 'STRIPE_SECRET_KEY',
201
+ status: 'warn',
202
+ message: 'Billing detected but Stripe not configured',
203
+ fix: 'Run: chimerai setup stripe',
204
+ });
205
+ }
206
+ // Check Provider Encryption Key
207
+ if (envContent.includes('PROVIDER_ENCRYPTION_KEY=')) {
208
+ const match = envContent.match(/PROVIDER_ENCRYPTION_KEY=["']?([^"'\n]*)["']?/);
209
+ if (!match?.[1] || match[1].length < 64) {
210
+ results.push({
211
+ name: 'PROVIDER_ENCRYPTION_KEY',
212
+ status: 'warn',
213
+ message: 'Too short (need 64 hex chars)',
214
+ fix: "Generate: node -e \"console.log(require('crypto').randomBytes(32).toString('hex'))\"",
215
+ });
216
+ }
217
+ else {
218
+ results.push({
219
+ name: 'PROVIDER_ENCRYPTION_KEY',
220
+ status: 'pass',
221
+ message: 'Configured (64 hex chars)',
222
+ });
223
+ }
224
+ }
225
+ else {
226
+ // Check if the project has the Provider model (should have encryption key)
227
+ const schemaPath = path_1.default.join(targetDir, 'prisma/schema.prisma');
228
+ if (fs_extra_1.default.existsSync(schemaPath)) {
229
+ const schema = await fs_extra_1.default.readFile(schemaPath, 'utf-8');
230
+ if (schema.includes('model Provider')) {
231
+ results.push({
232
+ name: 'PROVIDER_ENCRYPTION_KEY',
233
+ status: 'fail',
234
+ message: 'Missing — required for provider encryption',
235
+ fix: 'Add to .env: PROVIDER_ENCRYPTION_KEY=<64-hex-chars>',
236
+ });
237
+ }
238
+ }
239
+ }
240
+ // Check Internal API Token
241
+ if (envContent.includes('INTERNAL_API_TOKEN=')) {
242
+ const match = envContent.match(/INTERNAL_API_TOKEN=["']?([^"'\n]*)["']?/);
243
+ if (!match?.[1] || match[1].length < 20) {
244
+ results.push({
245
+ name: 'INTERNAL_API_TOKEN',
246
+ status: 'warn',
247
+ message: 'Token too short',
248
+ fix: 'Generate a longer token for internal API communication',
249
+ });
250
+ }
251
+ else {
252
+ results.push({ name: 'INTERNAL_API_TOKEN', status: 'pass', message: 'Configured' });
253
+ }
254
+ }
255
+ return results;
256
+ }
257
+ async function checkPrismaSchema(targetDir) {
258
+ const schemaPath = path_1.default.join(targetDir, 'prisma/schema.prisma');
259
+ if (!fs_extra_1.default.existsSync(schemaPath)) {
260
+ return {
261
+ name: 'Prisma Schema',
262
+ status: 'fail',
263
+ message: 'Not found',
264
+ fix: 'Run: chimerai init',
265
+ };
266
+ }
267
+ const content = await fs_extra_1.default.readFile(schemaPath, 'utf-8');
268
+ if (!content.includes('datasource db')) {
269
+ return { name: 'Prisma Schema', status: 'fail', message: 'Missing datasource block' };
270
+ }
271
+ if (!content.includes('model Provider')) {
272
+ return {
273
+ name: 'Prisma Schema',
274
+ status: 'warn',
275
+ message: 'Provider model missing — regenerate project',
276
+ fix: 'Run: chimerai create (with latest CLI)',
277
+ };
278
+ }
279
+ const modelCount = (content.match(/^model\s+\w+/gm) || []).length;
280
+ return {
281
+ name: 'Prisma Schema',
282
+ status: 'pass',
283
+ message: `Valid schema with ${modelCount} models (incl. Provider, Model, ProviderHealth)`,
284
+ };
285
+ }
286
+ function checkDirectories(targetDir) {
287
+ const results = [];
288
+ for (const { dir, label } of [
289
+ { dir: 'app', label: 'App directory' },
290
+ { dir: 'prisma', label: 'Prisma directory' },
291
+ { dir: 'lib', label: 'Lib directory' },
292
+ ]) {
293
+ if (fs_extra_1.default.existsSync(path_1.default.join(targetDir, dir))) {
294
+ results.push({ name: label, status: 'pass', message: `${dir}/ exists` });
295
+ }
296
+ else {
297
+ results.push({
298
+ name: label,
299
+ status: 'fail',
300
+ message: `${dir}/ missing`,
301
+ fix: `Create the ${dir}/ directory`,
302
+ });
303
+ }
304
+ }
305
+ return results;
306
+ }
307
+ async function checkDependencies(targetDir) {
308
+ const nodeModules = path_1.default.join(targetDir, 'node_modules');
309
+ if (!fs_extra_1.default.existsSync(nodeModules)) {
310
+ return {
311
+ name: 'Dependencies',
312
+ status: 'fail',
313
+ message: 'node_modules not found',
314
+ fix: 'Run: pnpm install',
315
+ };
316
+ }
317
+ const missing = ['next', 'react', '@prisma/client'].filter((dep) => !fs_extra_1.default.existsSync(path_1.default.join(nodeModules, dep)));
318
+ if (missing.length > 0) {
319
+ return {
320
+ name: 'Dependencies',
321
+ status: 'warn',
322
+ message: `Missing: ${missing.join(', ')}`,
323
+ fix: 'Run: pnpm install',
324
+ };
325
+ }
326
+ return { name: 'Dependencies', status: 'pass', message: 'All critical dependencies installed' };
327
+ }
328
+ async function checkTsConfig(targetDir) {
329
+ const tsConfigPath = path_1.default.join(targetDir, 'tsconfig.json');
330
+ if (!fs_extra_1.default.existsSync(tsConfigPath)) {
331
+ return {
332
+ name: 'TypeScript',
333
+ status: 'warn',
334
+ message: 'tsconfig.json not found',
335
+ fix: 'Run: npx tsc --init',
336
+ };
337
+ }
338
+ try {
339
+ const content = await fs_extra_1.default.readFile(tsConfigPath, 'utf-8');
340
+ if (!content.includes('"@/*"') && !content.includes("'@/*'")) {
341
+ return {
342
+ name: 'TypeScript',
343
+ status: 'warn',
344
+ message: 'Path alias @/* not configured',
345
+ fix: 'Add paths to tsconfig.json',
346
+ };
347
+ }
348
+ return { name: 'TypeScript', status: 'pass', message: 'Configured with path aliases' };
349
+ }
350
+ catch {
351
+ return { name: 'TypeScript', status: 'warn', message: 'Could not parse tsconfig.json' };
352
+ }
353
+ }
354
+ async function checkCommonIssues(targetDir) {
355
+ const results = [];
356
+ const gitignorePath = path_1.default.join(targetDir, '.gitignore');
357
+ if (fs_extra_1.default.existsSync(gitignorePath)) {
358
+ const content = await fs_extra_1.default.readFile(gitignorePath, 'utf-8');
359
+ if (!content.includes('.env')) {
360
+ results.push({
361
+ name: '.gitignore',
362
+ status: 'warn',
363
+ message: '.env not in .gitignore!',
364
+ fix: 'Add .env to .gitignore',
365
+ });
366
+ }
367
+ else {
368
+ results.push({ name: '.gitignore', status: 'pass', message: '.env excluded from git' });
369
+ }
370
+ }
371
+ else {
372
+ results.push({
373
+ name: '.gitignore',
374
+ status: 'warn',
375
+ message: 'No .gitignore found',
376
+ fix: 'Create a .gitignore file',
377
+ });
378
+ }
379
+ if (!fs_extra_1.default.existsSync(path_1.default.join(targetDir, 'prisma/migrations'))) {
380
+ results.push({
381
+ name: 'Migrations',
382
+ status: 'warn',
383
+ message: 'No migrations directory (using db push?)',
384
+ fix: 'Consider: prisma migrate dev for production',
385
+ });
386
+ }
387
+ else {
388
+ results.push({ name: 'Migrations', status: 'pass', message: 'Migration directory exists' });
389
+ }
390
+ return results;
391
+ }
392
+ /* ------------------------------------------------------------------ */
393
+ /* Provider Checks */
394
+ /* ------------------------------------------------------------------ */
395
+ async function checkProviders(targetDir) {
396
+ const results = [];
397
+ const { found, providers } = await (0, provider_db_js_1.getProvidersFromDB)(targetDir);
398
+ if (!found) {
399
+ // DB not available — skip provider checks silently
400
+ return results;
401
+ }
402
+ if (providers.length === 0) {
403
+ results.push({
404
+ name: 'Providers',
405
+ status: 'warn',
406
+ message: 'No providers configured',
407
+ fix: 'Run: chimerai setup openai',
408
+ });
409
+ return results;
410
+ }
411
+ for (const provider of providers) {
412
+ const modelCount = provider.models?.length ?? 0;
413
+ const label = `Provider "${provider.name}"`;
414
+ if (provider.status === 'inactive') {
415
+ results.push({
416
+ name: label,
417
+ status: 'warn',
418
+ message: 'Inactive',
419
+ fix: 'Activate in Provider Management page',
420
+ });
421
+ continue;
422
+ }
423
+ if (provider.status === 'error') {
424
+ results.push({
425
+ name: label,
426
+ status: 'fail',
427
+ message: 'In error state',
428
+ fix: 'Check API key and run: chimerai setup ' + provider.type,
429
+ });
430
+ continue;
431
+ }
432
+ // Validate the API key depending on type
433
+ try {
434
+ if (provider.type === 'openai' && provider.apiKey) {
435
+ // We can't decrypt here (would need the encryption key), so check if health record is recent
436
+ if (provider.health?.status === 'unhealthy' || provider.health?.status === 'error') {
437
+ results.push({
438
+ name: label,
439
+ status: 'fail',
440
+ message: `Unhealthy — last error: ${provider.health.errorMessage || 'unknown'}`,
441
+ fix: 'Run: chimerai setup openai',
442
+ });
443
+ }
444
+ else {
445
+ results.push({
446
+ name: label,
447
+ status: 'pass',
448
+ message: `Active, ${modelCount} model${modelCount !== 1 ? 's' : ''}${provider.isDefault ? ' (default)' : ''}`,
449
+ });
450
+ }
451
+ }
452
+ else if (provider.type === 'anthropic' && provider.apiKey) {
453
+ if (provider.health?.status === 'unhealthy' || provider.health?.status === 'error') {
454
+ results.push({
455
+ name: label,
456
+ status: 'fail',
457
+ message: `Unhealthy — last error: ${provider.health.errorMessage || 'unknown'}`,
458
+ fix: 'Run: chimerai setup anthropic',
459
+ });
460
+ }
461
+ else {
462
+ results.push({
463
+ name: label,
464
+ status: 'pass',
465
+ message: `Active, ${modelCount} model${modelCount !== 1 ? 's' : ''}${provider.isDefault ? ' (default)' : ''}`,
466
+ });
467
+ }
468
+ }
469
+ else if (provider.type === 'ollama') {
470
+ const baseUrl = provider.baseUrl || 'http://localhost:11434';
471
+ const validation = await (0, provider_db_js_1.validateOllamaConnection)(baseUrl);
472
+ if (!validation.valid) {
473
+ results.push({
474
+ name: label,
475
+ status: 'fail',
476
+ message: `Cannot reach Ollama at ${baseUrl}`,
477
+ fix: 'Start Ollama: ollama serve',
478
+ });
479
+ }
480
+ else {
481
+ results.push({
482
+ name: label,
483
+ status: 'pass',
484
+ message: `Active, ${validation.models.length} model${validation.models.length !== 1 ? 's' : ''} available${provider.isDefault ? ' (default)' : ''}`,
485
+ });
486
+ }
487
+ }
488
+ else {
489
+ results.push({
490
+ name: label,
491
+ status: 'pass',
492
+ message: `Active (${provider.type}), ${modelCount} model${modelCount !== 1 ? 's' : ''}${provider.isDefault ? ' (default)' : ''}`,
493
+ });
494
+ }
495
+ }
496
+ catch {
497
+ results.push({ name: label, status: 'warn', message: `Could not verify (${provider.type})` });
498
+ }
499
+ }
500
+ // Check if at least one default provider exists
501
+ const hasDefault = providers.some((p) => p.isDefault);
502
+ if (!hasDefault && providers.length > 0) {
503
+ results.push({
504
+ name: 'Default Provider',
505
+ status: 'warn',
506
+ message: 'No default provider set',
507
+ fix: 'Set a default provider in Provider Management',
508
+ });
509
+ }
510
+ return results;
511
+ }
512
+ async function checkUnmigratedEnvKeys(targetDir) {
513
+ const results = [];
514
+ try {
515
+ const unmigrated = await (0, provider_db_js_1.findUnmigratedEnvKeys)(targetDir);
516
+ for (const key of unmigrated) {
517
+ results.push({
518
+ name: key.envKey,
519
+ status: 'warn',
520
+ message: `Found in .env but not in database`,
521
+ fix: `Run: chimerai setup ${key.type} (to migrate to database)`,
522
+ });
523
+ }
524
+ }
525
+ catch {
526
+ // DB not available — skip
527
+ }
528
+ return results;
529
+ }
530
+ /* ------------------------------------------------------------------ */
531
+ /* Auto-fix */
532
+ /* ------------------------------------------------------------------ */
533
+ async function autoFix(targetDir, results) {
534
+ let fixedCount = 0;
535
+ let manualCount = 0;
536
+ for (const result of results.filter((r) => r.status !== 'pass' && r.fix)) {
537
+ const spinner = (0, ora_1.default)(`Fixing: ${result.name}...`).start();
538
+ try {
539
+ switch (result.name) {
540
+ case '.env file': {
541
+ const examplePath = path_1.default.join(targetDir, '.env.example');
542
+ const envPath = path_1.default.join(targetDir, '.env');
543
+ if (fs_extra_1.default.existsSync(examplePath)) {
544
+ await fs_extra_1.default.copy(examplePath, envPath);
545
+ spinner.succeed('Created .env from .env.example');
546
+ }
547
+ else {
548
+ await fs_extra_1.default.writeFile(envPath, '# ChimerAI Environment\nDATABASE_URL=postgresql://postgres:postgres@localhost:5432/chimerai_db\nNEXTAUTH_SECRET=change-me-in-production\nNEXTAUTH_URL=http://localhost:3001\n');
549
+ spinner.succeed('Created .env with defaults');
550
+ }
551
+ fixedCount++;
552
+ break;
553
+ }
554
+ case 'NEXTAUTH_SECRET': {
555
+ // Generate a secure secret and update .env
556
+ const envPath = path_1.default.join(targetDir, '.env');
557
+ if (fs_extra_1.default.existsSync(envPath)) {
558
+ const { randomBytes } = await import('crypto');
559
+ const secret = randomBytes(32).toString('base64');
560
+ let content = await fs_extra_1.default.readFile(envPath, 'utf-8');
561
+ content = content.replace(/NEXTAUTH_SECRET=["']?[^"'\n]*["']?/, `NEXTAUTH_SECRET="${secret}"`);
562
+ await fs_extra_1.default.writeFile(envPath, content);
563
+ spinner.succeed('Generated secure NEXTAUTH_SECRET');
564
+ fixedCount++;
565
+ }
566
+ else {
567
+ spinner.info('Manual fix needed: Create .env first');
568
+ manualCount++;
569
+ }
570
+ break;
571
+ }
572
+ case 'DATABASE_URL': {
573
+ const envPath = path_1.default.join(targetDir, '.env');
574
+ if (fs_extra_1.default.existsSync(envPath)) {
575
+ let content = await fs_extra_1.default.readFile(envPath, 'utf-8');
576
+ if (content.includes('DATABASE_URL=""') || !content.includes('DATABASE_URL=')) {
577
+ content = content.includes('DATABASE_URL=')
578
+ ? content.replace(/DATABASE_URL=["']?[^"'\n]*["']?/, 'DATABASE_URL=postgresql://postgres:postgres@localhost:5432/chimerai_db')
579
+ : content +
580
+ '\nDATABASE_URL=postgresql://postgres:postgres@localhost:5432/chimerai_db\n';
581
+ await fs_extra_1.default.writeFile(envPath, content);
582
+ spinner.succeed('Set DATABASE_URL to local default');
583
+ fixedCount++;
584
+ }
585
+ }
586
+ else {
587
+ spinner.info('Manual fix needed: Create .env first');
588
+ manualCount++;
589
+ }
590
+ break;
591
+ }
592
+ case '.gitignore': {
593
+ const gpath = path_1.default.join(targetDir, '.gitignore');
594
+ if (fs_extra_1.default.existsSync(gpath)) {
595
+ const c = await fs_extra_1.default.readFile(gpath, 'utf-8');
596
+ await fs_extra_1.default.writeFile(gpath, c + '\n.env\n.env.local\n');
597
+ }
598
+ else {
599
+ await fs_extra_1.default.writeFile(gpath, 'node_modules/\n.next/\n.env\n.env.local\ndist/\n*.log\n');
600
+ }
601
+ spinner.succeed('Updated .gitignore');
602
+ fixedCount++;
603
+ break;
604
+ }
605
+ case 'App directory':
606
+ case 'Prisma directory':
607
+ case 'Lib directory': {
608
+ const dirName = result.message.split('/')[0];
609
+ await fs_extra_1.default.ensureDir(path_1.default.join(targetDir, dirName));
610
+ spinner.succeed(`Created ${dirName}/ directory`);
611
+ fixedCount++;
612
+ break;
613
+ }
614
+ case 'Dependencies': {
615
+ spinner.text = 'Installing dependencies (this may take a moment)...';
616
+ const { execa } = await import('execa');
617
+ try {
618
+ await execa('pnpm', ['install'], { cwd: targetDir });
619
+ spinner.succeed('Dependencies installed');
620
+ fixedCount++;
621
+ }
622
+ catch {
623
+ spinner.warn('pnpm install failed — try running manually');
624
+ manualCount++;
625
+ }
626
+ break;
627
+ }
628
+ case 'package.json': {
629
+ if (result.message.includes('Next.js')) {
630
+ spinner.info(`Manual fix needed: ${result.fix}`);
631
+ manualCount++;
632
+ }
633
+ else if (result.message.includes('@prisma/client')) {
634
+ spinner.info(`Manual fix needed: ${result.fix}`);
635
+ manualCount++;
636
+ }
637
+ else {
638
+ spinner.info(`Manual fix needed: ${result.fix}`);
639
+ manualCount++;
640
+ }
641
+ break;
642
+ }
643
+ case 'OPENAI_API_KEY': {
644
+ // Migrate OpenAI key from .env to DB
645
+ const envPath = path_1.default.join(targetDir, '.env');
646
+ const envContent = await fs_extra_1.default.readFile(envPath, 'utf-8');
647
+ const match = envContent.match(/OPENAI_API_KEY=["']?([^"'\n]+)["']?/);
648
+ if (match?.[1]) {
649
+ spinner.text = 'Migrating OpenAI API key to database...';
650
+ const validation = await (0, provider_db_js_1.validateOpenAIKey)(match[1]);
651
+ const created = await (0, provider_db_js_1.createProviderInDB)({
652
+ targetDir,
653
+ providerId: `migrated-openai-${Date.now()}`,
654
+ name: 'OpenAI',
655
+ type: 'openai',
656
+ description: 'Migrated from .env by chimerai doctor',
657
+ apiKey: match[1],
658
+ isDefault: true,
659
+ priority: 1,
660
+ models: provider_db_js_1.OPENAI_MODELS,
661
+ });
662
+ if (created) {
663
+ spinner.succeed(`Migrated OPENAI_API_KEY to database (${validation.valid ? 'key valid' : 'key not verified'})`);
664
+ console.log(chalk_1.default.gray(' ⚠ You can remove OPENAI_API_KEY from .env now (or keep as fallback)'));
665
+ fixedCount++;
666
+ }
667
+ else {
668
+ spinner.warn('Could not migrate — database not available');
669
+ manualCount++;
670
+ }
671
+ }
672
+ else {
673
+ spinner.info('Manual fix needed: Key not found in .env');
674
+ manualCount++;
675
+ }
676
+ break;
677
+ }
678
+ case 'ANTHROPIC_API_KEY': {
679
+ // Migrate Anthropic key from .env to DB
680
+ const envPath = path_1.default.join(targetDir, '.env');
681
+ const envContent = await fs_extra_1.default.readFile(envPath, 'utf-8');
682
+ const match = envContent.match(/ANTHROPIC_API_KEY=["']?([^"'\n]+)["']?/);
683
+ if (match?.[1]) {
684
+ spinner.text = 'Migrating Anthropic API key to database...';
685
+ const validation = await (0, provider_db_js_1.validateAnthropicKey)(match[1]);
686
+ const created = await (0, provider_db_js_1.createProviderInDB)({
687
+ targetDir,
688
+ providerId: `migrated-anthropic-${Date.now()}`,
689
+ name: 'Anthropic',
690
+ type: 'anthropic',
691
+ description: 'Migrated from .env by chimerai doctor',
692
+ apiKey: match[1],
693
+ isDefault: false,
694
+ priority: 2,
695
+ models: provider_db_js_1.ANTHROPIC_MODELS,
696
+ });
697
+ if (created) {
698
+ spinner.succeed(`Migrated ANTHROPIC_API_KEY to database (${validation.valid ? 'key valid' : 'key not verified'})`);
699
+ console.log(chalk_1.default.gray(' ⚠ You can remove ANTHROPIC_API_KEY from .env now (or keep as fallback)'));
700
+ fixedCount++;
701
+ }
702
+ else {
703
+ spinner.warn('Could not migrate — database not available');
704
+ manualCount++;
705
+ }
706
+ }
707
+ else {
708
+ spinner.info('Manual fix needed: Key not found in .env');
709
+ manualCount++;
710
+ }
711
+ break;
712
+ }
713
+ case 'AZURE_OPENAI_API_KEY': {
714
+ spinner.info('Manual fix needed: Run chimerai setup azure to migrate');
715
+ manualCount++;
716
+ break;
717
+ }
718
+ default:
719
+ spinner.info(`Manual fix needed: ${result.fix}`);
720
+ manualCount++;
721
+ }
722
+ }
723
+ catch (error) {
724
+ spinner.fail(`Could not fix ${result.name}: ${error.message}`);
725
+ }
726
+ }
727
+ console.log(chalk_1.default.bold(`\n 🔧 Auto-fix summary: ${chalk_1.default.green(`${fixedCount} fixed`)}, ${chalk_1.default.yellow(`${manualCount} need manual action`)}\n`));
728
+ }