@codebakers/cli 1.4.1 → 1.4.3

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/src/mcp/server.ts CHANGED
@@ -8,9 +8,11 @@ import {
8
8
  ErrorCode,
9
9
  McpError,
10
10
  } from '@modelcontextprotocol/sdk/types.js';
11
- import { getApiKey, getApiUrl } from '../config.js';
11
+ import { getApiKey, getApiUrl, getExperienceLevel, setExperienceLevel, type ExperienceLevel } from '../config.js';
12
12
  import * as fs from 'fs';
13
13
  import * as path from 'path';
14
+ import { execSync } from 'child_process';
15
+ import * as templates from '../templates/nextjs-supabase.js';
14
16
 
15
17
  // Pattern cache to avoid repeated API calls
16
18
  const patternCache = new Map<string, { content: string; timestamp: number }>();
@@ -377,6 +379,64 @@ class CodeBakersServer {
377
379
  required: ['pattern', 'section'],
378
380
  },
379
381
  },
382
+ {
383
+ name: 'scaffold_project',
384
+ description:
385
+ 'Create a new project from scratch with Next.js + Supabase + Drizzle. Use this when user wants to build something new and no project exists yet. Creates all files, installs dependencies, and sets up CodeBakers patterns automatically.',
386
+ inputSchema: {
387
+ type: 'object' as const,
388
+ properties: {
389
+ projectName: {
390
+ type: 'string',
391
+ description: 'Name of the project (lowercase, no spaces)',
392
+ },
393
+ description: {
394
+ type: 'string',
395
+ description: 'Brief description of what the project is for (used in PRD.md)',
396
+ },
397
+ },
398
+ required: ['projectName'],
399
+ },
400
+ },
401
+ {
402
+ name: 'init_project',
403
+ description:
404
+ 'Add CodeBakers patterns to an existing project. Use this when user has an existing codebase and wants to add AI patterns to it.',
405
+ inputSchema: {
406
+ type: 'object' as const,
407
+ properties: {
408
+ projectName: {
409
+ type: 'string',
410
+ description: 'Name of the project (optional, will be auto-detected from package.json)',
411
+ },
412
+ },
413
+ },
414
+ },
415
+ {
416
+ name: 'set_experience_level',
417
+ description:
418
+ 'Set the user experience level. This affects how detailed explanations are when building features. Use "beginner" for new developers who need more explanations, "intermediate" for developers who know the basics, or "advanced" for experienced developers who want minimal explanations.',
419
+ inputSchema: {
420
+ type: 'object' as const,
421
+ properties: {
422
+ level: {
423
+ type: 'string',
424
+ enum: ['beginner', 'intermediate', 'advanced'],
425
+ description: 'Experience level: beginner (detailed explanations), intermediate (balanced), advanced (minimal explanations)',
426
+ },
427
+ },
428
+ required: ['level'],
429
+ },
430
+ },
431
+ {
432
+ name: 'get_experience_level',
433
+ description:
434
+ 'Get the current user experience level setting. Returns beginner, intermediate, or advanced. Use this at the start of building to know how much detail to include in explanations.',
435
+ inputSchema: {
436
+ type: 'object' as const,
437
+ properties: {},
438
+ },
439
+ },
380
440
  ],
381
441
  }));
382
442
 
@@ -410,6 +470,18 @@ class CodeBakersServer {
410
470
  case 'get_pattern_section':
411
471
  return this.handleGetPatternSection(args as { pattern: string; section: string });
412
472
 
473
+ case 'scaffold_project':
474
+ return this.handleScaffoldProject(args as { projectName: string; description?: string });
475
+
476
+ case 'init_project':
477
+ return this.handleInitProject(args as { projectName?: string });
478
+
479
+ case 'set_experience_level':
480
+ return this.handleSetExperienceLevel(args as { level: ExperienceLevel });
481
+
482
+ case 'get_experience_level':
483
+ return this.handleGetExperienceLevel();
484
+
413
485
  default:
414
486
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
415
487
  }
@@ -836,6 +908,362 @@ Show the user what their simple request was expanded into, then proceed with the
836
908
  };
837
909
  }
838
910
 
911
+ private async handleScaffoldProject(args: { projectName: string; description?: string }) {
912
+ const { projectName, description } = args;
913
+ const cwd = process.cwd();
914
+
915
+ // Check if directory has files
916
+ const files = fs.readdirSync(cwd);
917
+ const hasFiles = files.filter(f => !f.startsWith('.')).length > 0;
918
+
919
+ if (hasFiles) {
920
+ // Check if it's already a CodeBakers project
921
+ if (fs.existsSync(path.join(cwd, 'CLAUDE.md'))) {
922
+ return {
923
+ content: [{
924
+ type: 'text' as const,
925
+ text: `# Project Already Exists\n\nThis directory already has a CodeBakers project. Use the existing project or navigate to an empty directory.`,
926
+ }],
927
+ };
928
+ }
929
+ }
930
+
931
+ const results: string[] = [];
932
+ results.push(`# 🚀 Creating Project: ${projectName}\n`);
933
+
934
+ try {
935
+ // Create directories
936
+ const dirs = [
937
+ 'src/app',
938
+ 'src/components',
939
+ 'src/lib/supabase',
940
+ 'src/db',
941
+ 'src/db/migrations',
942
+ 'src/services',
943
+ 'src/types',
944
+ 'public',
945
+ ];
946
+
947
+ for (const dir of dirs) {
948
+ const dirPath = path.join(cwd, dir);
949
+ if (!fs.existsSync(dirPath)) {
950
+ fs.mkdirSync(dirPath, { recursive: true });
951
+ }
952
+ }
953
+ results.push('✓ Created directory structure');
954
+
955
+ // Write package.json
956
+ const packageJson = { ...templates.PACKAGE_JSON, name: projectName };
957
+ fs.writeFileSync(path.join(cwd, 'package.json'), JSON.stringify(packageJson, null, 2));
958
+ results.push('✓ Created package.json');
959
+
960
+ // Write config files
961
+ fs.writeFileSync(path.join(cwd, '.env.example'), templates.ENV_EXAMPLE);
962
+ fs.writeFileSync(path.join(cwd, '.env.local'), templates.ENV_EXAMPLE);
963
+ fs.writeFileSync(path.join(cwd, 'drizzle.config.ts'), templates.DRIZZLE_CONFIG);
964
+ fs.writeFileSync(path.join(cwd, 'tailwind.config.ts'), templates.TAILWIND_CONFIG);
965
+ fs.writeFileSync(path.join(cwd, 'postcss.config.mjs'), templates.POSTCSS_CONFIG);
966
+ fs.writeFileSync(path.join(cwd, 'tsconfig.json'), JSON.stringify(templates.TSCONFIG, null, 2));
967
+ fs.writeFileSync(path.join(cwd, 'next.config.ts'), templates.NEXT_CONFIG);
968
+ fs.writeFileSync(path.join(cwd, '.gitignore'), templates.GITIGNORE);
969
+ results.push('✓ Created configuration files');
970
+
971
+ // Write source files
972
+ fs.writeFileSync(path.join(cwd, 'src/lib/supabase/server.ts'), templates.SUPABASE_SERVER);
973
+ fs.writeFileSync(path.join(cwd, 'src/lib/supabase/client.ts'), templates.SUPABASE_CLIENT);
974
+ fs.writeFileSync(path.join(cwd, 'src/lib/supabase/middleware.ts'), templates.SUPABASE_MIDDLEWARE);
975
+ fs.writeFileSync(path.join(cwd, 'middleware.ts'), templates.MIDDLEWARE);
976
+ fs.writeFileSync(path.join(cwd, 'src/db/schema.ts'), templates.DB_SCHEMA);
977
+ fs.writeFileSync(path.join(cwd, 'src/db/index.ts'), templates.DB_INDEX);
978
+ fs.writeFileSync(path.join(cwd, 'src/app/globals.css'), templates.GLOBALS_CSS);
979
+ fs.writeFileSync(path.join(cwd, 'src/app/layout.tsx'), templates.LAYOUT_TSX);
980
+ fs.writeFileSync(path.join(cwd, 'src/app/page.tsx'), templates.PAGE_TSX);
981
+ fs.writeFileSync(path.join(cwd, 'src/lib/utils.ts'), templates.UTILS_CN);
982
+ results.push('✓ Created source files');
983
+
984
+ // Install dependencies
985
+ try {
986
+ execSync('npm install', { cwd, stdio: 'pipe' });
987
+ results.push('✓ Installed npm dependencies');
988
+ } catch {
989
+ results.push('⚠️ Could not install dependencies - run `npm install` manually');
990
+ }
991
+
992
+ // Now install CodeBakers patterns
993
+ results.push('\n## Installing CodeBakers Patterns...\n');
994
+
995
+ const response = await fetch(`${this.apiUrl}/api/content`, {
996
+ method: 'GET',
997
+ headers: { Authorization: `Bearer ${this.apiKey}` },
998
+ });
999
+
1000
+ if (response.ok) {
1001
+ const content = await response.json();
1002
+
1003
+ // Write CLAUDE.md
1004
+ if (content.router) {
1005
+ fs.writeFileSync(path.join(cwd, 'CLAUDE.md'), content.router);
1006
+ results.push('✓ Created CLAUDE.md (AI router)');
1007
+ }
1008
+
1009
+ // Write pattern modules
1010
+ if (content.modules && Object.keys(content.modules).length > 0) {
1011
+ const modulesDir = path.join(cwd, '.claude');
1012
+ if (!fs.existsSync(modulesDir)) {
1013
+ fs.mkdirSync(modulesDir, { recursive: true });
1014
+ }
1015
+ for (const [name, data] of Object.entries(content.modules)) {
1016
+ fs.writeFileSync(path.join(modulesDir, name), data as string);
1017
+ }
1018
+ results.push(`✓ Installed ${Object.keys(content.modules).length} pattern modules`);
1019
+ }
1020
+
1021
+ // Create PRD with description
1022
+ const date = new Date().toISOString().split('T')[0];
1023
+ const prdContent = `# Product Requirements Document
1024
+ # Project: ${projectName}
1025
+ # Created: ${date}
1026
+
1027
+ ## Overview
1028
+ **One-liner:** ${description || '[Describe this project in one sentence]'}
1029
+
1030
+ **Problem:** [What problem does this solve?]
1031
+
1032
+ **Solution:** [How does this solve it?]
1033
+
1034
+ ## Core Features (MVP)
1035
+ 1. [ ] **Feature 1:** [Description]
1036
+ 2. [ ] **Feature 2:** [Description]
1037
+ 3. [ ] **Feature 3:** [Description]
1038
+
1039
+ ## Technical Requirements
1040
+ - Framework: Next.js 14 (App Router)
1041
+ - Database: PostgreSQL + Drizzle ORM
1042
+ - Auth: Supabase Auth
1043
+ - UI: Tailwind CSS + shadcn/ui
1044
+
1045
+ ---
1046
+ <!-- AI: Reference this PRD when building features -->
1047
+ `;
1048
+ fs.writeFileSync(path.join(cwd, 'PRD.md'), prdContent);
1049
+ results.push('✓ Created PRD.md');
1050
+
1051
+ // Create other project files
1052
+ fs.writeFileSync(path.join(cwd, 'PROJECT-STATE.md'), `# PROJECT STATE
1053
+ # Last Updated: ${date}
1054
+
1055
+ ## Project Info
1056
+ name: ${projectName}
1057
+ phase: setup
1058
+
1059
+ ## In Progress
1060
+ ## Completed
1061
+ ## Next Up
1062
+ `);
1063
+ results.push('✓ Created PROJECT-STATE.md');
1064
+ }
1065
+
1066
+ results.push('\n---\n');
1067
+ results.push('## ✅ Project Created Successfully!\n');
1068
+ results.push('### Next Steps:\n');
1069
+ results.push('1. **Set up Supabase:** Go to https://supabase.com and create a free project');
1070
+ results.push('2. **Add credentials:** Copy your Supabase URL and anon key to `.env.local`');
1071
+ results.push('3. **Start building:** Just tell me what features you want!\n');
1072
+ results.push('### Example:\n');
1073
+ results.push('> "Add user authentication with email/password"');
1074
+ results.push('> "Create a dashboard with stats cards"');
1075
+ results.push('> "Build a todo list with CRUD operations"');
1076
+
1077
+ } catch (error) {
1078
+ const message = error instanceof Error ? error.message : 'Unknown error';
1079
+ results.push(`\n❌ Error: ${message}`);
1080
+ }
1081
+
1082
+ return {
1083
+ content: [{
1084
+ type: 'text' as const,
1085
+ text: results.join('\n'),
1086
+ }],
1087
+ };
1088
+ }
1089
+
1090
+ private async handleInitProject(args: { projectName?: string }) {
1091
+ const cwd = process.cwd();
1092
+ const results: string[] = [];
1093
+
1094
+ // Detect project name from package.json
1095
+ let projectName = args.projectName || 'my-project';
1096
+ try {
1097
+ const pkgPath = path.join(cwd, 'package.json');
1098
+ if (fs.existsSync(pkgPath)) {
1099
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
1100
+ projectName = pkg.name || projectName;
1101
+ }
1102
+ } catch {
1103
+ // Use default
1104
+ }
1105
+
1106
+ results.push(`# 🎨 Adding CodeBakers to: ${projectName}\n`);
1107
+
1108
+ // Check if already initialized
1109
+ if (fs.existsSync(path.join(cwd, 'CLAUDE.md'))) {
1110
+ results.push('⚠️ CLAUDE.md already exists. Updating patterns...\n');
1111
+ }
1112
+
1113
+ try {
1114
+ const response = await fetch(`${this.apiUrl}/api/content`, {
1115
+ method: 'GET',
1116
+ headers: { Authorization: `Bearer ${this.apiKey}` },
1117
+ });
1118
+
1119
+ if (!response.ok) {
1120
+ throw new Error('Failed to fetch patterns from API');
1121
+ }
1122
+
1123
+ const content = await response.json();
1124
+
1125
+ // Write CLAUDE.md
1126
+ if (content.router) {
1127
+ fs.writeFileSync(path.join(cwd, 'CLAUDE.md'), content.router);
1128
+ results.push('✓ Created/Updated CLAUDE.md');
1129
+ }
1130
+
1131
+ // Write pattern modules
1132
+ if (content.modules && Object.keys(content.modules).length > 0) {
1133
+ const modulesDir = path.join(cwd, '.claude');
1134
+ if (!fs.existsSync(modulesDir)) {
1135
+ fs.mkdirSync(modulesDir, { recursive: true });
1136
+ }
1137
+ for (const [name, data] of Object.entries(content.modules)) {
1138
+ fs.writeFileSync(path.join(modulesDir, name), data as string);
1139
+ }
1140
+ results.push(`✓ Installed ${Object.keys(content.modules).length} pattern modules (v${content.version})`);
1141
+ }
1142
+
1143
+ // Create PRD if doesn't exist
1144
+ const date = new Date().toISOString().split('T')[0];
1145
+ const prdPath = path.join(cwd, 'PRD.md');
1146
+ if (!fs.existsSync(prdPath)) {
1147
+ fs.writeFileSync(prdPath, `# Product Requirements Document
1148
+ # Project: ${projectName}
1149
+ # Created: ${date}
1150
+
1151
+ ## Overview
1152
+ **One-liner:** [Describe this project]
1153
+
1154
+ ## Core Features (MVP)
1155
+ 1. [ ] **Feature 1:** [Description]
1156
+ 2. [ ] **Feature 2:** [Description]
1157
+ `);
1158
+ results.push('✓ Created PRD.md template');
1159
+ }
1160
+
1161
+ // Create PROJECT-STATE if doesn't exist
1162
+ const statePath = path.join(cwd, 'PROJECT-STATE.md');
1163
+ if (!fs.existsSync(statePath)) {
1164
+ fs.writeFileSync(statePath, `# PROJECT STATE
1165
+ # Last Updated: ${date}
1166
+
1167
+ ## Project Info
1168
+ name: ${projectName}
1169
+ phase: development
1170
+
1171
+ ## In Progress
1172
+ ## Completed
1173
+ ## Next Up
1174
+ `);
1175
+ results.push('✓ Created PROJECT-STATE.md');
1176
+ }
1177
+
1178
+ // Update .gitignore
1179
+ const gitignorePath = path.join(cwd, '.gitignore');
1180
+ if (fs.existsSync(gitignorePath)) {
1181
+ const gitignore = fs.readFileSync(gitignorePath, 'utf-8');
1182
+ if (!gitignore.includes('.claude/')) {
1183
+ fs.writeFileSync(gitignorePath, gitignore + '\n# CodeBakers\n.claude/\n');
1184
+ results.push('✓ Updated .gitignore');
1185
+ }
1186
+ }
1187
+
1188
+ results.push('\n---\n');
1189
+ results.push('## ✅ CodeBakers Patterns Installed!\n');
1190
+ results.push('The AI now has access to production patterns for:');
1191
+ results.push('- Authentication, Database, API design');
1192
+ results.push('- Frontend components, Forms, Validation');
1193
+ results.push('- Payments, Email, Real-time features');
1194
+ results.push('- And 30+ more specialized patterns\n');
1195
+ results.push('Just describe what you want to build!');
1196
+
1197
+ } catch (error) {
1198
+ const message = error instanceof Error ? error.message : 'Unknown error';
1199
+ results.push(`\n❌ Error: ${message}`);
1200
+ }
1201
+
1202
+ return {
1203
+ content: [{
1204
+ type: 'text' as const,
1205
+ text: results.join('\n'),
1206
+ }],
1207
+ };
1208
+ }
1209
+
1210
+ private handleSetExperienceLevel(args: { level: ExperienceLevel }) {
1211
+ const { level } = args;
1212
+
1213
+ // Validate level
1214
+ if (!['beginner', 'intermediate', 'advanced'].includes(level)) {
1215
+ return {
1216
+ content: [{
1217
+ type: 'text' as const,
1218
+ text: `❌ Invalid experience level: "${level}". Must be: beginner, intermediate, or advanced.`,
1219
+ }],
1220
+ };
1221
+ }
1222
+
1223
+ setExperienceLevel(level);
1224
+
1225
+ const descriptions: Record<ExperienceLevel, string> = {
1226
+ beginner: '🎓 **Beginner Mode**\n\nI will:\n- Explain concepts as I go\n- Break down complex steps\n- Provide more context about what each piece of code does\n- Suggest learning resources when relevant',
1227
+ intermediate: '⚡ **Intermediate Mode**\n\nI will:\n- Provide balanced explanations\n- Focus on the "why" behind decisions\n- Skip basic explanations you already know',
1228
+ advanced: '🚀 **Advanced Mode**\n\nI will:\n- Skip explanations, just build\n- Focus on efficiency and best practices\n- Assume you know the fundamentals\n- Get straight to the code',
1229
+ };
1230
+
1231
+ return {
1232
+ content: [{
1233
+ type: 'text' as const,
1234
+ text: `✅ Experience level set to: **${level}**\n\n${descriptions[level]}`,
1235
+ }],
1236
+ };
1237
+ }
1238
+
1239
+ private handleGetExperienceLevel() {
1240
+ const level = getExperienceLevel();
1241
+
1242
+ const modeInfo: Record<ExperienceLevel, { emoji: string; description: string }> = {
1243
+ beginner: {
1244
+ emoji: '🎓',
1245
+ description: 'Detailed explanations, step-by-step guidance'
1246
+ },
1247
+ intermediate: {
1248
+ emoji: '⚡',
1249
+ description: 'Balanced explanations, focus on decisions'
1250
+ },
1251
+ advanced: {
1252
+ emoji: '🚀',
1253
+ description: 'Minimal explanations, straight to code'
1254
+ },
1255
+ };
1256
+
1257
+ const info = modeInfo[level];
1258
+
1259
+ return {
1260
+ content: [{
1261
+ type: 'text' as const,
1262
+ text: `# Current Experience Level\n\n${info.emoji} **${level.charAt(0).toUpperCase() + level.slice(1)}**\n${info.description}\n\n---\n\nTo change, use: \`set_experience_level\` with "beginner", "intermediate", or "advanced"`,
1263
+ }],
1264
+ };
1265
+ }
1266
+
839
1267
  async run(): Promise<void> {
840
1268
  const transport = new StdioServerTransport();
841
1269
  await this.server.connect(transport);