import { z } from 'zod';
import { existsSync, readFileSync, unlinkSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, extname } from 'node:path';
import { randomUUID } from 'node:crypto';
import { simctl } from '../executor/simctl.js';
import { resolveDevice } from '../utils/device-resolver.js';
export const screenshotSchema = z.object({
device: z
.string()
.describe('Device UDID, name, or "booted"'),
output_path: z
.string()
.optional()
.describe('Path to save the screenshot. If omitted, returns base64 image data'),
type: z
.enum(['png', 'jpeg', 'tiff', 'bmp', 'gif', 'pict'])
.optional()
.default('png')
.describe('Image format (default: png)'),
mask: z
.enum(['ignored', 'alpha', 'black'])
.optional()
.describe('Mask to apply for non-rectangular displays'),
});
export type ScreenshotInput = z.infer<typeof screenshotSchema>;
export const screenshotTool = {
name: 'simctl_io_screenshot',
description: 'Capture a screenshot from a simulator',
inputSchema: screenshotSchema,
handler: async (input: ScreenshotInput) => {
const udid = resolveDevice(input.device);
const imageType = input.type ?? 'png';
let outputPath: string;
let tempFile = false;
if (input.output_path) {
outputPath = input.output_path;
} else {
outputPath = join(tmpdir(), `screenshot-${randomUUID()}.${imageType}`);
tempFile = true;
}
const args: string[] = [udid, 'screenshot'];
if (imageType) {
args.push(`--type=${imageType}`);
}
if (input.mask) {
args.push(`--mask=${input.mask}`);
}
args.push(outputPath);
const result = simctl('io', args);
if (result.exitCode !== 0) {
throw new Error(`Failed to capture screenshot: ${result.stderr || result.stdout}`);
}
if (!existsSync(outputPath)) {
throw new Error(`Screenshot file was not created: ${outputPath}`);
}
if (tempFile) {
// Return as base64 image content
try {
const imageData = readFileSync(outputPath);
const base64Data = imageData.toString('base64');
const mimeTypes: Record<string, string> = {
png: 'image/png',
jpeg: 'image/jpeg',
tiff: 'image/tiff',
bmp: 'image/bmp',
gif: 'image/gif',
pict: 'image/x-pict',
};
return {
content: [
{
type: 'image' as const,
data: base64Data,
mimeType: mimeTypes[imageType] || 'image/png',
},
],
};
} finally {
try {
unlinkSync(outputPath);
} catch {
// Ignore cleanup errors
}
}
}
return {
content: [
{
type: 'text' as const,
text: `Screenshot saved to ${outputPath}`,
},
],
};
},
};