@codebakers/cli 2.2.0 โ 2.4.0
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/dist/mcp/server.js +600 -1
- package/package.json +1 -1
- package/src/mcp/server.ts +663 -1
package/dist/mcp/server.js
CHANGED
|
@@ -453,6 +453,125 @@ class CodeBakersServer {
|
|
|
453
453
|
},
|
|
454
454
|
},
|
|
455
455
|
},
|
|
456
|
+
{
|
|
457
|
+
name: 'design',
|
|
458
|
+
description: 'Clone and implement designs from mockups, screenshots, or website references. Analyzes visual designs and generates pixel-perfect matching code with extracted design tokens (colors, typography, spacing). Use when user says "clone this design", "make it look like...", or "copy this UI".',
|
|
459
|
+
inputSchema: {
|
|
460
|
+
type: 'object',
|
|
461
|
+
properties: {
|
|
462
|
+
source: {
|
|
463
|
+
type: 'string',
|
|
464
|
+
description: 'Path to mockup image, folder of images, URL to clone, or reference style (e.g., "./mockups", "https://linear.app", "like Notion")',
|
|
465
|
+
},
|
|
466
|
+
outputDir: {
|
|
467
|
+
type: 'string',
|
|
468
|
+
description: 'Directory to output generated components (default: src/components)',
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
required: ['source'],
|
|
472
|
+
},
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
name: 'upgrade',
|
|
476
|
+
description: 'Upgrade an existing project to CodeBakers patterns WITHOUT changing the user\'s tech stack. Preserves their existing ORM (Prisma/Drizzle), auth (NextAuth/Clerk), UI library (Chakra/MUI), etc. Only upgrades code quality patterns like error handling, validation, tests, and security. Use when user says "upgrade this project", "improve my code", or "make this production ready".',
|
|
477
|
+
inputSchema: {
|
|
478
|
+
type: 'object',
|
|
479
|
+
properties: {
|
|
480
|
+
areas: {
|
|
481
|
+
type: 'array',
|
|
482
|
+
items: { type: 'string' },
|
|
483
|
+
description: 'Specific areas to upgrade: "api", "components", "testing", "security", "all" (default: all)',
|
|
484
|
+
},
|
|
485
|
+
severity: {
|
|
486
|
+
type: 'string',
|
|
487
|
+
enum: ['critical', 'high', 'medium', 'low', 'all'],
|
|
488
|
+
description: 'Filter upgrades by severity (default: all)',
|
|
489
|
+
},
|
|
490
|
+
dryRun: {
|
|
491
|
+
type: 'boolean',
|
|
492
|
+
description: 'Show what would be upgraded without making changes (default: false)',
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
name: 'project_status',
|
|
499
|
+
description: 'Show project build progress, completed features, and what\'s next. Different from get_status which shows CodeBakers connection status. Use when user asks "where am I?", "what\'s built?", "show progress", or "what\'s next?".',
|
|
500
|
+
inputSchema: {
|
|
501
|
+
type: 'object',
|
|
502
|
+
properties: {},
|
|
503
|
+
},
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: 'run_tests',
|
|
507
|
+
description: 'Run the project test suite (npm test or configured test command). Use after completing a feature to verify everything works. Returns test results with pass/fail status.',
|
|
508
|
+
inputSchema: {
|
|
509
|
+
type: 'object',
|
|
510
|
+
properties: {
|
|
511
|
+
filter: {
|
|
512
|
+
type: 'string',
|
|
513
|
+
description: 'Optional filter to run specific tests (passed to test runner)',
|
|
514
|
+
},
|
|
515
|
+
watch: {
|
|
516
|
+
type: 'boolean',
|
|
517
|
+
description: 'Run in watch mode (default: false)',
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
},
|
|
522
|
+
{
|
|
523
|
+
name: 'report_pattern_gap',
|
|
524
|
+
description: 'Report when a user request cannot be fully handled by existing patterns. This helps improve CodeBakers by tracking what patterns are missing. The AI should automatically call this when it encounters something outside pattern coverage.',
|
|
525
|
+
inputSchema: {
|
|
526
|
+
type: 'object',
|
|
527
|
+
properties: {
|
|
528
|
+
category: {
|
|
529
|
+
type: 'string',
|
|
530
|
+
description: 'Category of the gap (e.g., "third-party-apis", "mobile", "blockchain", "iot")',
|
|
531
|
+
},
|
|
532
|
+
request: {
|
|
533
|
+
type: 'string',
|
|
534
|
+
description: 'What the user asked for',
|
|
535
|
+
},
|
|
536
|
+
context: {
|
|
537
|
+
type: 'string',
|
|
538
|
+
description: 'Additional context about what was needed',
|
|
539
|
+
},
|
|
540
|
+
handledWith: {
|
|
541
|
+
type: 'string',
|
|
542
|
+
description: 'Which existing patterns were used as fallback',
|
|
543
|
+
},
|
|
544
|
+
wasSuccessful: {
|
|
545
|
+
type: 'boolean',
|
|
546
|
+
description: 'Whether the request was handled successfully despite the gap',
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
required: ['category', 'request'],
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: 'track_analytics',
|
|
554
|
+
description: 'Track CLI usage analytics for improving smart triggers and recommendations. Called automatically by the system for key events.',
|
|
555
|
+
inputSchema: {
|
|
556
|
+
type: 'object',
|
|
557
|
+
properties: {
|
|
558
|
+
eventType: {
|
|
559
|
+
type: 'string',
|
|
560
|
+
enum: ['trigger_fired', 'trigger_accepted', 'trigger_dismissed', 'topic_learned', 'command_used', 'pattern_fetched', 'build_started', 'build_completed', 'feature_added', 'audit_run', 'design_cloned'],
|
|
561
|
+
description: 'Type of event to track',
|
|
562
|
+
},
|
|
563
|
+
eventData: {
|
|
564
|
+
type: 'object',
|
|
565
|
+
description: 'Additional data specific to the event',
|
|
566
|
+
},
|
|
567
|
+
projectHash: {
|
|
568
|
+
type: 'string',
|
|
569
|
+
description: 'Hash of project path for grouping analytics',
|
|
570
|
+
},
|
|
571
|
+
},
|
|
572
|
+
required: ['eventType'],
|
|
573
|
+
},
|
|
574
|
+
},
|
|
456
575
|
],
|
|
457
576
|
}));
|
|
458
577
|
// Handle tool calls
|
|
@@ -488,6 +607,18 @@ class CodeBakersServer {
|
|
|
488
607
|
return this.handleRunAudit();
|
|
489
608
|
case 'heal':
|
|
490
609
|
return this.handleHeal(args);
|
|
610
|
+
case 'design':
|
|
611
|
+
return this.handleDesign(args);
|
|
612
|
+
case 'upgrade':
|
|
613
|
+
return this.handleUpgrade(args);
|
|
614
|
+
case 'project_status':
|
|
615
|
+
return this.handleProjectStatus();
|
|
616
|
+
case 'run_tests':
|
|
617
|
+
return this.handleRunTests(args);
|
|
618
|
+
case 'report_pattern_gap':
|
|
619
|
+
return this.handleReportPatternGap(args);
|
|
620
|
+
case 'track_analytics':
|
|
621
|
+
return this.handleTrackAnalytics(args);
|
|
491
622
|
default:
|
|
492
623
|
throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
493
624
|
}
|
|
@@ -1277,7 +1408,7 @@ phase: development
|
|
|
1277
1408
|
## Connection Status
|
|
1278
1409
|
- **MCP Server:** Running
|
|
1279
1410
|
- **API Connected:** Yes
|
|
1280
|
-
- **Version:**
|
|
1411
|
+
- **Version:** 2.2.0
|
|
1281
1412
|
|
|
1282
1413
|
## Current Settings
|
|
1283
1414
|
- **Experience Level:** ${level.charAt(0).toUpperCase() + level.slice(1)}
|
|
@@ -1289,6 +1420,9 @@ phase: development
|
|
|
1289
1420
|
- ๐ **search_patterns** - Search for specific guidance
|
|
1290
1421
|
- ๐๏ธ **scaffold_project** - Create new projects
|
|
1291
1422
|
- โ๏ธ **init_project** - Add patterns to existing projects
|
|
1423
|
+
- ๐จ **design** - Clone designs from mockups/websites
|
|
1424
|
+
- โฌ๏ธ **upgrade** - Upgrade project patterns (preserves your stack)
|
|
1425
|
+
- ๐ **project_status** - Show build progress
|
|
1292
1426
|
|
|
1293
1427
|
## How to Use
|
|
1294
1428
|
Just describe what you want to build! I'll automatically:
|
|
@@ -1307,6 +1441,471 @@ Just describe what you want to build! I'll automatically:
|
|
|
1307
1441
|
}],
|
|
1308
1442
|
};
|
|
1309
1443
|
}
|
|
1444
|
+
async handleDesign(args) {
|
|
1445
|
+
const { source, outputDir = 'src/components' } = args;
|
|
1446
|
+
const cwd = process.cwd();
|
|
1447
|
+
// Detect source type
|
|
1448
|
+
let sourceType = 'reference';
|
|
1449
|
+
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
1450
|
+
sourceType = 'url';
|
|
1451
|
+
}
|
|
1452
|
+
else if (source.startsWith('./') || source.startsWith('/') || source.includes('\\')) {
|
|
1453
|
+
const fullPath = path.join(cwd, source);
|
|
1454
|
+
if (fs.existsSync(fullPath)) {
|
|
1455
|
+
const stat = fs.statSync(fullPath);
|
|
1456
|
+
sourceType = stat.isDirectory() ? 'folder' : 'file';
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
else if (source.toLowerCase().startsWith('like ')) {
|
|
1460
|
+
sourceType = 'reference';
|
|
1461
|
+
}
|
|
1462
|
+
// Fetch design pattern from API
|
|
1463
|
+
const patternResult = await this.fetchPatterns(['33-design-clone', '09-design']);
|
|
1464
|
+
let response = `# ๐จ Design Clone Tool\n\n`;
|
|
1465
|
+
response += `**Source:** ${source}\n`;
|
|
1466
|
+
response += `**Type:** ${sourceType}\n`;
|
|
1467
|
+
response += `**Output:** ${outputDir}\n\n`;
|
|
1468
|
+
switch (sourceType) {
|
|
1469
|
+
case 'folder':
|
|
1470
|
+
const folderPath = path.join(cwd, source);
|
|
1471
|
+
const images = fs.readdirSync(folderPath).filter(f => /\.(png|jpg|jpeg|webp|svg|gif)$/i.test(f));
|
|
1472
|
+
response += `## Found ${images.length} Design Files\n\n`;
|
|
1473
|
+
images.forEach(img => {
|
|
1474
|
+
response += `- ${img}\n`;
|
|
1475
|
+
});
|
|
1476
|
+
response += `\n## Next Steps\n\n`;
|
|
1477
|
+
response += `1. I'll analyze each image for design tokens\n`;
|
|
1478
|
+
response += `2. Extract colors, typography, spacing\n`;
|
|
1479
|
+
response += `3. Generate Tailwind config with your design system\n`;
|
|
1480
|
+
response += `4. Create matching components\n\n`;
|
|
1481
|
+
response += `**Note:** For best results, provide screenshots of:\n`;
|
|
1482
|
+
response += `- Color palette / brand guidelines\n`;
|
|
1483
|
+
response += `- Typography samples\n`;
|
|
1484
|
+
response += `- Key UI components (buttons, cards, forms)\n`;
|
|
1485
|
+
break;
|
|
1486
|
+
case 'file':
|
|
1487
|
+
response += `## Analyzing Single Design\n\n`;
|
|
1488
|
+
response += `I'll extract design tokens from this image and generate matching components.\n\n`;
|
|
1489
|
+
response += `**Tip:** For a complete design system, provide a folder with multiple mockups.\n`;
|
|
1490
|
+
break;
|
|
1491
|
+
case 'url':
|
|
1492
|
+
response += `## Cloning Website Design\n\n`;
|
|
1493
|
+
response += `I'll analyze the visual design at ${source} and extract:\n`;
|
|
1494
|
+
response += `- Color palette\n`;
|
|
1495
|
+
response += `- Typography (fonts, sizes, weights)\n`;
|
|
1496
|
+
response += `- Spacing system\n`;
|
|
1497
|
+
response += `- Component patterns\n\n`;
|
|
1498
|
+
response += `**Note:** This creates inspired-by components, not exact copies.\n`;
|
|
1499
|
+
break;
|
|
1500
|
+
case 'reference':
|
|
1501
|
+
const refStyle = source.replace(/^like\s+/i, '').toLowerCase();
|
|
1502
|
+
const knownStyles = {
|
|
1503
|
+
'linear': 'Dark theme, purple accents, minimal, clean',
|
|
1504
|
+
'notion': 'Light theme, black/white, content-focused, lots of whitespace',
|
|
1505
|
+
'stripe': 'Professional, purple gradients, polished shadows',
|
|
1506
|
+
'vercel': 'Black/white, developer-focused, geometric',
|
|
1507
|
+
'github': 'Blue accents, familiar, dev-tool aesthetic',
|
|
1508
|
+
'figma': 'Purple accents, collaborative, modern',
|
|
1509
|
+
};
|
|
1510
|
+
const matchedStyle = Object.entries(knownStyles).find(([key]) => refStyle.includes(key));
|
|
1511
|
+
if (matchedStyle) {
|
|
1512
|
+
response += `## Reference Style: ${matchedStyle[0]}\n\n`;
|
|
1513
|
+
response += `**Characteristics:** ${matchedStyle[1]}\n\n`;
|
|
1514
|
+
}
|
|
1515
|
+
else {
|
|
1516
|
+
response += `## Reference Style: "${source}"\n\n`;
|
|
1517
|
+
response += `I'll apply a design language inspired by this reference.\n\n`;
|
|
1518
|
+
}
|
|
1519
|
+
response += `I'll generate components matching this aesthetic.\n`;
|
|
1520
|
+
break;
|
|
1521
|
+
}
|
|
1522
|
+
response += `\n---\n\n`;
|
|
1523
|
+
response += `## Design Pattern Loaded\n\n`;
|
|
1524
|
+
response += `The design-clone pattern (33-design-clone) is now active.\n`;
|
|
1525
|
+
response += `Proceed with specific component requests like:\n`;
|
|
1526
|
+
response += `- "Create the navigation bar"\n`;
|
|
1527
|
+
response += `- "Build the hero section"\n`;
|
|
1528
|
+
response += `- "Generate the card components"\n`;
|
|
1529
|
+
return {
|
|
1530
|
+
content: [{
|
|
1531
|
+
type: 'text',
|
|
1532
|
+
text: response,
|
|
1533
|
+
}],
|
|
1534
|
+
};
|
|
1535
|
+
}
|
|
1536
|
+
async handleUpgrade(args) {
|
|
1537
|
+
const { areas = ['all'], severity = 'all', dryRun = false } = args;
|
|
1538
|
+
const context = this.gatherProjectContext();
|
|
1539
|
+
let response = `# โฌ๏ธ Project Upgrade Analysis\n\n`;
|
|
1540
|
+
// Stack detection
|
|
1541
|
+
response += `## Your Stack (Preserving As-Is)\n\n`;
|
|
1542
|
+
response += `| Layer | Detected | Status |\n`;
|
|
1543
|
+
response += `|-------|----------|--------|\n`;
|
|
1544
|
+
// ORM detection
|
|
1545
|
+
let orm = 'None';
|
|
1546
|
+
if (context.dependencies.includes('drizzle-orm'))
|
|
1547
|
+
orm = 'Drizzle';
|
|
1548
|
+
else if (context.dependencies.includes('@prisma/client'))
|
|
1549
|
+
orm = 'Prisma';
|
|
1550
|
+
else if (context.dependencies.includes('typeorm'))
|
|
1551
|
+
orm = 'TypeORM';
|
|
1552
|
+
else if (context.dependencies.includes('mongoose'))
|
|
1553
|
+
orm = 'Mongoose';
|
|
1554
|
+
response += `| ORM | ${orm} | โ Keeping |\n`;
|
|
1555
|
+
// Auth detection
|
|
1556
|
+
let auth = 'None';
|
|
1557
|
+
if (context.dependencies.includes('@supabase/supabase-js'))
|
|
1558
|
+
auth = 'Supabase';
|
|
1559
|
+
else if (context.dependencies.includes('next-auth'))
|
|
1560
|
+
auth = 'NextAuth';
|
|
1561
|
+
else if (context.dependencies.includes('@clerk/nextjs'))
|
|
1562
|
+
auth = 'Clerk';
|
|
1563
|
+
else if (context.dependencies.includes('firebase'))
|
|
1564
|
+
auth = 'Firebase';
|
|
1565
|
+
response += `| Auth | ${auth} | โ Keeping |\n`;
|
|
1566
|
+
// UI detection
|
|
1567
|
+
response += `| UI | ${context.uiLibrary || 'Tailwind'} | โ Keeping |\n`;
|
|
1568
|
+
// Framework (always Next.js for now)
|
|
1569
|
+
const hasNext = context.dependencies.includes('next');
|
|
1570
|
+
response += `| Framework | ${hasNext ? 'Next.js' : 'Unknown'} | โ Keeping |\n`;
|
|
1571
|
+
response += `\n---\n\n`;
|
|
1572
|
+
// Scan for upgrade opportunities
|
|
1573
|
+
response += `## Upgrade Opportunities\n\n`;
|
|
1574
|
+
const upgrades = [];
|
|
1575
|
+
// Check API routes
|
|
1576
|
+
if (context.existingApiRoutes.length > 0) {
|
|
1577
|
+
upgrades.push({
|
|
1578
|
+
area: 'API Routes',
|
|
1579
|
+
issue: 'Add error handling, validation, rate limiting',
|
|
1580
|
+
severity: 'HIGH',
|
|
1581
|
+
count: context.existingApiRoutes.length,
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
// Check components
|
|
1585
|
+
if (context.existingComponents.length > 0) {
|
|
1586
|
+
upgrades.push({
|
|
1587
|
+
area: 'Components',
|
|
1588
|
+
issue: 'Add loading states, error boundaries, accessibility',
|
|
1589
|
+
severity: 'MEDIUM',
|
|
1590
|
+
count: context.existingComponents.length,
|
|
1591
|
+
});
|
|
1592
|
+
}
|
|
1593
|
+
// Check for tests
|
|
1594
|
+
const hasTests = context.dependencies.includes('@playwright/test') ||
|
|
1595
|
+
context.dependencies.includes('jest') ||
|
|
1596
|
+
context.dependencies.includes('vitest');
|
|
1597
|
+
if (!hasTests) {
|
|
1598
|
+
upgrades.push({
|
|
1599
|
+
area: 'Testing',
|
|
1600
|
+
issue: 'No test framework detected',
|
|
1601
|
+
severity: 'HIGH',
|
|
1602
|
+
count: 0,
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
// Check for Zod
|
|
1606
|
+
const hasZod = context.dependencies.includes('zod');
|
|
1607
|
+
if (!hasZod && context.existingApiRoutes.length > 0) {
|
|
1608
|
+
upgrades.push({
|
|
1609
|
+
area: 'Validation',
|
|
1610
|
+
issue: 'No Zod validation detected for API routes',
|
|
1611
|
+
severity: 'HIGH',
|
|
1612
|
+
count: context.existingApiRoutes.length,
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
// Display upgrades
|
|
1616
|
+
for (const upgrade of upgrades) {
|
|
1617
|
+
const icon = upgrade.severity === 'HIGH' ? '๐ด' :
|
|
1618
|
+
upgrade.severity === 'MEDIUM' ? '๐ก' : '๐ข';
|
|
1619
|
+
response += `### ${icon} ${upgrade.area}\n`;
|
|
1620
|
+
response += `- **Issue:** ${upgrade.issue}\n`;
|
|
1621
|
+
if (upgrade.count > 0) {
|
|
1622
|
+
response += `- **Affected:** ${upgrade.count} files\n`;
|
|
1623
|
+
}
|
|
1624
|
+
response += `- **Severity:** ${upgrade.severity}\n\n`;
|
|
1625
|
+
}
|
|
1626
|
+
if (upgrades.length === 0) {
|
|
1627
|
+
response += `โ
No major upgrade opportunities detected!\n\n`;
|
|
1628
|
+
}
|
|
1629
|
+
// Recommendations
|
|
1630
|
+
response += `---\n\n`;
|
|
1631
|
+
response += `## Recommended Actions\n\n`;
|
|
1632
|
+
if (dryRun) {
|
|
1633
|
+
response += `**(Dry Run Mode - No changes will be made)**\n\n`;
|
|
1634
|
+
}
|
|
1635
|
+
response += `1. Run \`run_audit\` for detailed code quality check\n`;
|
|
1636
|
+
response += `2. Use \`heal\` to auto-fix common issues\n`;
|
|
1637
|
+
response += `3. Request specific upgrades like:\n`;
|
|
1638
|
+
response += ` - "Add Zod validation to my API routes"\n`;
|
|
1639
|
+
response += ` - "Add error boundaries to components"\n`;
|
|
1640
|
+
response += ` - "Set up Playwright testing"\n\n`;
|
|
1641
|
+
response += `---\n\n`;
|
|
1642
|
+
response += `**Key Principle:** Your stack stays the same. Only code quality patterns are upgraded.\n`;
|
|
1643
|
+
return {
|
|
1644
|
+
content: [{
|
|
1645
|
+
type: 'text',
|
|
1646
|
+
text: response,
|
|
1647
|
+
}],
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
handleProjectStatus() {
|
|
1651
|
+
const cwd = process.cwd();
|
|
1652
|
+
const context = this.gatherProjectContext();
|
|
1653
|
+
let response = `# ๐ Project Status\n\n`;
|
|
1654
|
+
response += `**Project:** ${context.projectName}\n\n`;
|
|
1655
|
+
// Check for .codebakers.json state
|
|
1656
|
+
let state = null;
|
|
1657
|
+
try {
|
|
1658
|
+
const statePath = path.join(cwd, '.codebakers.json');
|
|
1659
|
+
if (fs.existsSync(statePath)) {
|
|
1660
|
+
state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
catch {
|
|
1664
|
+
// No state file
|
|
1665
|
+
}
|
|
1666
|
+
// Check for PRD
|
|
1667
|
+
const prdPath = path.join(cwd, 'PRD.md');
|
|
1668
|
+
const hasPrd = fs.existsSync(prdPath);
|
|
1669
|
+
// Check for PROJECT-STATE.md
|
|
1670
|
+
const projectStatePath = path.join(cwd, 'PROJECT-STATE.md');
|
|
1671
|
+
const hasProjectState = fs.existsSync(projectStatePath);
|
|
1672
|
+
if (state && typeof state === 'object') {
|
|
1673
|
+
const s = state;
|
|
1674
|
+
// Build progress
|
|
1675
|
+
if (s.build && typeof s.build === 'object') {
|
|
1676
|
+
const build = s.build;
|
|
1677
|
+
response += `## Build Progress\n\n`;
|
|
1678
|
+
const currentPhase = build.currentPhase || 0;
|
|
1679
|
+
const totalPhases = build.totalPhases || 1;
|
|
1680
|
+
const percent = Math.round((currentPhase / totalPhases) * 100);
|
|
1681
|
+
response += `**Phase ${currentPhase}/${totalPhases}** (${percent}%)\n\n`;
|
|
1682
|
+
// Progress bar
|
|
1683
|
+
const filled = Math.round(percent / 10);
|
|
1684
|
+
const empty = 10 - filled;
|
|
1685
|
+
response += `[${'โ'.repeat(filled)}${'โ'.repeat(empty)}] ${percent}%\n\n`;
|
|
1686
|
+
if (build.status) {
|
|
1687
|
+
response += `**Status:** ${build.status}\n\n`;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
// Current work
|
|
1691
|
+
if (s.currentWork && typeof s.currentWork === 'object') {
|
|
1692
|
+
const work = s.currentWork;
|
|
1693
|
+
response += `## Current Work\n\n`;
|
|
1694
|
+
if (work.activeFeature) {
|
|
1695
|
+
response += `**In Progress:** ${work.activeFeature}\n`;
|
|
1696
|
+
}
|
|
1697
|
+
if (work.summary) {
|
|
1698
|
+
response += `**Summary:** ${work.summary}\n`;
|
|
1699
|
+
}
|
|
1700
|
+
if (work.lastUpdated) {
|
|
1701
|
+
response += `**Last Updated:** ${work.lastUpdated}\n`;
|
|
1702
|
+
}
|
|
1703
|
+
response += `\n`;
|
|
1704
|
+
}
|
|
1705
|
+
// Stack
|
|
1706
|
+
if (s.stack && typeof s.stack === 'object') {
|
|
1707
|
+
const stack = s.stack;
|
|
1708
|
+
response += `## Stack\n\n`;
|
|
1709
|
+
for (const [key, value] of Object.entries(stack)) {
|
|
1710
|
+
if (value) {
|
|
1711
|
+
response += `- **${key}:** ${value}\n`;
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
response += `\n`;
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
else {
|
|
1718
|
+
response += `## Project Overview\n\n`;
|
|
1719
|
+
response += `- **PRD:** ${hasPrd ? 'โ
Found' : 'โ Not found'}\n`;
|
|
1720
|
+
response += `- **State Tracking:** ${hasProjectState ? 'โ
Found' : 'โ Not found'}\n`;
|
|
1721
|
+
response += `- **CodeBakers State:** ${state ? 'โ
Found' : 'โ Not initialized'}\n\n`;
|
|
1722
|
+
}
|
|
1723
|
+
// What's built
|
|
1724
|
+
response += `## What's Built\n\n`;
|
|
1725
|
+
response += `- **Components:** ${context.existingComponents.length}\n`;
|
|
1726
|
+
response += `- **API Routes:** ${context.existingApiRoutes.length}\n`;
|
|
1727
|
+
response += `- **Services:** ${context.existingServices.length}\n`;
|
|
1728
|
+
if (context.hasAuth)
|
|
1729
|
+
response += `- โ
Authentication\n`;
|
|
1730
|
+
if (context.hasDatabase)
|
|
1731
|
+
response += `- โ
Database\n`;
|
|
1732
|
+
if (context.hasPayments)
|
|
1733
|
+
response += `- โ
Payments\n`;
|
|
1734
|
+
response += `\n`;
|
|
1735
|
+
// Recent components
|
|
1736
|
+
if (context.existingComponents.length > 0) {
|
|
1737
|
+
response += `### Recent Components\n`;
|
|
1738
|
+
context.existingComponents.slice(0, 10).forEach(comp => {
|
|
1739
|
+
response += `- ${comp}\n`;
|
|
1740
|
+
});
|
|
1741
|
+
if (context.existingComponents.length > 10) {
|
|
1742
|
+
response += `- ... and ${context.existingComponents.length - 10} more\n`;
|
|
1743
|
+
}
|
|
1744
|
+
response += `\n`;
|
|
1745
|
+
}
|
|
1746
|
+
response += `---\n\n`;
|
|
1747
|
+
response += `*Run \`upgrade\` to improve code quality or \`run_audit\` for detailed analysis.*`;
|
|
1748
|
+
return {
|
|
1749
|
+
content: [{
|
|
1750
|
+
type: 'text',
|
|
1751
|
+
text: response,
|
|
1752
|
+
}],
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
handleRunTests(args) {
|
|
1756
|
+
const { filter, watch = false } = args;
|
|
1757
|
+
const cwd = process.cwd();
|
|
1758
|
+
// Detect test runner from package.json
|
|
1759
|
+
let testCommand = 'npm test';
|
|
1760
|
+
try {
|
|
1761
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
1762
|
+
if (fs.existsSync(pkgPath)) {
|
|
1763
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
1764
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1765
|
+
if (deps['@playwright/test']) {
|
|
1766
|
+
testCommand = 'npx playwright test';
|
|
1767
|
+
}
|
|
1768
|
+
else if (deps['vitest']) {
|
|
1769
|
+
testCommand = 'npx vitest run';
|
|
1770
|
+
}
|
|
1771
|
+
else if (deps['jest']) {
|
|
1772
|
+
testCommand = 'npx jest';
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
catch {
|
|
1777
|
+
// Use default
|
|
1778
|
+
}
|
|
1779
|
+
// Add filter if provided
|
|
1780
|
+
if (filter) {
|
|
1781
|
+
testCommand += ` ${filter}`;
|
|
1782
|
+
}
|
|
1783
|
+
// Add watch mode
|
|
1784
|
+
if (watch) {
|
|
1785
|
+
testCommand = testCommand.replace(' run', ''); // Remove 'run' for vitest watch
|
|
1786
|
+
if (testCommand.includes('vitest')) {
|
|
1787
|
+
testCommand = testCommand.replace('vitest run', 'vitest');
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
let response = `# ๐งช Running Tests\n\n`;
|
|
1791
|
+
response += `**Command:** \`${testCommand}\`\n\n`;
|
|
1792
|
+
try {
|
|
1793
|
+
const output = (0, child_process_1.execSync)(testCommand, {
|
|
1794
|
+
cwd,
|
|
1795
|
+
encoding: 'utf-8',
|
|
1796
|
+
timeout: 120000, // 2 minute timeout
|
|
1797
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1798
|
+
});
|
|
1799
|
+
response += `## โ
Tests Passed\n\n`;
|
|
1800
|
+
response += '```\n' + output.slice(-2000) + '\n```\n';
|
|
1801
|
+
}
|
|
1802
|
+
catch (error) {
|
|
1803
|
+
const execError = error;
|
|
1804
|
+
response += `## โ Tests Failed\n\n`;
|
|
1805
|
+
if (execError.stdout) {
|
|
1806
|
+
response += '### Output\n```\n' + execError.stdout.slice(-2000) + '\n```\n\n';
|
|
1807
|
+
}
|
|
1808
|
+
if (execError.stderr) {
|
|
1809
|
+
response += '### Errors\n```\n' + execError.stderr.slice(-1000) + '\n```\n\n';
|
|
1810
|
+
}
|
|
1811
|
+
response += `**Exit Code:** ${execError.status || 1}\n\n`;
|
|
1812
|
+
response += `---\n\n*Fix the failing tests and run again.*`;
|
|
1813
|
+
}
|
|
1814
|
+
return {
|
|
1815
|
+
content: [{
|
|
1816
|
+
type: 'text',
|
|
1817
|
+
text: response,
|
|
1818
|
+
}],
|
|
1819
|
+
};
|
|
1820
|
+
}
|
|
1821
|
+
async handleReportPatternGap(args) {
|
|
1822
|
+
const { category, request, context, handledWith, wasSuccessful = true } = args;
|
|
1823
|
+
try {
|
|
1824
|
+
const response = await fetch(`${this.apiUrl}/api/pattern-gaps`, {
|
|
1825
|
+
method: 'POST',
|
|
1826
|
+
headers: {
|
|
1827
|
+
'Content-Type': 'application/json',
|
|
1828
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
1829
|
+
},
|
|
1830
|
+
body: JSON.stringify({
|
|
1831
|
+
category,
|
|
1832
|
+
request,
|
|
1833
|
+
context,
|
|
1834
|
+
handledWith,
|
|
1835
|
+
wasSuccessful,
|
|
1836
|
+
}),
|
|
1837
|
+
});
|
|
1838
|
+
if (!response.ok) {
|
|
1839
|
+
const error = await response.json().catch(() => ({}));
|
|
1840
|
+
throw new Error(error.error || 'Failed to report pattern gap');
|
|
1841
|
+
}
|
|
1842
|
+
const result = await response.json();
|
|
1843
|
+
if (result.deduplicated) {
|
|
1844
|
+
return {
|
|
1845
|
+
content: [{
|
|
1846
|
+
type: 'text',
|
|
1847
|
+
text: `๐ Pattern gap already reported recently (category: ${category}). No duplicate created.`,
|
|
1848
|
+
}],
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
return {
|
|
1852
|
+
content: [{
|
|
1853
|
+
type: 'text',
|
|
1854
|
+
text: `โ
Pattern gap reported successfully.\n\n**Category:** ${category}\n**Request:** ${request}\n\nThis helps improve CodeBakers for everyone!`,
|
|
1855
|
+
}],
|
|
1856
|
+
};
|
|
1857
|
+
}
|
|
1858
|
+
catch (error) {
|
|
1859
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
1860
|
+
return {
|
|
1861
|
+
content: [{
|
|
1862
|
+
type: 'text',
|
|
1863
|
+
text: `โ ๏ธ Could not report pattern gap: ${message}\n\n(This doesn't affect your current work)`,
|
|
1864
|
+
}],
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
async handleTrackAnalytics(args) {
|
|
1869
|
+
const { eventType, eventData, projectHash } = args;
|
|
1870
|
+
try {
|
|
1871
|
+
const response = await fetch(`${this.apiUrl}/api/cli/analytics`, {
|
|
1872
|
+
method: 'POST',
|
|
1873
|
+
headers: {
|
|
1874
|
+
'Content-Type': 'application/json',
|
|
1875
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
1876
|
+
},
|
|
1877
|
+
body: JSON.stringify({
|
|
1878
|
+
eventType,
|
|
1879
|
+
eventData,
|
|
1880
|
+
projectHash,
|
|
1881
|
+
}),
|
|
1882
|
+
});
|
|
1883
|
+
if (!response.ok) {
|
|
1884
|
+
// Silently fail - analytics shouldn't interrupt user workflow
|
|
1885
|
+
return {
|
|
1886
|
+
content: [{
|
|
1887
|
+
type: 'text',
|
|
1888
|
+
text: `๐ Analytics tracked: ${eventType}`,
|
|
1889
|
+
}],
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
return {
|
|
1893
|
+
content: [{
|
|
1894
|
+
type: 'text',
|
|
1895
|
+
text: `๐ Analytics tracked: ${eventType}`,
|
|
1896
|
+
}],
|
|
1897
|
+
};
|
|
1898
|
+
}
|
|
1899
|
+
catch {
|
|
1900
|
+
// Silently fail
|
|
1901
|
+
return {
|
|
1902
|
+
content: [{
|
|
1903
|
+
type: 'text',
|
|
1904
|
+
text: `๐ Analytics tracked: ${eventType}`,
|
|
1905
|
+
}],
|
|
1906
|
+
};
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1310
1909
|
async run() {
|
|
1311
1910
|
const transport = new stdio_js_1.StdioServerTransport();
|
|
1312
1911
|
await this.server.connect(transport);
|
package/package.json
CHANGED
package/src/mcp/server.ts
CHANGED
|
@@ -480,6 +480,131 @@ class CodeBakersServer {
|
|
|
480
480
|
},
|
|
481
481
|
},
|
|
482
482
|
},
|
|
483
|
+
{
|
|
484
|
+
name: 'design',
|
|
485
|
+
description:
|
|
486
|
+
'Clone and implement designs from mockups, screenshots, or website references. Analyzes visual designs and generates pixel-perfect matching code with extracted design tokens (colors, typography, spacing). Use when user says "clone this design", "make it look like...", or "copy this UI".',
|
|
487
|
+
inputSchema: {
|
|
488
|
+
type: 'object' as const,
|
|
489
|
+
properties: {
|
|
490
|
+
source: {
|
|
491
|
+
type: 'string',
|
|
492
|
+
description: 'Path to mockup image, folder of images, URL to clone, or reference style (e.g., "./mockups", "https://linear.app", "like Notion")',
|
|
493
|
+
},
|
|
494
|
+
outputDir: {
|
|
495
|
+
type: 'string',
|
|
496
|
+
description: 'Directory to output generated components (default: src/components)',
|
|
497
|
+
},
|
|
498
|
+
},
|
|
499
|
+
required: ['source'],
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: 'upgrade',
|
|
504
|
+
description:
|
|
505
|
+
'Upgrade an existing project to CodeBakers patterns WITHOUT changing the user\'s tech stack. Preserves their existing ORM (Prisma/Drizzle), auth (NextAuth/Clerk), UI library (Chakra/MUI), etc. Only upgrades code quality patterns like error handling, validation, tests, and security. Use when user says "upgrade this project", "improve my code", or "make this production ready".',
|
|
506
|
+
inputSchema: {
|
|
507
|
+
type: 'object' as const,
|
|
508
|
+
properties: {
|
|
509
|
+
areas: {
|
|
510
|
+
type: 'array',
|
|
511
|
+
items: { type: 'string' },
|
|
512
|
+
description: 'Specific areas to upgrade: "api", "components", "testing", "security", "all" (default: all)',
|
|
513
|
+
},
|
|
514
|
+
severity: {
|
|
515
|
+
type: 'string',
|
|
516
|
+
enum: ['critical', 'high', 'medium', 'low', 'all'],
|
|
517
|
+
description: 'Filter upgrades by severity (default: all)',
|
|
518
|
+
},
|
|
519
|
+
dryRun: {
|
|
520
|
+
type: 'boolean',
|
|
521
|
+
description: 'Show what would be upgraded without making changes (default: false)',
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
name: 'project_status',
|
|
528
|
+
description:
|
|
529
|
+
'Show project build progress, completed features, and what\'s next. Different from get_status which shows CodeBakers connection status. Use when user asks "where am I?", "what\'s built?", "show progress", or "what\'s next?".',
|
|
530
|
+
inputSchema: {
|
|
531
|
+
type: 'object' as const,
|
|
532
|
+
properties: {},
|
|
533
|
+
},
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
name: 'run_tests',
|
|
537
|
+
description:
|
|
538
|
+
'Run the project test suite (npm test or configured test command). Use after completing a feature to verify everything works. Returns test results with pass/fail status.',
|
|
539
|
+
inputSchema: {
|
|
540
|
+
type: 'object' as const,
|
|
541
|
+
properties: {
|
|
542
|
+
filter: {
|
|
543
|
+
type: 'string',
|
|
544
|
+
description: 'Optional filter to run specific tests (passed to test runner)',
|
|
545
|
+
},
|
|
546
|
+
watch: {
|
|
547
|
+
type: 'boolean',
|
|
548
|
+
description: 'Run in watch mode (default: false)',
|
|
549
|
+
},
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
name: 'report_pattern_gap',
|
|
555
|
+
description:
|
|
556
|
+
'Report when a user request cannot be fully handled by existing patterns. This helps improve CodeBakers by tracking what patterns are missing. The AI should automatically call this when it encounters something outside pattern coverage.',
|
|
557
|
+
inputSchema: {
|
|
558
|
+
type: 'object' as const,
|
|
559
|
+
properties: {
|
|
560
|
+
category: {
|
|
561
|
+
type: 'string',
|
|
562
|
+
description: 'Category of the gap (e.g., "third-party-apis", "mobile", "blockchain", "iot")',
|
|
563
|
+
},
|
|
564
|
+
request: {
|
|
565
|
+
type: 'string',
|
|
566
|
+
description: 'What the user asked for',
|
|
567
|
+
},
|
|
568
|
+
context: {
|
|
569
|
+
type: 'string',
|
|
570
|
+
description: 'Additional context about what was needed',
|
|
571
|
+
},
|
|
572
|
+
handledWith: {
|
|
573
|
+
type: 'string',
|
|
574
|
+
description: 'Which existing patterns were used as fallback',
|
|
575
|
+
},
|
|
576
|
+
wasSuccessful: {
|
|
577
|
+
type: 'boolean',
|
|
578
|
+
description: 'Whether the request was handled successfully despite the gap',
|
|
579
|
+
},
|
|
580
|
+
},
|
|
581
|
+
required: ['category', 'request'],
|
|
582
|
+
},
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: 'track_analytics',
|
|
586
|
+
description:
|
|
587
|
+
'Track CLI usage analytics for improving smart triggers and recommendations. Called automatically by the system for key events.',
|
|
588
|
+
inputSchema: {
|
|
589
|
+
type: 'object' as const,
|
|
590
|
+
properties: {
|
|
591
|
+
eventType: {
|
|
592
|
+
type: 'string',
|
|
593
|
+
enum: ['trigger_fired', 'trigger_accepted', 'trigger_dismissed', 'topic_learned', 'command_used', 'pattern_fetched', 'build_started', 'build_completed', 'feature_added', 'audit_run', 'design_cloned'],
|
|
594
|
+
description: 'Type of event to track',
|
|
595
|
+
},
|
|
596
|
+
eventData: {
|
|
597
|
+
type: 'object',
|
|
598
|
+
description: 'Additional data specific to the event',
|
|
599
|
+
},
|
|
600
|
+
projectHash: {
|
|
601
|
+
type: 'string',
|
|
602
|
+
description: 'Hash of project path for grouping analytics',
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
required: ['eventType'],
|
|
606
|
+
},
|
|
607
|
+
},
|
|
483
608
|
],
|
|
484
609
|
}));
|
|
485
610
|
|
|
@@ -534,6 +659,24 @@ class CodeBakersServer {
|
|
|
534
659
|
case 'heal':
|
|
535
660
|
return this.handleHeal(args as { auto?: boolean; dryRun?: boolean; severity?: string });
|
|
536
661
|
|
|
662
|
+
case 'design':
|
|
663
|
+
return this.handleDesign(args as { source: string; outputDir?: string });
|
|
664
|
+
|
|
665
|
+
case 'upgrade':
|
|
666
|
+
return this.handleUpgrade(args as { areas?: string[]; severity?: string; dryRun?: boolean });
|
|
667
|
+
|
|
668
|
+
case 'project_status':
|
|
669
|
+
return this.handleProjectStatus();
|
|
670
|
+
|
|
671
|
+
case 'run_tests':
|
|
672
|
+
return this.handleRunTests(args as { filter?: string; watch?: boolean });
|
|
673
|
+
|
|
674
|
+
case 'report_pattern_gap':
|
|
675
|
+
return this.handleReportPatternGap(args as { category: string; request: string; context?: string; handledWith?: string; wasSuccessful?: boolean });
|
|
676
|
+
|
|
677
|
+
case 'track_analytics':
|
|
678
|
+
return this.handleTrackAnalytics(args as { eventType: string; eventData?: Record<string, unknown>; projectHash?: string });
|
|
679
|
+
|
|
537
680
|
default:
|
|
538
681
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
539
682
|
}
|
|
@@ -1445,7 +1588,7 @@ phase: development
|
|
|
1445
1588
|
## Connection Status
|
|
1446
1589
|
- **MCP Server:** Running
|
|
1447
1590
|
- **API Connected:** Yes
|
|
1448
|
-
- **Version:**
|
|
1591
|
+
- **Version:** 2.2.0
|
|
1449
1592
|
|
|
1450
1593
|
## Current Settings
|
|
1451
1594
|
- **Experience Level:** ${level.charAt(0).toUpperCase() + level.slice(1)}
|
|
@@ -1457,6 +1600,9 @@ phase: development
|
|
|
1457
1600
|
- ๐ **search_patterns** - Search for specific guidance
|
|
1458
1601
|
- ๐๏ธ **scaffold_project** - Create new projects
|
|
1459
1602
|
- โ๏ธ **init_project** - Add patterns to existing projects
|
|
1603
|
+
- ๐จ **design** - Clone designs from mockups/websites
|
|
1604
|
+
- โฌ๏ธ **upgrade** - Upgrade project patterns (preserves your stack)
|
|
1605
|
+
- ๐ **project_status** - Show build progress
|
|
1460
1606
|
|
|
1461
1607
|
## How to Use
|
|
1462
1608
|
Just describe what you want to build! I'll automatically:
|
|
@@ -1477,6 +1623,522 @@ Just describe what you want to build! I'll automatically:
|
|
|
1477
1623
|
};
|
|
1478
1624
|
}
|
|
1479
1625
|
|
|
1626
|
+
private async handleDesign(args: { source: string; outputDir?: string }) {
|
|
1627
|
+
const { source, outputDir = 'src/components' } = args;
|
|
1628
|
+
const cwd = process.cwd();
|
|
1629
|
+
|
|
1630
|
+
// Detect source type
|
|
1631
|
+
let sourceType: 'folder' | 'file' | 'url' | 'reference' = 'reference';
|
|
1632
|
+
if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
1633
|
+
sourceType = 'url';
|
|
1634
|
+
} else if (source.startsWith('./') || source.startsWith('/') || source.includes('\\')) {
|
|
1635
|
+
const fullPath = path.join(cwd, source);
|
|
1636
|
+
if (fs.existsSync(fullPath)) {
|
|
1637
|
+
const stat = fs.statSync(fullPath);
|
|
1638
|
+
sourceType = stat.isDirectory() ? 'folder' : 'file';
|
|
1639
|
+
}
|
|
1640
|
+
} else if (source.toLowerCase().startsWith('like ')) {
|
|
1641
|
+
sourceType = 'reference';
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
// Fetch design pattern from API
|
|
1645
|
+
const patternResult = await this.fetchPatterns(['33-design-clone', '09-design']);
|
|
1646
|
+
|
|
1647
|
+
let response = `# ๐จ Design Clone Tool\n\n`;
|
|
1648
|
+
response += `**Source:** ${source}\n`;
|
|
1649
|
+
response += `**Type:** ${sourceType}\n`;
|
|
1650
|
+
response += `**Output:** ${outputDir}\n\n`;
|
|
1651
|
+
|
|
1652
|
+
switch (sourceType) {
|
|
1653
|
+
case 'folder':
|
|
1654
|
+
const folderPath = path.join(cwd, source);
|
|
1655
|
+
const images = fs.readdirSync(folderPath).filter(f =>
|
|
1656
|
+
/\.(png|jpg|jpeg|webp|svg|gif)$/i.test(f)
|
|
1657
|
+
);
|
|
1658
|
+
response += `## Found ${images.length} Design Files\n\n`;
|
|
1659
|
+
images.forEach(img => {
|
|
1660
|
+
response += `- ${img}\n`;
|
|
1661
|
+
});
|
|
1662
|
+
response += `\n## Next Steps\n\n`;
|
|
1663
|
+
response += `1. I'll analyze each image for design tokens\n`;
|
|
1664
|
+
response += `2. Extract colors, typography, spacing\n`;
|
|
1665
|
+
response += `3. Generate Tailwind config with your design system\n`;
|
|
1666
|
+
response += `4. Create matching components\n\n`;
|
|
1667
|
+
response += `**Note:** For best results, provide screenshots of:\n`;
|
|
1668
|
+
response += `- Color palette / brand guidelines\n`;
|
|
1669
|
+
response += `- Typography samples\n`;
|
|
1670
|
+
response += `- Key UI components (buttons, cards, forms)\n`;
|
|
1671
|
+
break;
|
|
1672
|
+
|
|
1673
|
+
case 'file':
|
|
1674
|
+
response += `## Analyzing Single Design\n\n`;
|
|
1675
|
+
response += `I'll extract design tokens from this image and generate matching components.\n\n`;
|
|
1676
|
+
response += `**Tip:** For a complete design system, provide a folder with multiple mockups.\n`;
|
|
1677
|
+
break;
|
|
1678
|
+
|
|
1679
|
+
case 'url':
|
|
1680
|
+
response += `## Cloning Website Design\n\n`;
|
|
1681
|
+
response += `I'll analyze the visual design at ${source} and extract:\n`;
|
|
1682
|
+
response += `- Color palette\n`;
|
|
1683
|
+
response += `- Typography (fonts, sizes, weights)\n`;
|
|
1684
|
+
response += `- Spacing system\n`;
|
|
1685
|
+
response += `- Component patterns\n\n`;
|
|
1686
|
+
response += `**Note:** This creates inspired-by components, not exact copies.\n`;
|
|
1687
|
+
break;
|
|
1688
|
+
|
|
1689
|
+
case 'reference':
|
|
1690
|
+
const refStyle = source.replace(/^like\s+/i, '').toLowerCase();
|
|
1691
|
+
const knownStyles: Record<string, string> = {
|
|
1692
|
+
'linear': 'Dark theme, purple accents, minimal, clean',
|
|
1693
|
+
'notion': 'Light theme, black/white, content-focused, lots of whitespace',
|
|
1694
|
+
'stripe': 'Professional, purple gradients, polished shadows',
|
|
1695
|
+
'vercel': 'Black/white, developer-focused, geometric',
|
|
1696
|
+
'github': 'Blue accents, familiar, dev-tool aesthetic',
|
|
1697
|
+
'figma': 'Purple accents, collaborative, modern',
|
|
1698
|
+
};
|
|
1699
|
+
const matchedStyle = Object.entries(knownStyles).find(([key]) =>
|
|
1700
|
+
refStyle.includes(key)
|
|
1701
|
+
);
|
|
1702
|
+
if (matchedStyle) {
|
|
1703
|
+
response += `## Reference Style: ${matchedStyle[0]}\n\n`;
|
|
1704
|
+
response += `**Characteristics:** ${matchedStyle[1]}\n\n`;
|
|
1705
|
+
} else {
|
|
1706
|
+
response += `## Reference Style: "${source}"\n\n`;
|
|
1707
|
+
response += `I'll apply a design language inspired by this reference.\n\n`;
|
|
1708
|
+
}
|
|
1709
|
+
response += `I'll generate components matching this aesthetic.\n`;
|
|
1710
|
+
break;
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
response += `\n---\n\n`;
|
|
1714
|
+
response += `## Design Pattern Loaded\n\n`;
|
|
1715
|
+
response += `The design-clone pattern (33-design-clone) is now active.\n`;
|
|
1716
|
+
response += `Proceed with specific component requests like:\n`;
|
|
1717
|
+
response += `- "Create the navigation bar"\n`;
|
|
1718
|
+
response += `- "Build the hero section"\n`;
|
|
1719
|
+
response += `- "Generate the card components"\n`;
|
|
1720
|
+
|
|
1721
|
+
return {
|
|
1722
|
+
content: [{
|
|
1723
|
+
type: 'text' as const,
|
|
1724
|
+
text: response,
|
|
1725
|
+
}],
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
private async handleUpgrade(args: { areas?: string[]; severity?: string; dryRun?: boolean }) {
|
|
1730
|
+
const { areas = ['all'], severity = 'all', dryRun = false } = args;
|
|
1731
|
+
const context = this.gatherProjectContext();
|
|
1732
|
+
|
|
1733
|
+
let response = `# โฌ๏ธ Project Upgrade Analysis\n\n`;
|
|
1734
|
+
|
|
1735
|
+
// Stack detection
|
|
1736
|
+
response += `## Your Stack (Preserving As-Is)\n\n`;
|
|
1737
|
+
response += `| Layer | Detected | Status |\n`;
|
|
1738
|
+
response += `|-------|----------|--------|\n`;
|
|
1739
|
+
|
|
1740
|
+
// ORM detection
|
|
1741
|
+
let orm = 'None';
|
|
1742
|
+
if (context.dependencies.includes('drizzle-orm')) orm = 'Drizzle';
|
|
1743
|
+
else if (context.dependencies.includes('@prisma/client')) orm = 'Prisma';
|
|
1744
|
+
else if (context.dependencies.includes('typeorm')) orm = 'TypeORM';
|
|
1745
|
+
else if (context.dependencies.includes('mongoose')) orm = 'Mongoose';
|
|
1746
|
+
response += `| ORM | ${orm} | โ Keeping |\n`;
|
|
1747
|
+
|
|
1748
|
+
// Auth detection
|
|
1749
|
+
let auth = 'None';
|
|
1750
|
+
if (context.dependencies.includes('@supabase/supabase-js')) auth = 'Supabase';
|
|
1751
|
+
else if (context.dependencies.includes('next-auth')) auth = 'NextAuth';
|
|
1752
|
+
else if (context.dependencies.includes('@clerk/nextjs')) auth = 'Clerk';
|
|
1753
|
+
else if (context.dependencies.includes('firebase')) auth = 'Firebase';
|
|
1754
|
+
response += `| Auth | ${auth} | โ Keeping |\n`;
|
|
1755
|
+
|
|
1756
|
+
// UI detection
|
|
1757
|
+
response += `| UI | ${context.uiLibrary || 'Tailwind'} | โ Keeping |\n`;
|
|
1758
|
+
|
|
1759
|
+
// Framework (always Next.js for now)
|
|
1760
|
+
const hasNext = context.dependencies.includes('next');
|
|
1761
|
+
response += `| Framework | ${hasNext ? 'Next.js' : 'Unknown'} | โ Keeping |\n`;
|
|
1762
|
+
|
|
1763
|
+
response += `\n---\n\n`;
|
|
1764
|
+
|
|
1765
|
+
// Scan for upgrade opportunities
|
|
1766
|
+
response += `## Upgrade Opportunities\n\n`;
|
|
1767
|
+
|
|
1768
|
+
const upgrades: Array<{ area: string; issue: string; severity: string; count: number }> = [];
|
|
1769
|
+
|
|
1770
|
+
// Check API routes
|
|
1771
|
+
if (context.existingApiRoutes.length > 0) {
|
|
1772
|
+
upgrades.push({
|
|
1773
|
+
area: 'API Routes',
|
|
1774
|
+
issue: 'Add error handling, validation, rate limiting',
|
|
1775
|
+
severity: 'HIGH',
|
|
1776
|
+
count: context.existingApiRoutes.length,
|
|
1777
|
+
});
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
// Check components
|
|
1781
|
+
if (context.existingComponents.length > 0) {
|
|
1782
|
+
upgrades.push({
|
|
1783
|
+
area: 'Components',
|
|
1784
|
+
issue: 'Add loading states, error boundaries, accessibility',
|
|
1785
|
+
severity: 'MEDIUM',
|
|
1786
|
+
count: context.existingComponents.length,
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
// Check for tests
|
|
1791
|
+
const hasTests = context.dependencies.includes('@playwright/test') ||
|
|
1792
|
+
context.dependencies.includes('jest') ||
|
|
1793
|
+
context.dependencies.includes('vitest');
|
|
1794
|
+
if (!hasTests) {
|
|
1795
|
+
upgrades.push({
|
|
1796
|
+
area: 'Testing',
|
|
1797
|
+
issue: 'No test framework detected',
|
|
1798
|
+
severity: 'HIGH',
|
|
1799
|
+
count: 0,
|
|
1800
|
+
});
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
// Check for Zod
|
|
1804
|
+
const hasZod = context.dependencies.includes('zod');
|
|
1805
|
+
if (!hasZod && context.existingApiRoutes.length > 0) {
|
|
1806
|
+
upgrades.push({
|
|
1807
|
+
area: 'Validation',
|
|
1808
|
+
issue: 'No Zod validation detected for API routes',
|
|
1809
|
+
severity: 'HIGH',
|
|
1810
|
+
count: context.existingApiRoutes.length,
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
// Display upgrades
|
|
1815
|
+
for (const upgrade of upgrades) {
|
|
1816
|
+
const icon = upgrade.severity === 'HIGH' ? '๐ด' :
|
|
1817
|
+
upgrade.severity === 'MEDIUM' ? '๐ก' : '๐ข';
|
|
1818
|
+
response += `### ${icon} ${upgrade.area}\n`;
|
|
1819
|
+
response += `- **Issue:** ${upgrade.issue}\n`;
|
|
1820
|
+
if (upgrade.count > 0) {
|
|
1821
|
+
response += `- **Affected:** ${upgrade.count} files\n`;
|
|
1822
|
+
}
|
|
1823
|
+
response += `- **Severity:** ${upgrade.severity}\n\n`;
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
if (upgrades.length === 0) {
|
|
1827
|
+
response += `โ
No major upgrade opportunities detected!\n\n`;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// Recommendations
|
|
1831
|
+
response += `---\n\n`;
|
|
1832
|
+
response += `## Recommended Actions\n\n`;
|
|
1833
|
+
|
|
1834
|
+
if (dryRun) {
|
|
1835
|
+
response += `**(Dry Run Mode - No changes will be made)**\n\n`;
|
|
1836
|
+
}
|
|
1837
|
+
|
|
1838
|
+
response += `1. Run \`run_audit\` for detailed code quality check\n`;
|
|
1839
|
+
response += `2. Use \`heal\` to auto-fix common issues\n`;
|
|
1840
|
+
response += `3. Request specific upgrades like:\n`;
|
|
1841
|
+
response += ` - "Add Zod validation to my API routes"\n`;
|
|
1842
|
+
response += ` - "Add error boundaries to components"\n`;
|
|
1843
|
+
response += ` - "Set up Playwright testing"\n\n`;
|
|
1844
|
+
|
|
1845
|
+
response += `---\n\n`;
|
|
1846
|
+
response += `**Key Principle:** Your stack stays the same. Only code quality patterns are upgraded.\n`;
|
|
1847
|
+
|
|
1848
|
+
return {
|
|
1849
|
+
content: [{
|
|
1850
|
+
type: 'text' as const,
|
|
1851
|
+
text: response,
|
|
1852
|
+
}],
|
|
1853
|
+
};
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
private handleProjectStatus() {
|
|
1857
|
+
const cwd = process.cwd();
|
|
1858
|
+
const context = this.gatherProjectContext();
|
|
1859
|
+
|
|
1860
|
+
let response = `# ๐ Project Status\n\n`;
|
|
1861
|
+
response += `**Project:** ${context.projectName}\n\n`;
|
|
1862
|
+
|
|
1863
|
+
// Check for .codebakers.json state
|
|
1864
|
+
let state: Record<string, unknown> | null = null;
|
|
1865
|
+
try {
|
|
1866
|
+
const statePath = path.join(cwd, '.codebakers.json');
|
|
1867
|
+
if (fs.existsSync(statePath)) {
|
|
1868
|
+
state = JSON.parse(fs.readFileSync(statePath, 'utf-8'));
|
|
1869
|
+
}
|
|
1870
|
+
} catch {
|
|
1871
|
+
// No state file
|
|
1872
|
+
}
|
|
1873
|
+
|
|
1874
|
+
// Check for PRD
|
|
1875
|
+
const prdPath = path.join(cwd, 'PRD.md');
|
|
1876
|
+
const hasPrd = fs.existsSync(prdPath);
|
|
1877
|
+
|
|
1878
|
+
// Check for PROJECT-STATE.md
|
|
1879
|
+
const projectStatePath = path.join(cwd, 'PROJECT-STATE.md');
|
|
1880
|
+
const hasProjectState = fs.existsSync(projectStatePath);
|
|
1881
|
+
|
|
1882
|
+
if (state && typeof state === 'object') {
|
|
1883
|
+
const s = state as Record<string, unknown>;
|
|
1884
|
+
|
|
1885
|
+
// Build progress
|
|
1886
|
+
if (s.build && typeof s.build === 'object') {
|
|
1887
|
+
const build = s.build as Record<string, unknown>;
|
|
1888
|
+
response += `## Build Progress\n\n`;
|
|
1889
|
+
const currentPhase = build.currentPhase as number || 0;
|
|
1890
|
+
const totalPhases = build.totalPhases as number || 1;
|
|
1891
|
+
const percent = Math.round((currentPhase / totalPhases) * 100);
|
|
1892
|
+
response += `**Phase ${currentPhase}/${totalPhases}** (${percent}%)\n\n`;
|
|
1893
|
+
|
|
1894
|
+
// Progress bar
|
|
1895
|
+
const filled = Math.round(percent / 10);
|
|
1896
|
+
const empty = 10 - filled;
|
|
1897
|
+
response += `[${'โ'.repeat(filled)}${'โ'.repeat(empty)}] ${percent}%\n\n`;
|
|
1898
|
+
|
|
1899
|
+
if (build.status) {
|
|
1900
|
+
response += `**Status:** ${build.status}\n\n`;
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1904
|
+
// Current work
|
|
1905
|
+
if (s.currentWork && typeof s.currentWork === 'object') {
|
|
1906
|
+
const work = s.currentWork as Record<string, unknown>;
|
|
1907
|
+
response += `## Current Work\n\n`;
|
|
1908
|
+
if (work.activeFeature) {
|
|
1909
|
+
response += `**In Progress:** ${work.activeFeature}\n`;
|
|
1910
|
+
}
|
|
1911
|
+
if (work.summary) {
|
|
1912
|
+
response += `**Summary:** ${work.summary}\n`;
|
|
1913
|
+
}
|
|
1914
|
+
if (work.lastUpdated) {
|
|
1915
|
+
response += `**Last Updated:** ${work.lastUpdated}\n`;
|
|
1916
|
+
}
|
|
1917
|
+
response += `\n`;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
// Stack
|
|
1921
|
+
if (s.stack && typeof s.stack === 'object') {
|
|
1922
|
+
const stack = s.stack as Record<string, unknown>;
|
|
1923
|
+
response += `## Stack\n\n`;
|
|
1924
|
+
for (const [key, value] of Object.entries(stack)) {
|
|
1925
|
+
if (value) {
|
|
1926
|
+
response += `- **${key}:** ${value}\n`;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
response += `\n`;
|
|
1930
|
+
}
|
|
1931
|
+
} else {
|
|
1932
|
+
response += `## Project Overview\n\n`;
|
|
1933
|
+
response += `- **PRD:** ${hasPrd ? 'โ
Found' : 'โ Not found'}\n`;
|
|
1934
|
+
response += `- **State Tracking:** ${hasProjectState ? 'โ
Found' : 'โ Not found'}\n`;
|
|
1935
|
+
response += `- **CodeBakers State:** ${state ? 'โ
Found' : 'โ Not initialized'}\n\n`;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
// What's built
|
|
1939
|
+
response += `## What's Built\n\n`;
|
|
1940
|
+
response += `- **Components:** ${context.existingComponents.length}\n`;
|
|
1941
|
+
response += `- **API Routes:** ${context.existingApiRoutes.length}\n`;
|
|
1942
|
+
response += `- **Services:** ${context.existingServices.length}\n`;
|
|
1943
|
+
|
|
1944
|
+
if (context.hasAuth) response += `- โ
Authentication\n`;
|
|
1945
|
+
if (context.hasDatabase) response += `- โ
Database\n`;
|
|
1946
|
+
if (context.hasPayments) response += `- โ
Payments\n`;
|
|
1947
|
+
|
|
1948
|
+
response += `\n`;
|
|
1949
|
+
|
|
1950
|
+
// Recent components
|
|
1951
|
+
if (context.existingComponents.length > 0) {
|
|
1952
|
+
response += `### Recent Components\n`;
|
|
1953
|
+
context.existingComponents.slice(0, 10).forEach(comp => {
|
|
1954
|
+
response += `- ${comp}\n`;
|
|
1955
|
+
});
|
|
1956
|
+
if (context.existingComponents.length > 10) {
|
|
1957
|
+
response += `- ... and ${context.existingComponents.length - 10} more\n`;
|
|
1958
|
+
}
|
|
1959
|
+
response += `\n`;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
response += `---\n\n`;
|
|
1963
|
+
response += `*Run \`upgrade\` to improve code quality or \`run_audit\` for detailed analysis.*`;
|
|
1964
|
+
|
|
1965
|
+
return {
|
|
1966
|
+
content: [{
|
|
1967
|
+
type: 'text' as const,
|
|
1968
|
+
text: response,
|
|
1969
|
+
}],
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
private handleRunTests(args: { filter?: string; watch?: boolean }) {
|
|
1974
|
+
const { filter, watch = false } = args;
|
|
1975
|
+
const cwd = process.cwd();
|
|
1976
|
+
|
|
1977
|
+
// Detect test runner from package.json
|
|
1978
|
+
let testCommand = 'npm test';
|
|
1979
|
+
try {
|
|
1980
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
1981
|
+
if (fs.existsSync(pkgPath)) {
|
|
1982
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
1983
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1984
|
+
|
|
1985
|
+
if (deps['@playwright/test']) {
|
|
1986
|
+
testCommand = 'npx playwright test';
|
|
1987
|
+
} else if (deps['vitest']) {
|
|
1988
|
+
testCommand = 'npx vitest run';
|
|
1989
|
+
} else if (deps['jest']) {
|
|
1990
|
+
testCommand = 'npx jest';
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
} catch {
|
|
1994
|
+
// Use default
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// Add filter if provided
|
|
1998
|
+
if (filter) {
|
|
1999
|
+
testCommand += ` ${filter}`;
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// Add watch mode
|
|
2003
|
+
if (watch) {
|
|
2004
|
+
testCommand = testCommand.replace(' run', ''); // Remove 'run' for vitest watch
|
|
2005
|
+
if (testCommand.includes('vitest')) {
|
|
2006
|
+
testCommand = testCommand.replace('vitest run', 'vitest');
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
let response = `# ๐งช Running Tests\n\n`;
|
|
2011
|
+
response += `**Command:** \`${testCommand}\`\n\n`;
|
|
2012
|
+
|
|
2013
|
+
try {
|
|
2014
|
+
const output = execSync(testCommand, {
|
|
2015
|
+
cwd,
|
|
2016
|
+
encoding: 'utf-8',
|
|
2017
|
+
timeout: 120000, // 2 minute timeout
|
|
2018
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
2019
|
+
});
|
|
2020
|
+
|
|
2021
|
+
response += `## โ
Tests Passed\n\n`;
|
|
2022
|
+
response += '```\n' + output.slice(-2000) + '\n```\n';
|
|
2023
|
+
} catch (error) {
|
|
2024
|
+
const execError = error as { stdout?: string; stderr?: string; status?: number };
|
|
2025
|
+
response += `## โ Tests Failed\n\n`;
|
|
2026
|
+
|
|
2027
|
+
if (execError.stdout) {
|
|
2028
|
+
response += '### Output\n```\n' + execError.stdout.slice(-2000) + '\n```\n\n';
|
|
2029
|
+
}
|
|
2030
|
+
if (execError.stderr) {
|
|
2031
|
+
response += '### Errors\n```\n' + execError.stderr.slice(-1000) + '\n```\n\n';
|
|
2032
|
+
}
|
|
2033
|
+
|
|
2034
|
+
response += `**Exit Code:** ${execError.status || 1}\n\n`;
|
|
2035
|
+
response += `---\n\n*Fix the failing tests and run again.*`;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
return {
|
|
2039
|
+
content: [{
|
|
2040
|
+
type: 'text' as const,
|
|
2041
|
+
text: response,
|
|
2042
|
+
}],
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
private async handleReportPatternGap(args: { category: string; request: string; context?: string; handledWith?: string; wasSuccessful?: boolean }) {
|
|
2047
|
+
const { category, request, context, handledWith, wasSuccessful = true } = args;
|
|
2048
|
+
|
|
2049
|
+
try {
|
|
2050
|
+
const response = await fetch(`${this.apiUrl}/api/pattern-gaps`, {
|
|
2051
|
+
method: 'POST',
|
|
2052
|
+
headers: {
|
|
2053
|
+
'Content-Type': 'application/json',
|
|
2054
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
2055
|
+
},
|
|
2056
|
+
body: JSON.stringify({
|
|
2057
|
+
category,
|
|
2058
|
+
request,
|
|
2059
|
+
context,
|
|
2060
|
+
handledWith,
|
|
2061
|
+
wasSuccessful,
|
|
2062
|
+
}),
|
|
2063
|
+
});
|
|
2064
|
+
|
|
2065
|
+
if (!response.ok) {
|
|
2066
|
+
const error = await response.json().catch(() => ({}));
|
|
2067
|
+
throw new Error(error.error || 'Failed to report pattern gap');
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
const result = await response.json();
|
|
2071
|
+
|
|
2072
|
+
if (result.deduplicated) {
|
|
2073
|
+
return {
|
|
2074
|
+
content: [{
|
|
2075
|
+
type: 'text' as const,
|
|
2076
|
+
text: `๐ Pattern gap already reported recently (category: ${category}). No duplicate created.`,
|
|
2077
|
+
}],
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
return {
|
|
2082
|
+
content: [{
|
|
2083
|
+
type: 'text' as const,
|
|
2084
|
+
text: `โ
Pattern gap reported successfully.\n\n**Category:** ${category}\n**Request:** ${request}\n\nThis helps improve CodeBakers for everyone!`,
|
|
2085
|
+
}],
|
|
2086
|
+
};
|
|
2087
|
+
} catch (error) {
|
|
2088
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
2089
|
+
return {
|
|
2090
|
+
content: [{
|
|
2091
|
+
type: 'text' as const,
|
|
2092
|
+
text: `โ ๏ธ Could not report pattern gap: ${message}\n\n(This doesn't affect your current work)`,
|
|
2093
|
+
}],
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
private async handleTrackAnalytics(args: { eventType: string; eventData?: Record<string, unknown>; projectHash?: string }) {
|
|
2099
|
+
const { eventType, eventData, projectHash } = args;
|
|
2100
|
+
|
|
2101
|
+
try {
|
|
2102
|
+
const response = await fetch(`${this.apiUrl}/api/cli/analytics`, {
|
|
2103
|
+
method: 'POST',
|
|
2104
|
+
headers: {
|
|
2105
|
+
'Content-Type': 'application/json',
|
|
2106
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
2107
|
+
},
|
|
2108
|
+
body: JSON.stringify({
|
|
2109
|
+
eventType,
|
|
2110
|
+
eventData,
|
|
2111
|
+
projectHash,
|
|
2112
|
+
}),
|
|
2113
|
+
});
|
|
2114
|
+
|
|
2115
|
+
if (!response.ok) {
|
|
2116
|
+
// Silently fail - analytics shouldn't interrupt user workflow
|
|
2117
|
+
return {
|
|
2118
|
+
content: [{
|
|
2119
|
+
type: 'text' as const,
|
|
2120
|
+
text: `๐ Analytics tracked: ${eventType}`,
|
|
2121
|
+
}],
|
|
2122
|
+
};
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
return {
|
|
2126
|
+
content: [{
|
|
2127
|
+
type: 'text' as const,
|
|
2128
|
+
text: `๐ Analytics tracked: ${eventType}`,
|
|
2129
|
+
}],
|
|
2130
|
+
};
|
|
2131
|
+
} catch {
|
|
2132
|
+
// Silently fail
|
|
2133
|
+
return {
|
|
2134
|
+
content: [{
|
|
2135
|
+
type: 'text' as const,
|
|
2136
|
+
text: `๐ Analytics tracked: ${eventType}`,
|
|
2137
|
+
}],
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
|
|
1480
2142
|
async run(): Promise<void> {
|
|
1481
2143
|
const transport = new StdioServerTransport();
|
|
1482
2144
|
await this.server.connect(transport);
|