xc-mcp
Version:
MCP server that wraps Xcode command-line tools for iOS/macOS development workflows
166 lines • 6.04 kB
JavaScript
/**
* 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