UNPKG

xc-mcp

Version:

MCP server that wraps Xcode command-line tools for iOS/macOS development workflows

166 lines 6.04 kB
/** * Screenshot sizing utilities for token optimization * * Claude vision API charges per 512×512 pixel tile. This module provides * tile-aligned sizing presets that minimize token usage while maximizing * visual clarity. * * Based on real-world testing documented in XC_MCP_IMPROVEMENTS.md: * - Default 'half' size saves 50% tokens (170 vs 340) * - 256×512 provides 57% more pixels than proportional scaling * - Power-of-2 dimensions enable faster image processing */ /** * Tile-aligned size presets for screenshot optimization * * Following Claude vision API billing (170 tokens per 512×512 tile): * - 1 tile = 170 tokens * - 2 tiles = 340 tokens (typical iPhone screenshot at full size) */ export const SIZE_PRESETS = { full: { name: 'Full Resolution', dimensions: undefined, // Native resolution tiles: 2, tokens: 340, description: 'Native device resolution - use for detailed UI analysis', }, half: { name: 'Half Size (Tile-Aligned)', dimensions: { width: 256, height: 512 }, tiles: 1, tokens: 170, description: 'Default - 50% token savings, 57% more pixels than proportional scaling', }, quarter: { name: 'Quarter Size', dimensions: { width: 128, height: 256 }, tiles: 1, tokens: 170, description: 'Thumbnail - suitable for layout verification', }, thumb: { name: 'Thumbnail', dimensions: { width: 128, height: 128 }, tiles: 1, tokens: 170, description: 'Minimal preview - suitable for quick visual checks', }, }; /** * Default screenshot size (opt-out approach for maximum token savings) */ export const DEFAULT_SCREENSHOT_SIZE = 'half'; /** * Get size preset configuration * * @param size Size preset name * @returns Size preset configuration */ export function getSizePreset(size) { return SIZE_PRESETS[size]; } /** * Validate screenshot size parameter * * @param size Size value to validate * @returns true if valid, false otherwise */ export function isValidScreenshotSize(size) { return (typeof size === 'string' && (size === 'full' || size === 'half' || size === 'quarter' || size === 'thumb')); } /** * Build sips resize command for screenshot optimization * * Uses macOS 'sips' (scriptable image processing system) to resize images. * Power-of-2 dimensions enable faster processing and better compression. * * IMPORTANT: Uses -Z (capital Z) to preserve aspect ratio while fitting within bounds. * This prevents squashing and maintains accurate coordinate mapping for UI interaction. * * @param inputPath Path to source screenshot * @param outputPath Path to save resized screenshot * @param size Size preset to use * @returns sips command string */ export function buildResizeCommand(inputPath, outputPath, size) { const preset = getSizePreset(size); // No resize needed for full resolution if (!preset.dimensions) { return null; } const { width, height } = preset.dimensions; // Use sips -Z (capital Z) to resize while preserving aspect ratio // Fits within width×height bounds without squashing // This is critical for accurate coordinate mapping to device screen return `sips -Z ${Math.max(width, height)} "${inputPath}" --out "${outputPath}"`; } /** * Calculate coordinate transform for mapping screenshot to device coordinates * * Computes scale factors needed to convert coordinates from resized screenshot * back to original device screen coordinates for accurate tap operations. * * @param originalWidth Original device screen width * @param originalHeight Original device screen height * @param displayWidth Resized screenshot width * @param displayHeight Resized screenshot height * @returns Coordinate transform metadata */ export function calculateCoordinateTransform(originalWidth, originalHeight, displayWidth, displayHeight) { const scaleX = originalWidth / displayWidth; const scaleY = originalHeight / displayHeight; return { originalDimensions: { width: originalWidth, height: originalHeight, }, displayDimensions: { width: displayWidth, height: displayHeight, }, scaleX: Number(scaleX.toFixed(2)), scaleY: Number(scaleY.toFixed(2)), guidance: `To tap coordinates from screenshot, multiply by scale factors: deviceX = screenshotX × ${scaleX.toFixed(2)}, deviceY = screenshotY × ${scaleY.toFixed(2)}`, }; } /** * Get screenshot size metadata for response * * Provides token savings information to help agents make informed decisions. * * @param size Size preset used * @param originalSize Original file size in bytes (optional) * @param optimizedSize Optimized file size in bytes (optional) * @returns Metadata object for inclusion in tool response */ export function getScreenshotSizeMetadata(size, originalSize, optimizedSize) { const preset = getSizePreset(size); const metadata = { size, preset: preset.name, dimensions: preset.dimensions ? `${preset.dimensions.width}×${preset.dimensions.height}` : 'native', tiles: preset.tiles, estimatedTokens: preset.tokens, }; // Calculate token savings compared to full size if (size !== 'full') { const fullTokens = SIZE_PRESETS.full.tokens; const savings = ((1 - preset.tokens / fullTokens) * 100).toFixed(0); metadata.tokenSavings = `${savings}% vs full size`; } // Add file size information if available if (originalSize !== undefined && optimizedSize !== undefined) { const compressionRatio = ((1 - optimizedSize / originalSize) * 100).toFixed(1); metadata.fileSizes = { original: originalSize, optimized: optimizedSize, compressionRatio: `${compressionRatio}%`, }; } return metadata; } //# sourceMappingURL=screenshot-sizing.js.map