@codebakers/cli 2.6.2 → 2.8.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.
@@ -718,6 +718,81 @@ class CodeBakersServer {
718
718
  required: ['eventType'],
719
719
  },
720
720
  },
721
+ {
722
+ name: 'vercel_logs',
723
+ description: 'Fetch runtime logs from Vercel for the current project. Use when user asks about errors, API failures, production issues, or wants to debug their deployed app. Requires VERCEL_TOKEN env var or vercel login. Examples: "show me errors from yesterday", "what API calls are failing", "why is my app crashing".',
724
+ inputSchema: {
725
+ type: 'object',
726
+ properties: {
727
+ hours: {
728
+ type: 'number',
729
+ description: 'How many hours of logs to fetch (default: 24, max: 168)',
730
+ },
731
+ level: {
732
+ type: 'string',
733
+ enum: ['error', 'warn', 'info', 'all'],
734
+ description: 'Filter by log level (default: error)',
735
+ },
736
+ route: {
737
+ type: 'string',
738
+ description: 'Filter logs by API route path (e.g., "/api/auth", "/api/payments")',
739
+ },
740
+ limit: {
741
+ type: 'number',
742
+ description: 'Maximum number of log entries to return (default: 50, max: 500)',
743
+ },
744
+ },
745
+ },
746
+ },
747
+ {
748
+ name: 'vercel_analyze_errors',
749
+ description: 'Analyze Vercel logs and suggest fixes using CodeBakers patterns. Fetches recent errors, classifies them, and provides actionable fixes. Use when user says "fix my production errors", "why is X failing", or "help me debug".',
750
+ inputSchema: {
751
+ type: 'object',
752
+ properties: {
753
+ hours: {
754
+ type: 'number',
755
+ description: 'How many hours of logs to analyze (default: 24)',
756
+ },
757
+ autoFix: {
758
+ type: 'boolean',
759
+ description: 'Automatically apply safe fixes with high confidence (default: false)',
760
+ },
761
+ },
762
+ },
763
+ },
764
+ {
765
+ name: 'vercel_deployments',
766
+ description: 'List recent Vercel deployments and their status. Use when user asks "what was deployed", "show deployment history", or "why did the last deploy fail".',
767
+ inputSchema: {
768
+ type: 'object',
769
+ properties: {
770
+ limit: {
771
+ type: 'number',
772
+ description: 'Number of deployments to show (default: 10)',
773
+ },
774
+ state: {
775
+ type: 'string',
776
+ enum: ['READY', 'ERROR', 'BUILDING', 'QUEUED', 'CANCELED', 'all'],
777
+ description: 'Filter by deployment state',
778
+ },
779
+ },
780
+ },
781
+ },
782
+ {
783
+ name: 'vercel_connect',
784
+ description: 'Connect to Vercel using an API token. Required before using other Vercel tools. Token is stored securely in config. Use when user says "connect to vercel", "setup vercel", or before any vercel_* tool if not connected.',
785
+ inputSchema: {
786
+ type: 'object',
787
+ properties: {
788
+ token: {
789
+ type: 'string',
790
+ description: 'Vercel API token from https://vercel.com/account/tokens',
791
+ },
792
+ },
793
+ required: ['token'],
794
+ },
795
+ },
721
796
  ],
722
797
  }));
723
798
  // Handle tool calls
@@ -765,6 +840,14 @@ class CodeBakersServer {
765
840
  return this.handleReportPatternGap(args);
766
841
  case 'track_analytics':
767
842
  return this.handleTrackAnalytics(args);
843
+ case 'vercel_logs':
844
+ return this.handleVercelLogs(args);
845
+ case 'vercel_analyze_errors':
846
+ return this.handleVercelAnalyzeErrors(args);
847
+ case 'vercel_deployments':
848
+ return this.handleVercelDeployments(args);
849
+ case 'vercel_connect':
850
+ return this.handleVercelConnect(args);
768
851
  default:
769
852
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
770
853
  }
@@ -2392,6 +2475,395 @@ Just describe what you want to build! I'll automatically:
2392
2475
  };
2393
2476
  }
2394
2477
  }
2478
+ // ========== VERCEL INTEGRATION ==========
2479
+ getVercelToken() {
2480
+ // Check config first, then env var
2481
+ return (0, config_js_1.getServiceKey)('vercel') || process.env.VERCEL_TOKEN || null;
2482
+ }
2483
+ async handleVercelConnect(args) {
2484
+ const { token } = args;
2485
+ // Validate the token by making a test API call
2486
+ try {
2487
+ const response = await fetch('https://api.vercel.com/v2/user', {
2488
+ headers: { Authorization: `Bearer ${token}` },
2489
+ });
2490
+ if (!response.ok) {
2491
+ return {
2492
+ content: [{
2493
+ type: 'text',
2494
+ text: `❌ Invalid Vercel token. Please check your token and try again.\n\nGet a new token at: https://vercel.com/account/tokens`,
2495
+ }],
2496
+ isError: true,
2497
+ };
2498
+ }
2499
+ const user = await response.json();
2500
+ // Store the token securely
2501
+ (0, config_js_1.setServiceKey)('vercel', token);
2502
+ return {
2503
+ content: [{
2504
+ type: 'text',
2505
+ text: `✅ Connected to Vercel as ${user.user?.username || user.user?.email || 'unknown user'}\n\nYou can now use:\n- vercel_logs: Fetch runtime logs\n- vercel_analyze_errors: Analyze and fix errors\n- vercel_deployments: View deployment history`,
2506
+ }],
2507
+ };
2508
+ }
2509
+ catch (error) {
2510
+ return {
2511
+ content: [{
2512
+ type: 'text',
2513
+ text: `❌ Failed to connect to Vercel: ${error instanceof Error ? error.message : 'Unknown error'}`,
2514
+ }],
2515
+ isError: true,
2516
+ };
2517
+ }
2518
+ }
2519
+ async handleVercelLogs(args) {
2520
+ const token = this.getVercelToken();
2521
+ if (!token) {
2522
+ return {
2523
+ content: [{
2524
+ type: 'text',
2525
+ text: `❌ Not connected to Vercel.\n\nTo connect, either:\n1. Use the vercel_connect tool with your API token\n2. Set VERCEL_TOKEN environment variable\n\nGet a token at: https://vercel.com/account/tokens`,
2526
+ }],
2527
+ isError: true,
2528
+ };
2529
+ }
2530
+ const hours = Math.min(args.hours || 24, 168); // Max 7 days
2531
+ const level = args.level || 'error';
2532
+ const limit = Math.min(args.limit || 50, 500);
2533
+ const since = Date.now() - (hours * 60 * 60 * 1000);
2534
+ try {
2535
+ // First, get the team/user's projects
2536
+ const projectsRes = await fetch('https://api.vercel.com/v9/projects?limit=10', {
2537
+ headers: { Authorization: `Bearer ${token}` },
2538
+ });
2539
+ if (!projectsRes.ok) {
2540
+ throw new Error(`Failed to fetch projects: ${projectsRes.statusText}`);
2541
+ }
2542
+ const projectsData = await projectsRes.json();
2543
+ const projects = projectsData.projects || [];
2544
+ if (projects.length === 0) {
2545
+ return {
2546
+ content: [{
2547
+ type: 'text',
2548
+ text: `❌ No Vercel projects found. Make sure your token has access to your projects.`,
2549
+ }],
2550
+ isError: true,
2551
+ };
2552
+ }
2553
+ // Try to match current project by name
2554
+ const cwd = process.cwd();
2555
+ const packageJsonPath = `${cwd}/package.json`;
2556
+ let currentProjectName = '';
2557
+ try {
2558
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
2559
+ currentProjectName = pkg.name?.replace(/^@[^/]+\//, '') || '';
2560
+ }
2561
+ catch {
2562
+ // Ignore
2563
+ }
2564
+ // Find matching project or use first one
2565
+ const project = projects.find((p) => p.name.toLowerCase() === currentProjectName.toLowerCase()) || projects[0];
2566
+ // Fetch logs for the project
2567
+ // Note: Vercel's logs API varies by plan. Using runtime logs endpoint
2568
+ const logsUrl = new URL(`https://api.vercel.com/v1/projects/${project.id}/logs`);
2569
+ logsUrl.searchParams.set('since', since.toString());
2570
+ logsUrl.searchParams.set('limit', limit.toString());
2571
+ if (level !== 'all') {
2572
+ logsUrl.searchParams.set('level', level);
2573
+ }
2574
+ if (args.route) {
2575
+ logsUrl.searchParams.set('path', args.route);
2576
+ }
2577
+ const logsRes = await fetch(logsUrl.toString(), {
2578
+ headers: { Authorization: `Bearer ${token}` },
2579
+ });
2580
+ if (!logsRes.ok) {
2581
+ // Try alternative endpoint for edge/serverless logs
2582
+ const altLogsRes = await fetch(`https://api.vercel.com/v2/deployments/${project.latestDeployments?.[0]?.id}/events?limit=${limit}`, { headers: { Authorization: `Bearer ${token}` } });
2583
+ if (!altLogsRes.ok) {
2584
+ throw new Error(`Failed to fetch logs: ${logsRes.statusText}. Your Vercel plan may not support log access via API.`);
2585
+ }
2586
+ const altLogs = await altLogsRes.json();
2587
+ return this.formatVercelLogs(altLogs, project.name, hours, level);
2588
+ }
2589
+ const logs = await logsRes.json();
2590
+ return this.formatVercelLogs(logs, project.name, hours, level);
2591
+ }
2592
+ catch (error) {
2593
+ return {
2594
+ content: [{
2595
+ type: 'text',
2596
+ text: `❌ Failed to fetch Vercel logs: ${error instanceof Error ? error.message : 'Unknown error'}`,
2597
+ }],
2598
+ isError: true,
2599
+ };
2600
+ }
2601
+ }
2602
+ formatVercelLogs(logs, projectName, hours, level) {
2603
+ const logEntries = Array.isArray(logs) ? logs : logs?.logs || [];
2604
+ if (logEntries.length === 0) {
2605
+ return {
2606
+ content: [{
2607
+ type: 'text',
2608
+ text: `📋 No ${level === 'all' ? '' : level + ' '}logs found for **${projectName}** in the last ${hours} hours.\n\nThis could mean:\n- No matching log entries exist\n- Your Vercel plan may have limited log retention\n- Logs may still be processing`,
2609
+ }],
2610
+ };
2611
+ }
2612
+ const formattedLogs = logEntries.slice(0, 50).map((log) => {
2613
+ const timestamp = log.timestamp || log.created || log.createdAt;
2614
+ const date = timestamp ? new Date(timestamp).toISOString() : 'Unknown';
2615
+ const logLevel = (log.level || log.type || 'info');
2616
+ const message = log.message || log.text || log.payload || JSON.stringify(log);
2617
+ const path = log.path || log.route || '';
2618
+ return `[${date}] ${logLevel.toUpperCase()}${path ? ` ${path}` : ''}\n${message}`;
2619
+ }).join('\n\n---\n\n');
2620
+ const summary = this.summarizeErrors(logEntries);
2621
+ return {
2622
+ content: [{
2623
+ type: 'text',
2624
+ text: `# Vercel Logs: ${projectName}\n\n**Period:** Last ${hours} hours\n**Filter:** ${level}\n**Found:** ${logEntries.length} entries\n\n${summary}\n\n## Log Entries\n\n${formattedLogs}`,
2625
+ }],
2626
+ };
2627
+ }
2628
+ summarizeErrors(logs) {
2629
+ const errorCounts = {};
2630
+ const routeCounts = {};
2631
+ logs.forEach((log) => {
2632
+ const message = String(log.message || log.text || '');
2633
+ const path = String(log.path || log.route || 'unknown');
2634
+ // Extract error type from message
2635
+ const errorMatch = message.match(/^(\w+Error):|Error:\s*(\w+)/);
2636
+ if (errorMatch) {
2637
+ const errorType = errorMatch[1] || errorMatch[2];
2638
+ errorCounts[errorType] = (errorCounts[errorType] || 0) + 1;
2639
+ }
2640
+ // Count by route
2641
+ if (path !== 'unknown') {
2642
+ routeCounts[path] = (routeCounts[path] || 0) + 1;
2643
+ }
2644
+ });
2645
+ const topErrors = Object.entries(errorCounts)
2646
+ .sort(([, a], [, b]) => b - a)
2647
+ .slice(0, 5);
2648
+ const topRoutes = Object.entries(routeCounts)
2649
+ .sort(([, a], [, b]) => b - a)
2650
+ .slice(0, 5);
2651
+ let summary = '## Summary\n\n';
2652
+ if (topErrors.length > 0) {
2653
+ summary += '**Top Error Types:**\n';
2654
+ topErrors.forEach(([error, count]) => {
2655
+ summary += `- ${error}: ${count} occurrences\n`;
2656
+ });
2657
+ summary += '\n';
2658
+ }
2659
+ if (topRoutes.length > 0) {
2660
+ summary += '**Most Affected Routes:**\n';
2661
+ topRoutes.forEach(([route, count]) => {
2662
+ summary += `- ${route}: ${count} errors\n`;
2663
+ });
2664
+ }
2665
+ return summary || 'No patterns detected in logs.';
2666
+ }
2667
+ async handleVercelAnalyzeErrors(args) {
2668
+ const token = this.getVercelToken();
2669
+ if (!token) {
2670
+ return {
2671
+ content: [{
2672
+ type: 'text',
2673
+ text: `❌ Not connected to Vercel. Use vercel_connect first.`,
2674
+ }],
2675
+ isError: true,
2676
+ };
2677
+ }
2678
+ const hours = args.hours || 24;
2679
+ try {
2680
+ // Fetch error logs
2681
+ const logsResult = await this.handleVercelLogs({ hours, level: 'error', limit: 100 });
2682
+ const logsText = logsResult.content?.[0]?.text || '';
2683
+ if (logsText.includes('No') && logsText.includes('logs found')) {
2684
+ return {
2685
+ content: [{
2686
+ type: 'text',
2687
+ text: `✅ No errors found in the last ${hours} hours! Your app is running smoothly.`,
2688
+ }],
2689
+ };
2690
+ }
2691
+ // Classify errors and suggest fixes
2692
+ const analysis = this.classifyAndSuggestFixes(logsText);
2693
+ return {
2694
+ content: [{
2695
+ type: 'text',
2696
+ text: `# Error Analysis\n\n${analysis}`,
2697
+ }],
2698
+ };
2699
+ }
2700
+ catch (error) {
2701
+ return {
2702
+ content: [{
2703
+ type: 'text',
2704
+ text: `❌ Failed to analyze errors: ${error instanceof Error ? error.message : 'Unknown error'}`,
2705
+ }],
2706
+ isError: true,
2707
+ };
2708
+ }
2709
+ }
2710
+ classifyAndSuggestFixes(logsText) {
2711
+ const issues = [];
2712
+ // Common error patterns and their fixes
2713
+ const errorPatterns = [
2714
+ {
2715
+ pattern: /TypeError.*undefined|Cannot read propert/gi,
2716
+ type: 'Null/Undefined Access',
2717
+ severity: 'HIGH',
2718
+ fix: 'Add null checks or optional chaining (?.) before accessing properties.',
2719
+ codePattern: '02-auth',
2720
+ },
2721
+ {
2722
+ pattern: /ECONNREFUSED|ETIMEDOUT|fetch failed/gi,
2723
+ type: 'Network/Connection Error',
2724
+ severity: 'HIGH',
2725
+ fix: 'Add retry logic with exponential backoff. Check if external services are available.',
2726
+ codePattern: '03-api',
2727
+ },
2728
+ {
2729
+ pattern: /401|Unauthorized|invalid.*token/gi,
2730
+ type: 'Authentication Error',
2731
+ severity: 'CRITICAL',
2732
+ fix: 'Check token expiration and refresh logic. Verify auth middleware is properly configured.',
2733
+ codePattern: '02-auth',
2734
+ },
2735
+ {
2736
+ pattern: /500|Internal Server Error/gi,
2737
+ type: 'Server Error',
2738
+ severity: 'CRITICAL',
2739
+ fix: 'Add proper error boundaries and try-catch blocks. Check server logs for stack traces.',
2740
+ codePattern: '00-core',
2741
+ },
2742
+ {
2743
+ pattern: /429|Too Many Requests|rate limit/gi,
2744
+ type: 'Rate Limiting',
2745
+ severity: 'MEDIUM',
2746
+ fix: 'Implement request throttling and caching. Add rate limit headers handling.',
2747
+ codePattern: '03-api',
2748
+ },
2749
+ {
2750
+ pattern: /CORS|cross-origin|Access-Control/gi,
2751
+ type: 'CORS Error',
2752
+ severity: 'MEDIUM',
2753
+ fix: 'Configure CORS headers in next.config.js or API routes. Check allowed origins.',
2754
+ codePattern: '03-api',
2755
+ },
2756
+ {
2757
+ pattern: /prisma|drizzle|database|sql/gi,
2758
+ type: 'Database Error',
2759
+ severity: 'HIGH',
2760
+ fix: 'Check database connection string. Verify migrations are applied. Add connection pooling.',
2761
+ codePattern: '01-database',
2762
+ },
2763
+ {
2764
+ pattern: /stripe|payment|charge failed/gi,
2765
+ type: 'Payment Error',
2766
+ severity: 'CRITICAL',
2767
+ fix: 'Check Stripe webhook configuration. Verify API keys. Add idempotency keys.',
2768
+ codePattern: '05-payments',
2769
+ },
2770
+ {
2771
+ pattern: /hydration|Minified React|client.*server/gi,
2772
+ type: 'React Hydration Error',
2773
+ severity: 'MEDIUM',
2774
+ fix: 'Ensure server and client render the same content. Use useEffect for client-only code.',
2775
+ codePattern: '04-frontend',
2776
+ },
2777
+ ];
2778
+ errorPatterns.forEach(({ pattern, type, severity, fix, codePattern }) => {
2779
+ const matches = logsText.match(pattern);
2780
+ if (matches) {
2781
+ issues.push({ type, severity, count: matches.length, fix, pattern: codePattern });
2782
+ }
2783
+ });
2784
+ if (issues.length === 0) {
2785
+ return `No common error patterns detected. Review the raw logs for custom application errors.`;
2786
+ }
2787
+ // Sort by severity
2788
+ const severityOrder = { CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3 };
2789
+ issues.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
2790
+ let output = `Found **${issues.length}** error patterns:\n\n`;
2791
+ issues.forEach((issue, i) => {
2792
+ const emoji = issue.severity === 'CRITICAL' ? '🔴' : issue.severity === 'HIGH' ? '🟠' : '🟡';
2793
+ output += `### ${i + 1}. ${emoji} ${issue.type}\n`;
2794
+ output += `**Severity:** ${issue.severity} | **Occurrences:** ${issue.count}\n\n`;
2795
+ output += `**Fix:** ${issue.fix}\n\n`;
2796
+ if (issue.pattern) {
2797
+ output += `**Pattern:** Load \`${issue.pattern}.md\` for detailed implementation guidance.\n\n`;
2798
+ }
2799
+ });
2800
+ output += `\n---\n\n**Next Steps:**\n`;
2801
+ output += `1. Address CRITICAL issues first\n`;
2802
+ output += `2. Use \`get_pattern\` to load relevant CodeBakers patterns\n`;
2803
+ output += `3. Run \`heal\` to auto-fix safe issues\n`;
2804
+ return output;
2805
+ }
2806
+ async handleVercelDeployments(args) {
2807
+ const token = this.getVercelToken();
2808
+ if (!token) {
2809
+ return {
2810
+ content: [{
2811
+ type: 'text',
2812
+ text: `❌ Not connected to Vercel. Use vercel_connect first.`,
2813
+ }],
2814
+ isError: true,
2815
+ };
2816
+ }
2817
+ const limit = args.limit || 10;
2818
+ const stateFilter = args.state;
2819
+ try {
2820
+ // Get deployments
2821
+ const url = new URL('https://api.vercel.com/v6/deployments');
2822
+ url.searchParams.set('limit', limit.toString());
2823
+ if (stateFilter && stateFilter !== 'all') {
2824
+ url.searchParams.set('state', stateFilter);
2825
+ }
2826
+ const response = await fetch(url.toString(), {
2827
+ headers: { Authorization: `Bearer ${token}` },
2828
+ });
2829
+ if (!response.ok) {
2830
+ throw new Error(`Failed to fetch deployments: ${response.statusText}`);
2831
+ }
2832
+ const data = await response.json();
2833
+ const deployments = data.deployments || [];
2834
+ if (deployments.length === 0) {
2835
+ return {
2836
+ content: [{
2837
+ type: 'text',
2838
+ text: `No deployments found${stateFilter ? ` with state: ${stateFilter}` : ''}.`,
2839
+ }],
2840
+ };
2841
+ }
2842
+ const formatted = deployments.map((d) => {
2843
+ const created = new Date(d.created).toLocaleString();
2844
+ const state = d.state || d.readyState || 'UNKNOWN';
2845
+ const emoji = state === 'READY' ? '✅' : state === 'ERROR' ? '❌' : state === 'BUILDING' ? '🔨' : '⏳';
2846
+ const url = d.url ? `https://${d.url}` : 'N/A';
2847
+ const commit = d.meta?.githubCommitMessage || d.meta?.gitlabCommitMessage || 'No commit message';
2848
+ return `${emoji} **${state}** - ${created}\n URL: ${url}\n Commit: ${commit}`;
2849
+ }).join('\n\n');
2850
+ return {
2851
+ content: [{
2852
+ type: 'text',
2853
+ text: `# Recent Deployments\n\n${formatted}`,
2854
+ }],
2855
+ };
2856
+ }
2857
+ catch (error) {
2858
+ return {
2859
+ content: [{
2860
+ type: 'text',
2861
+ text: `❌ Failed to fetch deployments: ${error instanceof Error ? error.message : 'Unknown error'}`,
2862
+ }],
2863
+ isError: true,
2864
+ };
2865
+ }
2866
+ }
2395
2867
  async run() {
2396
2868
  const transport = new stdio_js_1.StdioServerTransport();
2397
2869
  await this.server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codebakers/cli",
3
- "version": "2.6.2",
3
+ "version": "2.8.0",
4
4
  "description": "CodeBakers CLI - Production patterns for AI-assisted development",
5
5
  "main": "dist/index.js",
6
6
  "bin": {