Skip to main content
Glama
formatting.ts9.91 kB
/** * Formatting Service * Handles response formatting for both Markdown and JSON outputs */ import { PROTEIN_PROFILES, COOK_METHOD_INFO, DONENESS_INFO } from "../constants.js"; import type { ProteinType, CookMethod, DonenessLevel, ProteinProfile, CookTimeEstimate, TemperatureAnalysis, } from "../types.js"; /** * Format cooking guidance as Markdown */ export function formatCookingGuidanceMarkdown( profile: ProteinProfile, weightPounds: number, targetTemp: number, pullTemp: number, doneness: DonenessLevel, cookMethod: CookMethod, estimate: CookTimeEstimate, startTime?: { startTime: Date; restTime: number; bufferMinutes: number } ): string { const methodInfo = COOK_METHOD_INFO[cookMethod]; const donenessInfo = DONENESS_INFO[doneness]; let output = `## 🍖 Cooking Guide: ${profile.displayName}\n\n`; output += `**Weight:** ${weightPounds} lbs\n`; output += `**Method:** ${methodInfo.displayName} (${methodInfo.tempRange})\n`; output += `**Target Doneness:** ${donenessInfo.displayName} - ${donenessInfo.description}\n\n`; output += `### 🎯 Temperature Targets\n\n`; output += `- **Target Internal Temp:** ${targetTemp}°F\n`; output += `- **Pull Temperature:** ${pullTemp}°F (accounts for ${profile.carryoverDegrees}°F carryover)\n`; output += `- **USDA Safe Minimum:** ${profile.usdaSafeTemp}°F\n\n`; output += `### ⏱️ Time Estimate\n\n`; output += `**Estimated Cook Time:** ${estimate.hoursAndMinutes}\n`; output += `**Confidence:** ${estimate.confidence.charAt(0).toUpperCase() + estimate.confidence.slice(1)}\n\n`; if (estimate.assumptions.length > 0) { output += `**Notes:**\n`; for (const assumption of estimate.assumptions) { output += `- ${assumption}\n`; } output += "\n"; } if (estimate.warnings.length > 0) { output += `**⚠️ Warnings:**\n`; for (const warning of estimate.warnings) { output += `- ${warning}\n`; } output += "\n"; } if (startTime) { output += `### 📅 Timeline\n\n`; output += `- **Start Cooking:** ${formatDateTime(startTime.startTime)}\n`; output += `- **Rest Time:** ${startTime.restTime} minutes\n`; output += `- **Buffer:** ${startTime.bufferMinutes} minutes (for variability)\n\n`; } if (profile.requiresRest) { output += `### 😴 Resting\n\n`; output += `Rest for **${profile.restTimeMinutes} minutes** before slicing.\n`; output += `Temperature will rise approximately ${profile.carryoverDegrees}°F during rest.\n\n`; } if (profile.stallRange) { output += `### 🛑 Stall Warning\n\n`; output += `This cut typically stalls between **${profile.stallRange.start}-${profile.stallRange.end}°F**.\n`; output += `The stall can last 2-4 hours. Consider wrapping to push through faster.\n\n`; } output += `### 💡 Tips\n\n`; for (const tip of profile.tips.slice(0, 5)) { output += `- ${tip}\n`; } return output; } /** * Format temperature analysis as Markdown */ export function formatTemperatureAnalysisMarkdown(analysis: TemperatureAnalysis): string { let output = `## 🌡️ Temperature Analysis\n\n`; output += `**Current:** ${analysis.currentTemp}°F → **Target:** ${analysis.targetTemp}°F\n`; output += `**Progress:** ${analysis.percentComplete}% complete\n`; output += `**Remaining:** ${analysis.tempDelta}°F to go\n\n`; const trendEmoji = analysis.trend === "rising" ? "📈" : analysis.trend === "falling" ? "📉" : analysis.trend === "stalled" ? "⏸️" : "➡️"; output += `### Trend: ${trendEmoji} ${analysis.trend.charAt(0).toUpperCase() + analysis.trend.slice(1)}\n\n`; if (analysis.trendRatePerHour !== 0) { output += `**Rate:** ${analysis.trendRatePerHour > 0 ? "+" : ""}${analysis.trendRatePerHour}°F/hour\n`; } if (analysis.estimatedMinutesRemaining !== null) { const hours = Math.floor(analysis.estimatedMinutesRemaining / 60); const minutes = analysis.estimatedMinutesRemaining % 60; const timeString = hours > 0 ? `${hours}h ${minutes}m` : `${minutes}m`; output += `**ETA:** ~${timeString} remaining\n`; } else if (analysis.inStallZone) { output += `**ETA:** Cannot estimate during stall\n`; } if (analysis.inStallZone) { output += `\n⚠️ **Currently in the stall zone!**\n`; } if (analysis.recommendations.length > 0) { output += `\n### Recommendations\n\n`; for (const rec of analysis.recommendations) { output += `${rec}\n`; } } return output; } /** * Format protein list as Markdown */ export function formatProteinListMarkdown( proteins: ProteinProfile[], category?: string ): string { const categoryDisplay = category === "all" ? "All Proteins" : `${category?.charAt(0).toUpperCase()}${category?.slice(1)}`; let output = `## 🥩 ${categoryDisplay}\n\n`; // Group by category const grouped = proteins.reduce( (acc, protein) => { if (!acc[protein.category]) { acc[protein.category] = []; } acc[protein.category].push(protein); return acc; }, {} as Record<string, ProteinProfile[]> ); for (const [cat, prots] of Object.entries(grouped)) { output += `### ${cat.charAt(0).toUpperCase() + cat.slice(1)}\n\n`; for (const protein of prots) { const doneness = Object.keys(protein.donenessTemps)[0] as DonenessLevel; const temp = protein.donenessTemps[doneness]; output += `**${protein.displayName}** (\`${protein.type}\`)\n`; output += `- Target: ${temp}°F (${DONENESS_INFO[doneness]?.displayName || doneness})\n`; output += `- Methods: ${protein.recommendedMethods.map((m) => COOK_METHOD_INFO[m].displayName).join(", ")}\n`; if (protein.stallRange) { output += `- ⚠️ Stalls at ${protein.stallRange.start}-${protein.stallRange.end}°F\n`; } output += "\n"; } } return output; } /** * Format stall detection result as Markdown */ export function formatStallDetectionMarkdown( result: { isStalled: boolean; stallDurationMinutes: number; inStallZone: boolean; recommendation: string; }, currentTemp: number ): string { let output = `## 🛑 Stall Analysis\n\n`; output += `**Current Temperature:** ${currentTemp}°F\n`; output += `**In Stall Zone:** ${result.inStallZone ? "Yes" : "No"}\n`; output += `**Stalled:** ${result.isStalled ? "Yes" : "No"}\n`; if (result.isStalled) { output += `**Stall Duration:** ${result.stallDurationMinutes} minutes\n`; } output += `\n### Recommendation\n\n${result.recommendation}\n`; if (result.isStalled) { output += `\n### Options to Push Through\n\n`; output += `1. **Wrap (Texas Crutch):** Wrap in butcher paper or foil to trap moisture and speed cooking\n`; output += `2. **Increase Heat:** Bump smoker to 275-300°F temporarily\n`; output += `3. **Ride It Out:** Be patient - stall will eventually break\n`; } return output; } /** * Format rest time calculation as Markdown */ export function formatRestTimeMarkdown(result: { recommendedRestMinutes: number; expectedCarryover: number; expectedFinalTemp: number; instructions: string[]; }): string { let output = `## 😴 Rest Time Guide\n\n`; output += `**Recommended Rest:** ${result.recommendedRestMinutes} minutes\n`; output += `**Expected Carryover:** +${result.expectedCarryover}°F\n`; output += `**Expected Final Temp:** ${result.expectedFinalTemp}°F\n\n`; output += `### Instructions\n\n`; for (const instruction of result.instructions) { output += `- ${instruction}\n`; } return output; } /** * Format device reading simulation as Markdown */ export function formatDeviceReadingMarkdown( deviceType: string, probeReadings: Array<{ probe_id: string; name?: string; temperature: number }>, analysis?: TemperatureAnalysis ): string { let output = `## 📱 ThermoWorks ${deviceType} Reading\n\n`; for (const probe of probeReadings) { const name = probe.name || probe.probe_id; output += `**${name}:** ${probe.temperature}°F\n`; } if (analysis) { output += `\n---\n\n`; output += formatTemperatureAnalysisMarkdown(analysis); } return output; } /** * Format cooking tips as Markdown */ export function formatTipsMarkdown(tips: string[], proteinType: ProteinType): string { const profile = PROTEIN_PROFILES[proteinType]; let output = `## 💡 Cooking Tips: ${profile.displayName}\n\n`; for (const tip of tips) { output += `- ${tip}\n`; } return output; } /** * Format target temperature info as Markdown */ export function formatTargetTempMarkdown( profile: ProteinProfile, targetTemp: number, pullTemp: number, doneness: DonenessLevel ): string { const donenessInfo = DONENESS_INFO[doneness]; let output = `## 🎯 Target Temperature: ${profile.displayName}\n\n`; output += `**Doneness:** ${donenessInfo.displayName}\n`; output += `**Description:** ${donenessInfo.description}\n\n`; output += `### Temperatures\n\n`; output += `- **Target Internal:** ${targetTemp}°F\n`; output += `- **Pull At:** ${pullTemp}°F (${profile.carryoverDegrees}°F carryover)\n`; output += `- **USDA Safe Minimum:** ${profile.usdaSafeTemp}°F\n\n`; // Show all available doneness levels output += `### All Doneness Options\n\n`; for (const [level, temp] of Object.entries(profile.donenessTemps)) { const info = DONENESS_INFO[level as DonenessLevel]; const marker = level === doneness ? " ← Selected" : ""; output += `- **${info?.displayName || level}:** ${temp}°F${marker}\n`; } return output; } /** * Helper to format date/time */ function formatDateTime(date: Date): string { return date.toLocaleString("en-US", { weekday: "short", month: "short", day: "numeric", hour: "numeric", minute: "2-digit", hour12: true, }); }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jweingardt12/bbq-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server