@codebakers/cli 1.1.4 → 1.1.6

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 (44) hide show
  1. package/dist/commands/doctor.d.ts +8 -0
  2. package/dist/commands/doctor.js +218 -0
  3. package/dist/commands/init.d.ts +4 -0
  4. package/dist/commands/init.js +772 -0
  5. package/dist/commands/install-hook.d.ts +12 -0
  6. package/dist/commands/install-hook.js +193 -0
  7. package/dist/commands/install.d.ts +1 -0
  8. package/dist/commands/install.js +81 -0
  9. package/dist/commands/login.d.ts +1 -0
  10. package/dist/commands/login.js +54 -0
  11. package/dist/commands/mcp-config.d.ts +6 -0
  12. package/dist/commands/mcp-config.js +209 -0
  13. package/dist/commands/serve.d.ts +1 -0
  14. package/dist/commands/serve.js +26 -0
  15. package/dist/commands/setup.d.ts +1 -0
  16. package/dist/commands/setup.js +92 -0
  17. package/dist/commands/status.d.ts +1 -0
  18. package/dist/commands/status.js +49 -0
  19. package/dist/commands/uninstall.d.ts +1 -0
  20. package/dist/commands/uninstall.js +50 -0
  21. package/dist/config.d.ts +5 -0
  22. package/dist/config.js +33 -0
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.js +71 -1026
  25. package/dist/mcp/server.d.ts +2 -0
  26. package/dist/mcp/server.js +544 -0
  27. package/package.json +16 -38
  28. package/src/commands/doctor.ts +231 -0
  29. package/src/commands/init.ts +827 -0
  30. package/src/commands/install-hook.ts +207 -0
  31. package/src/commands/install.ts +94 -0
  32. package/src/commands/login.ts +56 -0
  33. package/src/commands/mcp-config.ts +235 -0
  34. package/src/commands/serve.ts +23 -0
  35. package/src/commands/setup.ts +104 -0
  36. package/src/commands/status.ts +48 -0
  37. package/src/commands/uninstall.ts +49 -0
  38. package/src/config.ts +34 -0
  39. package/src/index.ts +87 -0
  40. package/src/mcp/server.ts +617 -0
  41. package/tsconfig.json +16 -0
  42. package/README.md +0 -89
  43. package/dist/chunk-7CKLRE2H.js +0 -36
  44. package/dist/config-R2H6JKGW.js +0 -16
@@ -0,0 +1,827 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import { createInterface } from 'readline';
4
+ import { writeFileSync, mkdirSync, existsSync, readFileSync, readdirSync, statSync } from 'fs';
5
+ import { join, extname } from 'path';
6
+ import { setApiKey, getApiKey, getApiUrl } from '../config.js';
7
+
8
+ // Enhanced .cursorrules with pre-flight and self-review automation
9
+ const CURSORRULES_TEMPLATE = `# CODEBAKERS CURSOR RULES
10
+ # Zero-friction AI assistance - everything is automatic
11
+
12
+ ## ON EVERY MESSAGE - AUTOMATIC WORKFLOW
13
+
14
+ ### PHASE 1: CONTEXT LOAD (automatic)
15
+ 1. Read CLAUDE.md → Load router
16
+ 2. Read PRD.md → Understand what we're building
17
+ 3. Read PROJECT-CONTEXT.md → Understand codebase
18
+ 4. Read PROJECT-STATE.md → Check what's in progress
19
+ 5. Read DECISIONS.md → Know past decisions
20
+
21
+ ### PHASE 2: PRE-FLIGHT CHECK (before writing code)
22
+ Ask yourself silently:
23
+ - [ ] What existing code does this touch? (check PROJECT-CONTEXT.md)
24
+ - [ ] Is similar code already in the codebase? (copy that pattern)
25
+ - [ ] What's the data model involved?
26
+ - [ ] What are the error cases?
27
+ - [ ] Is someone else working on this? (check PROJECT-STATE.md)
28
+
29
+ If PROJECT-CONTEXT.md is empty or stale (>7 days), SCAN THE PROJECT FIRST:
30
+ - Read package.json for dependencies
31
+ - Check src/ structure
32
+ - Note existing patterns
33
+ - Update PROJECT-CONTEXT.md
34
+
35
+ ### PHASE 3: EXECUTE
36
+ - State: \`📋 CodeBakers | [Type] | Modules: [list]\`
37
+ - Load required modules from .claude/
38
+ - Follow patterns EXACTLY
39
+
40
+ ### PHASE 4: SELF-REVIEW (before saying "done")
41
+ - [ ] TypeScript compiles? (npx tsc --noEmit)
42
+ - [ ] Imports resolve correctly?
43
+ - [ ] Error handling exists?
44
+ - [ ] Matches existing patterns in codebase?
45
+ - [ ] Tests written?
46
+ - [ ] PROJECT-STATE.md updated?
47
+
48
+ If ANY check fails, fix it before responding.
49
+
50
+ ### PHASE 5: UPDATE STATE
51
+ - Update PROJECT-STATE.md with completed work
52
+ - Add to DECISIONS.md if architectural choice was made
53
+
54
+ ## MULTI-AGENT AWARENESS
55
+ - ALWAYS check PROJECT-STATE.md "In Progress" section
56
+ - Do NOT duplicate work another agent is doing
57
+ - If conflict detected, STOP and ask user
58
+
59
+ ## REMEMBER
60
+ - You are a full product team, not just a code assistant
61
+ - The modules contain production-tested patterns — USE THEM
62
+ - When in doubt, check existing code first
63
+ `;
64
+
65
+ const CURSORIGNORE_TEMPLATE = `# CodeBakers - Files to ignore in Cursor context
66
+
67
+ # Dependencies
68
+ node_modules/
69
+ .pnpm-store/
70
+ bower_components/
71
+
72
+ # Build outputs
73
+ dist/
74
+ build/
75
+ .next/
76
+ .nuxt/
77
+ .output/
78
+ out/
79
+
80
+ # Cache
81
+ .cache/
82
+ .turbo/
83
+ .eslintcache
84
+ .prettiercache
85
+ *.tsbuildinfo
86
+
87
+ # Logs
88
+ logs/
89
+ *.log
90
+ npm-debug.log*
91
+ yarn-debug.log*
92
+ yarn-error.log*
93
+
94
+ # Environment files (don't leak secrets)
95
+ .env
96
+ .env.local
97
+ .env.*.local
98
+ .env.production
99
+
100
+ # IDE
101
+ .idea/
102
+ *.swp
103
+ *.swo
104
+
105
+ # OS
106
+ .DS_Store
107
+ Thumbs.db
108
+
109
+ # Test coverage
110
+ coverage/
111
+ .nyc_output/
112
+
113
+ # Package locks (large files, not needed for context)
114
+ package-lock.json
115
+ yarn.lock
116
+ pnpm-lock.yaml
117
+
118
+ # Generated files
119
+ *.min.js
120
+ *.min.css
121
+ *.map
122
+ `;
123
+
124
+ const VSCODE_SETTINGS_TEMPLATE = {
125
+ "cursor.chat.defaultContext": [
126
+ "CLAUDE.md",
127
+ "PRD.md",
128
+ "PROJECT-CONTEXT.md",
129
+ "PROJECT-STATE.md",
130
+ "DECISIONS.md"
131
+ ],
132
+ "cursor.chat.alwaysIncludeRules": true,
133
+ "cursor.composer.alwaysIncludeRules": true,
134
+ "cursor.general.enableAutoImport": true
135
+ };
136
+
137
+ function createPrdTemplate(projectName: string, projectType: string): string {
138
+ const date = new Date().toISOString().split('T')[0];
139
+
140
+ const typeSpecificSections = projectType === 'client'
141
+ ? `
142
+ ## Client Info
143
+ - Client Name: [Who is this for?]
144
+ - Contact: [Primary contact]
145
+ - Deadline: [When is this due?]
146
+ - Budget: [If relevant]
147
+ `
148
+ : projectType === 'business'
149
+ ? `
150
+ ## Business Context
151
+ - Target Market: [Who are you selling to?]
152
+ - Revenue Model: [How does this make money?]
153
+ - Competition: [Who are you competing with?]
154
+ - MVP Deadline: [When do you need to launch?]
155
+ `
156
+ : `
157
+ ## Personal Goals
158
+ - Why am I building this? [Your motivation]
159
+ - Learning goals: [What do you want to learn?]
160
+ - Time commitment: [Hours per week?]
161
+ `;
162
+
163
+ return `# Product Requirements Document
164
+ # Project: ${projectName}
165
+ # Created: ${date}
166
+ # Type: ${projectType}
167
+
168
+ ## Overview
169
+ **One-liner:** [Describe this project in one sentence]
170
+
171
+ **Problem:** [What problem does this solve?]
172
+
173
+ **Solution:** [How does this solve it?]
174
+ ${typeSpecificSections}
175
+ ## Target Users
176
+ - **Primary:** [Who is the main user?]
177
+ - **Secondary:** [Other users?]
178
+
179
+ ## Core Features (MVP)
180
+ <!-- List the MINIMUM features needed to launch -->
181
+ <!-- AI will build these first -->
182
+
183
+ 1. [ ] **Feature 1:** [Description]
184
+ - Acceptance criteria: [How do we know it's done?]
185
+
186
+ 2. [ ] **Feature 2:** [Description]
187
+ - Acceptance criteria: [How do we know it's done?]
188
+
189
+ 3. [ ] **Feature 3:** [Description]
190
+ - Acceptance criteria: [How do we know it's done?]
191
+
192
+ ## Nice-to-Have Features (Post-MVP)
193
+ <!-- Features to add after MVP is working -->
194
+
195
+ 1. [ ] [Feature description]
196
+ 2. [ ] [Feature description]
197
+
198
+ ## Technical Requirements
199
+ <!-- AI will use these to make architecture decisions -->
200
+
201
+ - **Must use:** [Required technologies, APIs, etc.]
202
+ - **Must avoid:** [Things you don't want]
203
+ - **Performance:** [Any speed/scale requirements?]
204
+ - **Security:** [Auth requirements, data sensitivity?]
205
+
206
+ ## User Flows
207
+ <!-- Describe the main user journeys -->
208
+
209
+ ### Flow 1: [Name]
210
+ 1. User does X
211
+ 2. System responds with Y
212
+ 3. User sees Z
213
+
214
+ ### Flow 2: [Name]
215
+ 1. User does X
216
+ 2. System responds with Y
217
+ 3. User sees Z
218
+
219
+ ## Data Model (if known)
220
+ <!-- Rough idea of main entities -->
221
+
222
+ - **User:** [fields]
223
+ - **[Entity 2]:** [fields]
224
+ - **[Entity 3]:** [fields]
225
+
226
+ ## Success Metrics
227
+ - [ ] [How will you measure success?]
228
+ - [ ] [What does "done" look like?]
229
+
230
+ ## Open Questions
231
+ <!-- Things you're unsure about - AI can help clarify -->
232
+
233
+ 1. [Question?]
234
+ 2. [Question?]
235
+
236
+ ---
237
+ <!-- AI INSTRUCTIONS -->
238
+ <!-- When building features, reference this PRD -->
239
+ <!-- Check off features as they're completed -->
240
+ <!-- Add new questions to Open Questions -->
241
+ <!-- Update this doc as requirements change -->
242
+ `;
243
+ }
244
+
245
+ interface ContentResponse {
246
+ version: string;
247
+ router: string;
248
+ modules: Record<string, string>;
249
+ }
250
+
251
+ type ProjectType = 'personal' | 'client' | 'business';
252
+
253
+ async function prompt(question: string): Promise<string> {
254
+ const rl = createInterface({
255
+ input: process.stdin,
256
+ output: process.stdout,
257
+ });
258
+
259
+ return new Promise((resolve) => {
260
+ rl.question(question, (answer) => {
261
+ rl.close();
262
+ resolve(answer);
263
+ });
264
+ });
265
+ }
266
+
267
+ async function confirm(question: string): Promise<boolean> {
268
+ const answer = await prompt(`${question} (Y/n): `);
269
+ return answer.toLowerCase() !== 'n';
270
+ }
271
+
272
+ async function selectProjectType(): Promise<{ type: ProjectType; name: string }> {
273
+ console.log(chalk.white('\n What kind of project is this?\n'));
274
+ console.log(chalk.gray(' 1. ') + chalk.cyan('PERSONAL') + chalk.gray(' - Just building for myself'));
275
+ console.log(chalk.gray(' 2. ') + chalk.cyan('CLIENT') + chalk.gray(' - Building for someone else'));
276
+ console.log(chalk.gray(' 3. ') + chalk.cyan('BUSINESS') + chalk.gray(' - My own product/startup\n'));
277
+
278
+ let typeChoice = '';
279
+ while (!['1', '2', '3'].includes(typeChoice)) {
280
+ typeChoice = await prompt(' Enter 1, 2, or 3: ');
281
+ }
282
+
283
+ const typeMap: Record<string, ProjectType> = {
284
+ '1': 'personal',
285
+ '2': 'client',
286
+ '3': 'business'
287
+ };
288
+
289
+ const cwd = process.cwd();
290
+ const defaultName = cwd.split(/[\\/]/).pop() || 'my-project';
291
+ const name = await prompt(` Project name (${defaultName}): `) || defaultName;
292
+
293
+ return {
294
+ type: typeMap[typeChoice],
295
+ name
296
+ };
297
+ }
298
+
299
+ function createProjectState(projectName: string, projectType: string): string {
300
+ const date = new Date().toISOString().split('T')[0];
301
+ return `# PROJECT STATE
302
+ # Last Updated: ${date}
303
+ # Auto-maintained by AI - update when starting/completing tasks
304
+
305
+ ## Project Info
306
+ name: ${projectName}
307
+ type: ${projectType}
308
+ phase: planning
309
+
310
+ ## Current Sprint
311
+ Goal: [AI will fill this based on conversation]
312
+
313
+ ## In Progress
314
+ <!-- AI: Add tasks here when you START working on them -->
315
+ <!-- Format: - [task] (started: date, agent: cursor/claude) -->
316
+
317
+ ## Completed
318
+ <!-- AI: Move tasks here when DONE -->
319
+ <!-- Format: - [task] (completed: date) -->
320
+
321
+ ## Blockers
322
+ <!-- AI: List anything blocking progress -->
323
+
324
+ ## Next Up
325
+ <!-- AI: Queue of upcoming tasks -->
326
+ `;
327
+ }
328
+
329
+ function createProjectContext(projectName: string, projectType: string): string {
330
+ const date = new Date().toISOString().split('T')[0];
331
+ return `# PROJECT CONTEXT
332
+ # Last Scanned: ${date}
333
+ # AI: Update this when you first analyze the project or when structure changes significantly
334
+
335
+ ## Overview
336
+ name: ${projectName}
337
+ type: ${projectType}
338
+ description: [AI will fill after scanning]
339
+
340
+ ## Tech Stack
341
+ <!-- AI: Fill this by reading package.json and checking file extensions -->
342
+ framework:
343
+ language:
344
+ database:
345
+ auth:
346
+ styling:
347
+ testing:
348
+
349
+ ## Project Structure
350
+ <!-- AI: Fill this by scanning the directory structure -->
351
+ \`\`\`
352
+ [AI will map the project structure here]
353
+ \`\`\`
354
+
355
+ ## Key Files
356
+ <!-- AI: List the most important files for understanding the project -->
357
+ - Entry point:
358
+ - Config:
359
+ - Database schema:
360
+ - API routes:
361
+ - Components:
362
+
363
+ ## Existing Patterns
364
+ <!-- AI: Document patterns you find so you can reuse them -->
365
+
366
+ ### API Route Pattern
367
+ \`\`\`typescript
368
+ [AI: Copy an example API route pattern from this project]
369
+ \`\`\`
370
+
371
+ ### Component Pattern
372
+ \`\`\`typescript
373
+ [AI: Copy an example component pattern from this project]
374
+ \`\`\`
375
+
376
+ ### Database Query Pattern
377
+ \`\`\`typescript
378
+ [AI: Copy an example database query pattern from this project]
379
+ \`\`\`
380
+
381
+ ## Environment Variables
382
+ <!-- AI: List required env vars (don't include values!) -->
383
+ - [ ] DATABASE_URL
384
+ - [ ] [others...]
385
+
386
+ ## Notes
387
+ <!-- AI: Any important context about this specific project -->
388
+ `;
389
+ }
390
+
391
+ function createDecisionsLog(projectName: string): string {
392
+ const date = new Date().toISOString().split('T')[0];
393
+ return `# ARCHITECTURAL DECISIONS
394
+ # Project: ${projectName}
395
+ # AI: Add entries here when making significant technical choices
396
+
397
+ ## How to Use This File
398
+ When you make a decision that affects architecture, add an entry:
399
+ - Date
400
+ - Decision
401
+ - Reason
402
+ - Alternatives considered
403
+ - Pattern location (if applicable)
404
+
405
+ ---
406
+
407
+ ## ${date}: Project Initialized
408
+ **Decision:** Using CodeBakers pattern system
409
+ **Reason:** Ensure consistent, production-quality code
410
+ **Pattern:** See .claude/ folder for all patterns
411
+
412
+ ---
413
+
414
+ <!-- AI: Add new decisions above this line -->
415
+ `;
416
+ }
417
+
418
+ /**
419
+ * Auto-scan project to detect tech stack
420
+ */
421
+ function scanProject(cwd: string): { stack: Record<string, string>; structure: string } {
422
+ const stack: Record<string, string> = {};
423
+ let structure = '';
424
+
425
+ // Check package.json
426
+ const packageJsonPath = join(cwd, 'package.json');
427
+ if (existsSync(packageJsonPath)) {
428
+ try {
429
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
430
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
431
+
432
+ // Detect framework
433
+ if (deps['next']) stack.framework = `Next.js ${deps['next']}`;
434
+ else if (deps['react']) stack.framework = `React ${deps['react']}`;
435
+ else if (deps['vue']) stack.framework = `Vue ${deps['vue']}`;
436
+ else if (deps['express']) stack.framework = `Express ${deps['express']}`;
437
+
438
+ // Detect database
439
+ if (deps['drizzle-orm']) stack.database = 'Drizzle ORM';
440
+ else if (deps['prisma']) stack.database = 'Prisma';
441
+ else if (deps['mongoose']) stack.database = 'MongoDB/Mongoose';
442
+ else if (deps['pg']) stack.database = 'PostgreSQL';
443
+
444
+ // Detect auth
445
+ if (deps['@supabase/supabase-js']) stack.auth = 'Supabase Auth';
446
+ else if (deps['next-auth']) stack.auth = 'NextAuth.js';
447
+ else if (deps['@clerk/nextjs']) stack.auth = 'Clerk';
448
+
449
+ // Detect styling
450
+ if (deps['tailwindcss']) stack.styling = 'Tailwind CSS';
451
+ else if (deps['styled-components']) stack.styling = 'Styled Components';
452
+
453
+ // Detect testing
454
+ if (deps['vitest']) stack.testing = 'Vitest';
455
+ else if (deps['jest']) stack.testing = 'Jest';
456
+ else if (deps['@playwright/test']) stack.testing = 'Playwright';
457
+
458
+ // Detect language
459
+ if (deps['typescript'] || existsSync(join(cwd, 'tsconfig.json'))) {
460
+ stack.language = 'TypeScript';
461
+ } else {
462
+ stack.language = 'JavaScript';
463
+ }
464
+ } catch {
465
+ // Ignore parse errors
466
+ }
467
+ }
468
+
469
+ // Build simple structure
470
+ try {
471
+ const items = readdirSync(cwd);
472
+ const dirs: string[] = [];
473
+ const files: string[] = [];
474
+
475
+ for (const item of items) {
476
+ if (item.startsWith('.') || item === 'node_modules') continue;
477
+ const fullPath = join(cwd, item);
478
+ try {
479
+ if (statSync(fullPath).isDirectory()) {
480
+ dirs.push(item + '/');
481
+ } else {
482
+ files.push(item);
483
+ }
484
+ } catch {
485
+ // Skip inaccessible items
486
+ }
487
+ }
488
+
489
+ structure = [...dirs.sort(), ...files.sort()].join('\n');
490
+ } catch {
491
+ structure = '[Could not scan structure]';
492
+ }
493
+
494
+ return { stack, structure };
495
+ }
496
+
497
+ function updateProjectContextWithScan(contextContent: string, stack: Record<string, string>, structure: string): string {
498
+ let updated = contextContent;
499
+
500
+ // Update tech stack
501
+ if (stack.framework) updated = updated.replace('framework: ', `framework: ${stack.framework}`);
502
+ if (stack.language) updated = updated.replace('language: ', `language: ${stack.language}`);
503
+ if (stack.database) updated = updated.replace('database: ', `database: ${stack.database}`);
504
+ if (stack.auth) updated = updated.replace('auth: ', `auth: ${stack.auth}`);
505
+ if (stack.styling) updated = updated.replace('styling: ', `styling: ${stack.styling}`);
506
+ if (stack.testing) updated = updated.replace('testing: ', `testing: ${stack.testing}`);
507
+
508
+ // Update structure
509
+ updated = updated.replace(
510
+ '[AI will map the project structure here]',
511
+ structure || '[Empty project]'
512
+ );
513
+
514
+ return updated;
515
+ }
516
+
517
+ /**
518
+ * Interactive init command - walks users through complete setup
519
+ */
520
+ export async function init(): Promise<void> {
521
+ console.log(chalk.blue(`
522
+ ╔═══════════════════════════════════════════════════════════╗
523
+ ║ ║
524
+ ║ ${chalk.bold('Welcome to CodeBakers!')} ║
525
+ ║ ║
526
+ ║ Production-grade patterns for AI-assisted development ║
527
+ ║ ║
528
+ ╚═══════════════════════════════════════════════════════════╝
529
+ `));
530
+
531
+ console.log(chalk.gray(' This wizard will set up CodeBakers in your project.\n'));
532
+
533
+ const cwd = process.cwd();
534
+
535
+ // Check if already initialized
536
+ const claudeMdPath = join(cwd, 'CLAUDE.md');
537
+ if (existsSync(claudeMdPath)) {
538
+ const reinitialize = await confirm(' CLAUDE.md already exists. Reinitialize?');
539
+ if (!reinitialize) {
540
+ console.log(chalk.yellow('\n Skipping. Run with a fresh project or delete CLAUDE.md first.\n'));
541
+ return;
542
+ }
543
+ }
544
+
545
+ // Step 1: Get project type
546
+ const { type: projectType, name: projectName } = await selectProjectType();
547
+ console.log(chalk.green(`\n ✓ Setting up ${projectName} as ${projectType.toUpperCase()} project\n`));
548
+
549
+ // Step 2: Check if already logged in
550
+ let apiKey = getApiKey();
551
+
552
+ if (apiKey) {
553
+ console.log(chalk.green(' ✓ Already logged in\n'));
554
+ const useExisting = await confirm(' Use existing API key?');
555
+ if (!useExisting) {
556
+ apiKey = null;
557
+ }
558
+ }
559
+
560
+ // Step 3: Login if needed
561
+ if (!apiKey) {
562
+ console.log(chalk.white('\n Step 1: Get your API key\n'));
563
+ console.log(chalk.gray(' Go to: ') + chalk.cyan('https://codebakers.ai/dashboard'));
564
+ console.log(chalk.gray(' Copy your API key (starts with cb_)\n'));
565
+
566
+ apiKey = await prompt(' Paste your API key: ');
567
+
568
+ if (!apiKey || !apiKey.startsWith('cb_')) {
569
+ console.log(chalk.red('\n ✗ Invalid API key. Keys start with "cb_"\n'));
570
+ console.log(chalk.gray(' Get your key at https://codebakers.ai/dashboard\n'));
571
+ process.exit(1);
572
+ }
573
+
574
+ const spinner = ora(' Validating API key...').start();
575
+
576
+ try {
577
+ const apiUrl = getApiUrl();
578
+ const response = await fetch(`${apiUrl}/api/content`, {
579
+ method: 'GET',
580
+ headers: {
581
+ Authorization: `Bearer ${apiKey}`,
582
+ },
583
+ });
584
+
585
+ if (!response.ok) {
586
+ const error = await response.json().catch(() => ({}));
587
+ throw new Error(error.error || 'Invalid API key');
588
+ }
589
+
590
+ setApiKey(apiKey);
591
+ spinner.succeed('API key valid!');
592
+ } catch (error) {
593
+ spinner.fail('Invalid API key');
594
+ const message = error instanceof Error ? error.message : 'Unknown error';
595
+ console.log(chalk.red(`\n Error: ${message}\n`));
596
+ process.exit(1);
597
+ }
598
+ }
599
+
600
+ // Step 4: Install patterns from API
601
+ console.log(chalk.white('\n Step 2: Installing patterns\n'));
602
+
603
+ const spinner = ora(' Downloading patterns...').start();
604
+
605
+ try {
606
+ const apiUrl = getApiUrl();
607
+ const response = await fetch(`${apiUrl}/api/content`, {
608
+ method: 'GET',
609
+ headers: {
610
+ Authorization: `Bearer ${apiKey}`,
611
+ },
612
+ });
613
+
614
+ if (!response.ok) {
615
+ const error = await response.json().catch(() => ({}));
616
+ throw new Error(error.error || 'Failed to fetch content');
617
+ }
618
+
619
+ const content: ContentResponse = await response.json();
620
+ spinner.text = ' Installing patterns...';
621
+
622
+ // Write router file (CLAUDE.md)
623
+ if (content.router) {
624
+ writeFileSync(join(cwd, 'CLAUDE.md'), content.router);
625
+ }
626
+
627
+ // Write modules to .claude/
628
+ if (content.modules && Object.keys(content.modules).length > 0) {
629
+ const modulesDir = join(cwd, '.claude');
630
+ if (!existsSync(modulesDir)) {
631
+ mkdirSync(modulesDir, { recursive: true });
632
+ }
633
+
634
+ for (const [name, data] of Object.entries(content.modules)) {
635
+ writeFileSync(join(modulesDir, name), data);
636
+ }
637
+ }
638
+
639
+ spinner.succeed('Patterns installed!');
640
+ console.log(chalk.gray(`\n Version: ${content.version}`));
641
+ console.log(chalk.gray(` Modules: ${Object.keys(content.modules || {}).length} pattern files`));
642
+
643
+ } catch (error) {
644
+ spinner.fail('Pattern installation failed');
645
+ const message = error instanceof Error ? error.message : 'Unknown error';
646
+ console.log(chalk.red(`\n Error: ${message}\n`));
647
+ process.exit(1);
648
+ }
649
+
650
+ // Step 5: Auto-scan project
651
+ console.log(chalk.white('\n Step 3: Scanning project\n'));
652
+
653
+ const scanSpinner = ora(' Analyzing project structure...').start();
654
+ const { stack, structure } = scanProject(cwd);
655
+
656
+ const detectedItems = Object.entries(stack).filter(([_, v]) => v).map(([k, v]) => `${k}: ${v}`);
657
+ if (detectedItems.length > 0) {
658
+ scanSpinner.succeed('Project analyzed!');
659
+ console.log(chalk.gray('\n Detected:'));
660
+ for (const item of detectedItems) {
661
+ console.log(chalk.gray(` ${item}`));
662
+ }
663
+ } else {
664
+ scanSpinner.succeed('Project scanned (new project detected)');
665
+ }
666
+
667
+ // Step 6: Create PROJECT-CONTEXT.md with scan results
668
+ console.log(chalk.white('\n Step 4: Setting up project files\n'));
669
+
670
+ const filesSpinner = ora(' Creating project files...').start();
671
+
672
+ try {
673
+ // PROJECT-CONTEXT.md
674
+ let contextContent = createProjectContext(projectName, projectType);
675
+ contextContent = updateProjectContextWithScan(contextContent, stack, structure);
676
+ writeFileSync(join(cwd, 'PROJECT-CONTEXT.md'), contextContent);
677
+
678
+ // PROJECT-STATE.md
679
+ const stateContent = createProjectState(projectName, projectType);
680
+ writeFileSync(join(cwd, 'PROJECT-STATE.md'), stateContent);
681
+
682
+ // DECISIONS.md
683
+ const decisionsContent = createDecisionsLog(projectName);
684
+ writeFileSync(join(cwd, 'DECISIONS.md'), decisionsContent);
685
+
686
+ filesSpinner.succeed('Project files created!');
687
+ } catch (error) {
688
+ filesSpinner.warn('Some project files could not be created');
689
+ }
690
+
691
+ // Step 7: Install Cursor files
692
+ console.log(chalk.white('\n Step 5: Setting up Cursor IDE\n'));
693
+
694
+ const cursorSpinner = ora(' Installing Cursor configuration...').start();
695
+
696
+ try {
697
+ // Write .cursorrules
698
+ writeFileSync(join(cwd, '.cursorrules'), CURSORRULES_TEMPLATE);
699
+
700
+ // Write .cursorignore
701
+ writeFileSync(join(cwd, '.cursorignore'), CURSORIGNORE_TEMPLATE);
702
+
703
+ // Create/merge .vscode/settings.json
704
+ const vscodeDir = join(cwd, '.vscode');
705
+ if (!existsSync(vscodeDir)) {
706
+ mkdirSync(vscodeDir, { recursive: true });
707
+ }
708
+
709
+ const existingSettingsPath = join(vscodeDir, 'settings.json');
710
+
711
+ if (existsSync(existingSettingsPath)) {
712
+ const existing = JSON.parse(readFileSync(existingSettingsPath, 'utf-8'));
713
+ const merged = { ...existing, ...VSCODE_SETTINGS_TEMPLATE };
714
+ writeFileSync(existingSettingsPath, JSON.stringify(merged, null, 2));
715
+ } else {
716
+ writeFileSync(existingSettingsPath, JSON.stringify(VSCODE_SETTINGS_TEMPLATE, null, 2));
717
+ }
718
+
719
+ cursorSpinner.succeed('Cursor configuration installed!');
720
+ } catch (error) {
721
+ cursorSpinner.warn('Could not install Cursor files (continuing anyway)');
722
+ }
723
+
724
+ // Step 8: Add to .gitignore if not present
725
+ const gitignorePath = join(cwd, '.gitignore');
726
+ if (existsSync(gitignorePath)) {
727
+ const gitignore = readFileSync(gitignorePath, 'utf-8');
728
+ if (!gitignore.includes('.cursorrules')) {
729
+ const additions = '\n# CodeBakers (encoded patterns)\n.cursorrules\n.claude/\n';
730
+ writeFileSync(gitignorePath, gitignore + additions);
731
+ }
732
+ }
733
+
734
+ // Step 9: PRD Setup
735
+ console.log(chalk.white('\n Step 6: Product Requirements\n'));
736
+
737
+ const prdPath = join(cwd, 'PRD.md');
738
+ let prdCreated = false;
739
+
740
+ if (existsSync(prdPath)) {
741
+ console.log(chalk.green(' ✓ PRD.md already exists\n'));
742
+ prdCreated = true;
743
+ } else {
744
+ console.log(chalk.gray(' A PRD helps the AI understand what you\'re building.\n'));
745
+ console.log(chalk.white(' How would you like to set up your PRD?\n'));
746
+ console.log(chalk.gray(' 1. ') + chalk.cyan('CREATE TEMPLATE') + chalk.gray(' - I\'ll fill it out'));
747
+ console.log(chalk.gray(' 2. ') + chalk.cyan('PASTE CONTENT') + chalk.gray(' - I have requirements ready'));
748
+ console.log(chalk.gray(' 3. ') + chalk.cyan('SKIP FOR NOW') + chalk.gray(' - I\'ll add it later\n'));
749
+
750
+ let prdChoice = '';
751
+ while (!['1', '2', '3'].includes(prdChoice)) {
752
+ prdChoice = await prompt(' Enter 1, 2, or 3: ');
753
+ }
754
+
755
+ if (prdChoice === '1') {
756
+ // Create template
757
+ const prdSpinner = ora(' Creating PRD template...').start();
758
+ const prdContent = createPrdTemplate(projectName, projectType);
759
+ writeFileSync(prdPath, prdContent);
760
+ prdSpinner.succeed('PRD template created!');
761
+ console.log(chalk.yellow('\n → Open PRD.md and fill in your requirements'));
762
+ console.log(chalk.gray(' The AI will use this to understand what to build.\n'));
763
+ prdCreated = true;
764
+ } else if (prdChoice === '2') {
765
+ // Paste content
766
+ console.log(chalk.gray('\n Paste your PRD content below.'));
767
+ console.log(chalk.gray(' When done, type ') + chalk.cyan('END') + chalk.gray(' on a new line and press Enter.\n'));
768
+
769
+ const lines: string[] = [];
770
+ let line = '';
771
+
772
+ while (true) {
773
+ line = await prompt(' ');
774
+ if (line.trim().toUpperCase() === 'END') break;
775
+ lines.push(line);
776
+ }
777
+
778
+ if (lines.length > 0) {
779
+ const prdSpinner = ora(' Saving PRD...').start();
780
+ const header = `# Product Requirements Document\n# Project: ${projectName}\n# Created: ${new Date().toISOString().split('T')[0]}\n\n`;
781
+ writeFileSync(prdPath, header + lines.join('\n'));
782
+ prdSpinner.succeed('PRD saved!');
783
+ prdCreated = true;
784
+ } else {
785
+ console.log(chalk.yellow(' No content provided, skipping PRD.\n'));
786
+ }
787
+ } else {
788
+ console.log(chalk.gray('\n You can add PRD.md anytime. The AI will use it automatically.\n'));
789
+ }
790
+ }
791
+
792
+ // Success message
793
+ console.log(chalk.green(`
794
+ ╔═══════════════════════════════════════════════════════════╗
795
+ ║ ║
796
+ ║ ${chalk.bold('✓ Setup Complete!')} ║
797
+ ║ ║
798
+ ╚═══════════════════════════════════════════════════════════╝
799
+ `));
800
+
801
+ console.log(chalk.white(' Files created:\n'));
802
+ console.log(chalk.cyan(' CLAUDE.md ') + chalk.gray('→ AI router'));
803
+ if (prdCreated) {
804
+ console.log(chalk.cyan(' PRD.md ') + chalk.gray('→ Product requirements (AI reads this!)'));
805
+ }
806
+ console.log(chalk.cyan(' PROJECT-CONTEXT.md ') + chalk.gray('→ Codebase knowledge (auto-updated)'));
807
+ console.log(chalk.cyan(' PROJECT-STATE.md ') + chalk.gray('→ Task tracking (auto-updated)'));
808
+ console.log(chalk.cyan(' DECISIONS.md ') + chalk.gray('→ Architecture log (auto-updated)'));
809
+ console.log(chalk.cyan(' .cursorrules ') + chalk.gray('→ Cursor AI instructions'));
810
+ console.log(chalk.cyan(' .cursorignore ') + chalk.gray('→ Context optimization'));
811
+ console.log(chalk.cyan(' .claude/ ') + chalk.gray('→ Pattern modules\n'));
812
+
813
+ console.log(chalk.white(' What happens automatically:\n'));
814
+ console.log(chalk.gray(' ✓ AI loads context before every response'));
815
+ console.log(chalk.gray(' ✓ AI checks for existing patterns to copy'));
816
+ console.log(chalk.gray(' ✓ AI validates code before outputting'));
817
+ console.log(chalk.gray(' ✓ AI updates project state after completing tasks'));
818
+ console.log(chalk.gray(' ✓ AI logs architectural decisions\n'));
819
+
820
+ console.log(chalk.white(' For Cursor users:\n'));
821
+ console.log(chalk.gray(' Just open the project and start chatting!\n'));
822
+
823
+ console.log(chalk.white(' For Claude Code users:\n'));
824
+ console.log(chalk.cyan(' codebakers install-hook') + chalk.gray(' (one-time setup)\n'));
825
+
826
+ console.log(chalk.blue(' Zero friction. Just build.\n'));
827
+ }