preflight-ios-mcp 1.0.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.
@@ -0,0 +1,511 @@
1
+ import { z } from 'zod';
2
+ import { execSimctl, resolveDevice, runAppleScript } from '../helpers/simctl.js';
3
+ import * as idb from '../helpers/idb.js';
4
+ import * as logger from '../helpers/logger.js';
5
+ import { readFile, readdir, stat } from 'node:fs/promises';
6
+ import { join } from 'node:path';
7
+ import { homedir } from 'node:os';
8
+ import { execFile, spawn } from 'node:child_process';
9
+ import { promisify } from 'node:util';
10
+ import { getScreenMapping } from '../helpers/coordinate-mapper.js';
11
+ const execFileAsync = promisify(execFile);
12
+ // Active log stream processes
13
+ const activeStreams = new Map();
14
+ // --- get_logs ---
15
+ export const getLogsParams = {
16
+ process: z.string().optional().describe('Filter by process name (e.g., "MyApp", "SpringBoard")'),
17
+ subsystem: z.string().optional().describe('Filter by log subsystem (e.g., "com.apple.UIKit")'),
18
+ category: z.string().optional().describe('Filter by log category'),
19
+ level: z.enum(['debug', 'info', 'default', 'error', 'fault']).optional().describe('Minimum log level (default: default)'),
20
+ since: z.string().optional().describe('Time range: "5m", "1h", "30s", or ISO date (default: "1m")'),
21
+ messageContains: z.string().optional().describe('Filter messages containing this text'),
22
+ limit: z.number().optional().describe('Max number of log lines to return (default: 100)'),
23
+ deviceId: z.string().optional().describe('Device (default: booted)'),
24
+ };
25
+ export async function handleGetLogs(args) {
26
+ const device = await resolveDevice(args.deviceId);
27
+ const predicateParts = [];
28
+ // Sanitize predicate values — escape quotes to prevent predicate syntax breaking
29
+ const esc = (s) => s.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
30
+ if (args.process)
31
+ predicateParts.push(`processImagePath CONTAINS "${esc(args.process)}"`);
32
+ if (args.subsystem)
33
+ predicateParts.push(`subsystem == "${esc(args.subsystem)}"`);
34
+ if (args.category)
35
+ predicateParts.push(`category == "${esc(args.category)}"`);
36
+ if (args.messageContains)
37
+ predicateParts.push(`eventMessage CONTAINS "${esc(args.messageContains)}"`);
38
+ const cmdArgs = ['simctl', 'spawn', device, 'log', 'show'];
39
+ if (args.level)
40
+ cmdArgs.push('--level', args.level);
41
+ cmdArgs.push('--last', args.since || '1m');
42
+ cmdArgs.push('--style', 'compact');
43
+ if (predicateParts.length > 0) {
44
+ cmdArgs.push('--predicate', predicateParts.join(' AND '));
45
+ }
46
+ logger.debug('tool:getLogs', `Running: xcrun ${cmdArgs.join(' ')}`);
47
+ try {
48
+ const result = await execFileAsync('xcrun', cmdArgs, {
49
+ maxBuffer: 50 * 1024 * 1024,
50
+ encoding: 'utf-8',
51
+ timeout: 30000,
52
+ });
53
+ let lines = result.stdout.split('\n').filter(l => l.trim());
54
+ const total = lines.length;
55
+ const limit = args.limit || 100;
56
+ if (lines.length > limit) {
57
+ lines = lines.slice(-limit); // most recent
58
+ }
59
+ return {
60
+ content: [{
61
+ type: 'text',
62
+ text: `Device logs (showing ${lines.length} of ${total} entries, last ${args.since || '1m'}):\n\n${lines.join('\n')}`,
63
+ }],
64
+ };
65
+ }
66
+ catch (err) {
67
+ const e = err;
68
+ return {
69
+ content: [{
70
+ type: 'text',
71
+ text: `Log query returned: ${e.stdout?.split('\n').filter(l => l.trim()).slice(-50).join('\n') || e.stderr || e.message || 'no output'}`,
72
+ }],
73
+ };
74
+ }
75
+ }
76
+ // --- stream_logs ---
77
+ export const streamLogsParams = {
78
+ action: z.enum(['start', 'read', 'stop']).describe('"start" begins streaming, "read" returns current buffer, "stop" ends the stream'),
79
+ process: z.string().optional().describe('Filter by process name'),
80
+ level: z.enum(['debug', 'info', 'default', 'error', 'fault']).optional().describe('Minimum log level'),
81
+ bufferSize: z.number().optional().describe('Max lines to keep in buffer (default: 200)'),
82
+ deviceId: z.string().optional().describe('Device (default: booted)'),
83
+ };
84
+ export async function handleStreamLogs(args) {
85
+ const device = await resolveDevice(args.deviceId);
86
+ const streamKey = `${device}-${args.process || 'all'}`;
87
+ if (args.action === 'stop') {
88
+ const stream = activeStreams.get(streamKey);
89
+ if (stream) {
90
+ stream.process.kill();
91
+ activeStreams.delete(streamKey);
92
+ return { content: [{ type: 'text', text: 'Log stream stopped.' }] };
93
+ }
94
+ return { content: [{ type: 'text', text: 'No active stream to stop.' }] };
95
+ }
96
+ if (args.action === 'read') {
97
+ const stream = activeStreams.get(streamKey);
98
+ if (!stream) {
99
+ return { content: [{ type: 'text', text: 'No active stream. Use action="start" first.' }] };
100
+ }
101
+ const lines = stream.buffer.join('\n');
102
+ return {
103
+ content: [{
104
+ type: 'text',
105
+ text: `Live log buffer (${stream.buffer.length} lines):\n\n${lines || '(no output yet)'}`,
106
+ }],
107
+ };
108
+ }
109
+ // action === 'start'
110
+ const existing = activeStreams.get(streamKey);
111
+ if (existing) {
112
+ existing.process.kill();
113
+ activeStreams.delete(streamKey);
114
+ }
115
+ const cmdArgs = ['simctl', 'spawn', device, 'log', 'stream', '--style', 'compact'];
116
+ if (args.level)
117
+ cmdArgs.push('--level', args.level);
118
+ if (args.process)
119
+ cmdArgs.push('--predicate', `processImagePath CONTAINS "${args.process}"`);
120
+ const child = spawn('xcrun', cmdArgs, { stdio: ['ignore', 'pipe', 'pipe'] });
121
+ const maxLines = args.bufferSize || 200;
122
+ const buffer = [];
123
+ child.stdout.on('data', (data) => {
124
+ const newLines = data.toString().split('\n').filter(l => l.trim());
125
+ buffer.push(...newLines);
126
+ while (buffer.length > maxLines)
127
+ buffer.shift();
128
+ });
129
+ child.stderr.on('data', (data) => {
130
+ buffer.push(`[stderr] ${data.toString().trim()}`);
131
+ while (buffer.length > maxLines)
132
+ buffer.shift();
133
+ });
134
+ activeStreams.set(streamKey, { process: child, buffer, maxLines });
135
+ return {
136
+ content: [{
137
+ type: 'text',
138
+ text: `Log stream started (buffer=${maxLines} lines). Use action="read" to get buffer, action="stop" to end.`,
139
+ }],
140
+ };
141
+ }
142
+ // --- get_app_container ---
143
+ export const getAppContainerParams = {
144
+ bundleId: z.string().describe('App bundle identifier'),
145
+ containerType: z.enum(['app', 'data', 'groups']).optional().describe('Container type (default: data)'),
146
+ deviceId: z.string().optional().describe('Device (default: booted)'),
147
+ };
148
+ export async function handleGetAppContainer(args) {
149
+ const device = await resolveDevice(args.deviceId);
150
+ const type = args.containerType || 'data';
151
+ const cmdArgs = ['get_app_container', device, args.bundleId];
152
+ if (type !== 'data')
153
+ cmdArgs.push(type);
154
+ const { stdout } = await execSimctl(cmdArgs, 'tool:getAppContainer');
155
+ return { content: [{ type: 'text', text: `${type} container: ${stdout.trim()}` }] };
156
+ }
157
+ // --- list_app_files ---
158
+ export const listAppFilesParams = {
159
+ bundleId: z.string().describe('App bundle identifier'),
160
+ subPath: z.string().optional().describe('Subdirectory to list (e.g., "Documents", "Library/Preferences")'),
161
+ deviceId: z.string().optional().describe('Device (default: booted)'),
162
+ };
163
+ export async function handleListAppFiles(args) {
164
+ const device = await resolveDevice(args.deviceId);
165
+ const { stdout: containerPath } = await execSimctl(['get_app_container', device, args.bundleId, 'data'], 'tool:listAppFiles');
166
+ const basePath = args.subPath
167
+ ? join(containerPath.trim(), args.subPath)
168
+ : containerPath.trim();
169
+ try {
170
+ const result = await execFileAsync('find', [basePath, '-maxdepth', '4', '-ls'], {
171
+ maxBuffer: 10 * 1024 * 1024,
172
+ encoding: 'utf-8',
173
+ timeout: 10000,
174
+ });
175
+ const lines = result.stdout.split('\n').filter(l => l.trim());
176
+ const truncated = lines.length > 200;
177
+ const output = truncated ? lines.slice(0, 200) : lines;
178
+ return {
179
+ content: [{
180
+ type: 'text',
181
+ text: `Files in ${basePath} (${lines.length} entries${truncated ? ', showing first 200' : ''}):\n\n${output.join('\n')}`,
182
+ }],
183
+ };
184
+ }
185
+ catch (err) {
186
+ const e = err;
187
+ return { content: [{ type: 'text', text: `Error listing files: ${e.message}` }] };
188
+ }
189
+ }
190
+ // --- read_app_file ---
191
+ export const readAppFileParams = {
192
+ bundleId: z.string().describe('App bundle identifier'),
193
+ filePath: z.string().describe('Relative path within the data container (e.g., "Documents/data.json", "Library/Preferences/com.app.plist")'),
194
+ deviceId: z.string().optional().describe('Device (default: booted)'),
195
+ };
196
+ export async function handleReadAppFile(args) {
197
+ const device = await resolveDevice(args.deviceId);
198
+ const { stdout: containerPath } = await execSimctl(['get_app_container', device, args.bundleId, 'data'], 'tool:readAppFile');
199
+ const fullPath = join(containerPath.trim(), args.filePath);
200
+ try {
201
+ const stats = await stat(fullPath);
202
+ if (stats.size > 1024 * 1024) {
203
+ return { content: [{ type: 'text', text: `File too large (${Math.round(stats.size / 1024)}KB). Use a more specific path.` }] };
204
+ }
205
+ // Handle plist files specially
206
+ if (fullPath.endsWith('.plist')) {
207
+ try {
208
+ const result = await execFileAsync('plutil', ['-convert', 'json', '-o', '-', fullPath], {
209
+ encoding: 'utf-8',
210
+ timeout: 5000,
211
+ });
212
+ return { content: [{ type: 'text', text: `${args.filePath} (plist → JSON):\n\n${result.stdout}` }] };
213
+ }
214
+ catch {
215
+ // Fall through to binary read
216
+ }
217
+ }
218
+ // Handle sqlite files
219
+ if (fullPath.endsWith('.sqlite') || fullPath.endsWith('.db') || fullPath.endsWith('.sqlite3')) {
220
+ try {
221
+ const result = await execFileAsync('sqlite3', [fullPath, '.tables'], {
222
+ encoding: 'utf-8',
223
+ timeout: 5000,
224
+ });
225
+ const tables = result.stdout.trim();
226
+ let schemaInfo = `SQLite database: ${args.filePath}\nTables: ${tables}\n`;
227
+ // Get schema for each table
228
+ for (const table of tables.split(/\s+/).filter(t => t)) {
229
+ try {
230
+ const schema = await execFileAsync('sqlite3', [fullPath, `.schema ${table}`], {
231
+ encoding: 'utf-8',
232
+ timeout: 5000,
233
+ });
234
+ schemaInfo += `\n${schema.stdout}`;
235
+ }
236
+ catch { /* skip */ }
237
+ }
238
+ return { content: [{ type: 'text', text: schemaInfo }] };
239
+ }
240
+ catch {
241
+ return { content: [{ type: 'text', text: `Binary SQLite file at ${args.filePath} (${Math.round(stats.size / 1024)}KB)` }] };
242
+ }
243
+ }
244
+ // Try reading as text
245
+ const content = await readFile(fullPath, 'utf-8');
246
+ return { content: [{ type: 'text', text: `${args.filePath}:\n\n${content.slice(0, 10000)}${content.length > 10000 ? '\n\n...(truncated)' : ''}` }] };
247
+ }
248
+ catch (err) {
249
+ const e = err;
250
+ if (e.code === 'ENOENT') {
251
+ return { content: [{ type: 'text', text: `File not found: ${args.filePath}. Use simulator_list_app_files to see available files.` }] };
252
+ }
253
+ return { content: [{ type: 'text', text: `Error reading file: ${e.message}` }] };
254
+ }
255
+ }
256
+ // --- get_crash_logs ---
257
+ export const getCrashLogsParams = {
258
+ processName: z.string().optional().describe('Filter by process/app name'),
259
+ since: z.string().optional().describe('Only crashes since this ISO date (e.g., "2026-03-22")'),
260
+ limit: z.number().optional().describe('Max number of crash reports (default: 5)'),
261
+ deviceId: z.string().optional().describe('Device (default: booted)'),
262
+ };
263
+ export async function handleGetCrashLogs(args) {
264
+ await resolveDevice(args.deviceId);
265
+ const diagDir = join(homedir(), 'Library', 'Logs', 'DiagnosticReports');
266
+ const limit = args.limit || 5;
267
+ try {
268
+ const files = await readdir(diagDir);
269
+ let crashFiles = files.filter(f => f.endsWith('.ips') || f.endsWith('.crash') || f.endsWith('.ips.ca'));
270
+ if (args.processName) {
271
+ crashFiles = crashFiles.filter(f => f.toLowerCase().includes(args.processName.toLowerCase()));
272
+ }
273
+ // Sort by name (which includes date) descending
274
+ crashFiles.sort().reverse();
275
+ crashFiles = crashFiles.slice(0, limit);
276
+ if (crashFiles.length === 0) {
277
+ return { content: [{ type: 'text', text: `No crash logs found${args.processName ? ` for "${args.processName}"` : ''} in ${diagDir}` }] };
278
+ }
279
+ const reports = [];
280
+ for (const file of crashFiles) {
281
+ try {
282
+ const content = await readFile(join(diagDir, file), 'utf-8');
283
+ reports.push(`--- ${file} ---\n${content.slice(0, 5000)}${content.length > 5000 ? '\n...(truncated)' : ''}`);
284
+ }
285
+ catch {
286
+ reports.push(`--- ${file} --- (unreadable)`);
287
+ }
288
+ }
289
+ return {
290
+ content: [{
291
+ type: 'text',
292
+ text: `Found ${crashFiles.length} crash report(s):\n\n${reports.join('\n\n')}`,
293
+ }],
294
+ };
295
+ }
296
+ catch (err) {
297
+ const e = err;
298
+ return { content: [{ type: 'text', text: `Error reading crash logs: ${e.message}` }] };
299
+ }
300
+ }
301
+ // --- diagnose ---
302
+ export const diagnoseParams = {
303
+ deviceId: z.string().optional().describe('Device (default: booted)'),
304
+ };
305
+ export async function handleDiagnose(args) {
306
+ const device = await resolveDevice(args.deviceId);
307
+ // Collect various diagnostic info
308
+ const results = [];
309
+ // Device info
310
+ try {
311
+ const { stdout } = await execFileAsync('xcrun', ['simctl', 'list', '-j', 'devices'], {
312
+ encoding: 'utf-8',
313
+ timeout: 10000,
314
+ });
315
+ const data = JSON.parse(stdout);
316
+ for (const [runtime, devs] of Object.entries(data.devices)) {
317
+ for (const dev of devs) {
318
+ if (dev.state === 'Booted') {
319
+ results.push(`Booted device: ${dev.name} (${dev.udid})\nRuntime: ${runtime}\nDevice type: ${dev.deviceTypeIdentifier}`);
320
+ }
321
+ }
322
+ }
323
+ }
324
+ catch { /* skip */ }
325
+ // Disk usage of simulator data
326
+ try {
327
+ const { stdout } = await execFileAsync('du', ['-sh', join(homedir(), 'Library', 'Developer', 'CoreSimulator', 'Devices')], {
328
+ encoding: 'utf-8',
329
+ timeout: 10000,
330
+ });
331
+ results.push(`Simulator disk usage: ${stdout.trim()}`);
332
+ }
333
+ catch { /* skip */ }
334
+ // System version
335
+ try {
336
+ const { stdout } = await execFileAsync('xcrun', ['--version'], { encoding: 'utf-8' });
337
+ results.push(`Xcode toolchain: ${stdout.trim()}`);
338
+ }
339
+ catch { /* skip */ }
340
+ try {
341
+ const { stdout } = await execFileAsync('xcodebuild', ['-version'], { encoding: 'utf-8', timeout: 5000 });
342
+ results.push(`Xcode: ${stdout.trim()}`);
343
+ }
344
+ catch { /* skip */ }
345
+ return {
346
+ content: [{
347
+ type: 'text',
348
+ text: `Simulator Diagnostics:\n\n${results.join('\n\n')}`,
349
+ }],
350
+ };
351
+ }
352
+ // --- accessibility_audit ---
353
+ export const accessibilityAuditParams = {
354
+ deviceId: z.string().optional().describe('Device (default: booted)'),
355
+ };
356
+ export async function handleAccessibilityAudit(args) {
357
+ const device = await resolveDevice(args.deviceId);
358
+ // Prefer idb: returns actual iOS UI elements (UIButton, UILabel, etc.)
359
+ if (await idb.checkIdbAvailable()) {
360
+ try {
361
+ const output = await idb.idbDescribeAll(device);
362
+ const lines = output.split('\n').filter(l => l.trim());
363
+ return {
364
+ content: [{
365
+ type: 'text',
366
+ text: `iOS Accessibility Tree via idb (${lines.length} elements):\n\n${output}`,
367
+ }],
368
+ };
369
+ }
370
+ catch (err) {
371
+ const e = err;
372
+ logger.warn('tool:accessibility', `idb describe-all failed, falling back to AppleScript: ${e.message}`);
373
+ // Fall through to AppleScript fallback
374
+ }
375
+ }
376
+ // Fallback: Deep traversal of Simulator's accessibility tree (4 levels).
377
+ // iOS content is nested inside the Simulator window hierarchy.
378
+ // NOTE: handlers using "my" lose the "tell" context, so we inline
379
+ // all element access within the tell block.
380
+ const elemFmt = (varName, indent) => `
381
+ set _r to "?"
382
+ try
383
+ set _r to role of ${varName}
384
+ end try
385
+ set _d to ""
386
+ try
387
+ set _d to description of ${varName}
388
+ end try
389
+ set _v to ""
390
+ try
391
+ set _v to value of ${varName} as text
392
+ end try
393
+ set _p to {0, 0}
394
+ try
395
+ set _p to position of ${varName}
396
+ end try
397
+ set _s to {0, 0}
398
+ try
399
+ set _s to size of ${varName}
400
+ end try
401
+ set _info to "${indent}" & _r
402
+ if _d is not "" then set _info to _info & " | " & _d
403
+ if _v is not "" and _v is not _d then set _info to _info & " | val=" & _v
404
+ set _info to _info & " @" & (item 1 of _p) & "," & (item 2 of _p) & " " & (item 1 of _s) & "x" & (item 2 of _s)
405
+ set output to output & _info & return`;
406
+ const script = `
407
+ tell application "System Events"
408
+ tell process "Simulator"
409
+ set frontWin to front window
410
+ set winName to name of frontWin
411
+ set output to "=== Simulator Accessibility Tree ===" & return
412
+ set output to output & "Window: " & winName & return & return
413
+
414
+ set L1 to every UI element of frontWin
415
+ repeat with e1 in L1
416
+ ${elemFmt('e1', '')}
417
+ set L2 to {}
418
+ try
419
+ set L2 to every UI element of e1
420
+ end try
421
+ repeat with e2 in L2
422
+ ${elemFmt('e2', ' ')}
423
+ set L3 to {}
424
+ try
425
+ set L3 to every UI element of e2
426
+ end try
427
+ repeat with e3 in L3
428
+ ${elemFmt('e3', ' ')}
429
+ set L4 to {}
430
+ try
431
+ set L4 to every UI element of e3
432
+ end try
433
+ repeat with e4 in L4
434
+ ${elemFmt('e4', ' ')}
435
+ end repeat
436
+ end repeat
437
+ end repeat
438
+ end repeat
439
+
440
+ return output
441
+ end tell
442
+ end tell`;
443
+ try {
444
+ const result = await runAppleScript(script, 'tool:accessibility');
445
+ // AppleScript "return" is \r (CR), not \n — split on all line endings
446
+ const lines = result.split(/\r\n|\r|\n/).filter(l => l.trim());
447
+ // De-duplicate adjacent identical lines (common in deep Simulator trees)
448
+ const deduped = [];
449
+ for (const line of lines) {
450
+ if (deduped[deduped.length - 1] !== line)
451
+ deduped.push(line);
452
+ }
453
+ // Count only element lines (those with AX roles), skip headers
454
+ const elementCount = deduped.filter(l => /^[\s]*(AX\w+|missing value)/.test(l)).length;
455
+ return {
456
+ content: [{
457
+ type: 'text',
458
+ text: `Accessibility Tree (${elementCount} elements):\n\n${deduped.join('\n')}`,
459
+ }],
460
+ };
461
+ }
462
+ catch (err) {
463
+ const e = err;
464
+ return {
465
+ content: [{
466
+ type: 'text',
467
+ text: `Accessibility audit failed: ${e.message}\n\nNote: This requires Accessibility permission in System Settings → Privacy & Security → Accessibility.`,
468
+ }],
469
+ };
470
+ }
471
+ }
472
+ // --- get_screen_info ---
473
+ export const getScreenInfoParams = {
474
+ deviceId: z.string().optional().describe('Device (default: booted)'),
475
+ };
476
+ export async function handleGetScreenInfo(args) {
477
+ const device = await resolveDevice(args.deviceId);
478
+ try {
479
+ const mapping = await getScreenMapping(device);
480
+ return {
481
+ content: [{
482
+ type: 'text',
483
+ text: `Screen Mapping Info:
484
+
485
+ Window position: (${mapping.windowGeometry.windowX}, ${mapping.windowGeometry.windowY})
486
+ Window size: ${mapping.windowGeometry.windowWidth} x ${mapping.windowGeometry.windowHeight}
487
+ Title bar height: ${mapping.titleBarHeight}
488
+ Content area: ${mapping.contentWidth} x ${mapping.contentHeight}
489
+
490
+ Device screen: ${mapping.devicePointWidth} x ${mapping.devicePointHeight} points
491
+ Scale factor: ${mapping.scaleFactor}x
492
+
493
+ Coordinate mapping:
494
+ scaleX: ${mapping.scaleX.toFixed(4)}
495
+ scaleY: ${mapping.scaleY.toFixed(4)}
496
+
497
+ Example: sim(0,0) → mac(${mapping.windowGeometry.windowX}, ${mapping.windowGeometry.windowY + mapping.titleBarHeight})
498
+ Example: sim(${mapping.devicePointWidth},${mapping.devicePointHeight}) → mac(${mapping.windowGeometry.windowX + mapping.contentWidth}, ${mapping.windowGeometry.windowY + mapping.titleBarHeight + mapping.contentHeight})`,
499
+ }],
500
+ };
501
+ }
502
+ catch (err) {
503
+ const e = err;
504
+ return {
505
+ content: [{
506
+ type: 'text',
507
+ text: `Failed to get screen info: ${e.message}\n\nMake sure Simulator is running and visible.`,
508
+ }],
509
+ };
510
+ }
511
+ }
@@ -0,0 +1,74 @@
1
+ import { z } from 'zod';
2
+ export declare const listDevicesParams: {
3
+ filter: z.ZodOptional<z.ZodEnum<["available", "booted", "all"]>>;
4
+ };
5
+ export declare function handleListDevices(args: {
6
+ filter?: string;
7
+ }): Promise<{
8
+ content: {
9
+ type: "text";
10
+ text: string;
11
+ }[];
12
+ }>;
13
+ export declare const bootParams: {
14
+ deviceId: z.ZodString;
15
+ waitForBoot: z.ZodOptional<z.ZodBoolean>;
16
+ };
17
+ export declare function handleBoot(args: {
18
+ deviceId: string;
19
+ waitForBoot?: boolean;
20
+ }): Promise<{
21
+ content: {
22
+ type: "text";
23
+ text: string;
24
+ }[];
25
+ }>;
26
+ export declare const shutdownParams: {
27
+ deviceId: z.ZodOptional<z.ZodString>;
28
+ };
29
+ export declare function handleShutdown(args: {
30
+ deviceId?: string;
31
+ }): Promise<{
32
+ content: {
33
+ type: "text";
34
+ text: string;
35
+ }[];
36
+ }>;
37
+ export declare const eraseParams: {
38
+ deviceId: z.ZodString;
39
+ };
40
+ export declare function handleErase(args: {
41
+ deviceId: string;
42
+ }): Promise<{
43
+ content: {
44
+ type: "text";
45
+ text: string;
46
+ }[];
47
+ }>;
48
+ export declare const openUrlParams: {
49
+ url: z.ZodString;
50
+ deviceId: z.ZodOptional<z.ZodString>;
51
+ };
52
+ export declare function handleOpenUrl(args: {
53
+ url: string;
54
+ deviceId?: string;
55
+ }): Promise<{
56
+ content: {
57
+ type: "text";
58
+ text: string;
59
+ }[];
60
+ }>;
61
+ export declare const openSimulatorParams: {};
62
+ export declare function handleOpenSimulator(): Promise<{
63
+ content: {
64
+ type: "text";
65
+ text: string;
66
+ }[];
67
+ }>;
68
+ export declare const getBootedSimIdParams: {};
69
+ export declare function handleGetBootedSimId(): Promise<{
70
+ content: {
71
+ type: "text";
72
+ text: string;
73
+ }[];
74
+ }>;