Skip to content

Commit e0d82e7

Browse files
committed
feat(simulator-management): add consolidated erase_sims tool
Implements Simulator "Erase Content and Settings" using native simctl: - UDID: xcrun simctl erase <UDID> - All: xcrun simctl erase all Adds tests and updates workflow metadata to include 'erase'. Closes #110
1 parent 0ce407f commit e0d82e7

File tree

4 files changed

+156
-4
lines changed

4 files changed

+156
-4
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { z } from 'zod';
3+
import eraseSims, { erase_simsLogic } from '../erase_sims.ts';
4+
import { createMockExecutor } from '../../../../test-utils/mock-executors.ts';
5+
6+
describe('erase_sims tool (UDID or ALL only)', () => {
7+
describe('Export Field Validation (Literal)', () => {
8+
it('should have correct name', () => {
9+
expect(eraseSims.name).toBe('erase_sims');
10+
});
11+
12+
it('should have correct description', () => {
13+
expect(eraseSims.description).toContain('Provide exactly one of: simulatorUuid or all=true');
14+
});
15+
16+
it('should have handler function', () => {
17+
expect(typeof eraseSims.handler).toBe('function');
18+
});
19+
20+
it('should validate schema fields (shape only)', () => {
21+
const schema = z.object(eraseSims.schema);
22+
// Valid
23+
expect(schema.safeParse({ simulatorUuid: 'UDID-1' }).success).toBe(true);
24+
expect(schema.safeParse({ all: true }).success).toBe(true);
25+
// Shape-level schema does not enforce selection rules; handler validation covers that.
26+
});
27+
});
28+
29+
describe('Single mode', () => {
30+
it('erases a simulator successfully', async () => {
31+
const mock = createMockExecutor({ success: true, output: 'OK' });
32+
const res = await erase_simsLogic({ simulatorUuid: 'UD1' }, mock);
33+
expect(res).toEqual({
34+
content: [{ type: 'text', text: 'Successfully erased simulator UD1' }],
35+
});
36+
});
37+
38+
it('returns failure when erase fails', async () => {
39+
const mock = createMockExecutor({ success: false, error: 'Booted device' });
40+
const res = await erase_simsLogic({ simulatorUuid: 'UD1' }, mock);
41+
expect(res).toEqual({
42+
content: [{ type: 'text', text: 'Failed to erase simulator: Booted device' }],
43+
});
44+
});
45+
});
46+
47+
describe('All mode', () => {
48+
it('erases all simulators successfully', async () => {
49+
const exec = createMockExecutor({ success: true, output: 'OK' });
50+
const res = await erase_simsLogic({ all: true }, exec);
51+
expect(res).toEqual({
52+
content: [{ type: 'text', text: 'Successfully erased all simulators' }],
53+
});
54+
});
55+
56+
it('returns failure when erase all fails', async () => {
57+
const exec = createMockExecutor({ success: false, error: 'Denied' });
58+
const res = await erase_simsLogic({ all: true }, exec);
59+
expect(res).toEqual({
60+
content: [{ type: 'text', text: 'Failed to erase all simulators: Denied' }],
61+
});
62+
});
63+
});
64+
});

src/mcp/tools/simulator-management/__tests__/index.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ describe('simulator-management workflow metadata', () => {
2121

2222
it('should have correct description', () => {
2323
expect(workflow.description).toBe(
24-
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators and setting simulator environment options like location, network, statusbar and appearance.',
24+
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance.',
2525
);
2626
});
2727

@@ -46,6 +46,7 @@ describe('simulator-management workflow metadata', () => {
4646
'location',
4747
'network',
4848
'statusbar',
49+
'erase',
4950
]);
5051
});
5152
});
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { z } from 'zod';
2+
import { ToolResponse } from '../../../types/common.ts';
3+
import { log } from '../../../utils/logging/index.ts';
4+
import { CommandExecutor, getDefaultCommandExecutor } from '../../../utils/execution/index.ts';
5+
import { createTypedTool } from '../../../utils/typed-tool-factory.ts';
6+
7+
const eraseSimsBaseSchema = z.object({
8+
simulatorUuid: z.string().optional().describe('UUID of the simulator to erase.'),
9+
all: z.boolean().optional().describe('When true, erases all simulators.'),
10+
});
11+
12+
const eraseSimsSchema = eraseSimsBaseSchema.refine(
13+
(v) => {
14+
const selectors = (v.simulatorUuid ? 1 : 0) + (v.all === true ? 1 : 0);
15+
return selectors === 1;
16+
},
17+
{ message: 'Provide exactly one of: simulatorUuid OR all=true.' },
18+
);
19+
20+
type EraseSimsParams = z.infer<typeof eraseSimsSchema>;
21+
22+
async function eraseSingle(udid: string, executor: CommandExecutor): Promise<ToolResponse> {
23+
const result = await executor(
24+
['xcrun', 'simctl', 'erase', udid],
25+
'Erase Simulator',
26+
true,
27+
undefined,
28+
);
29+
if (result.success) {
30+
return { content: [{ type: 'text', text: `Successfully erased simulator ${udid}` }] };
31+
}
32+
return {
33+
content: [
34+
{ type: 'text', text: `Failed to erase simulator: ${result.error ?? 'Unknown error'}` },
35+
],
36+
};
37+
}
38+
39+
export async function erase_simsLogic(
40+
params: EraseSimsParams,
41+
executor: CommandExecutor,
42+
): Promise<ToolResponse> {
43+
try {
44+
if (params.simulatorUuid) {
45+
log('info', `Erasing simulator ${params.simulatorUuid}`);
46+
return await eraseSingle(params.simulatorUuid, executor);
47+
}
48+
49+
if (params.all === true) {
50+
log('info', 'Erasing ALL simulators');
51+
const result = await executor(
52+
['xcrun', 'simctl', 'erase', 'all'],
53+
'Erase All Simulators',
54+
true,
55+
undefined,
56+
);
57+
if (!result.success) {
58+
return {
59+
content: [
60+
{
61+
type: 'text',
62+
text: `Failed to erase all simulators: ${result.error ?? 'Unknown error'}`,
63+
},
64+
],
65+
};
66+
}
67+
return { content: [{ type: 'text', text: 'Successfully erased all simulators' }] };
68+
}
69+
70+
return {
71+
content: [{ type: 'text', text: 'Invalid parameters: provide simulatorUuid or all=true.' }],
72+
};
73+
} catch (error: unknown) {
74+
const message = error instanceof Error ? error.message : String(error);
75+
log('error', `Error erasing simulators: ${message}`);
76+
return { content: [{ type: 'text', text: `Failed to erase simulators: ${message}` }] };
77+
}
78+
}
79+
80+
export default {
81+
name: 'erase_sims',
82+
description:
83+
'Erases simulator content and settings. Provide exactly one of: simulatorUuid or all=true.',
84+
schema: eraseSimsBaseSchema.shape,
85+
handler: createTypedTool(eraseSimsSchema, erase_simsLogic, getDefaultCommandExecutor),
86+
};

src/mcp/tools/simulator-management/index.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22
* Simulator Management workflow
33
*
44
* Provides tools for working with simulators like booting and opening simulators, launching apps,
5-
* listing sims, stopping apps and setting sim environment options like location, network, statusbar and appearance.
5+
* listing sims, stopping apps, erasing simulator content and settings, and setting sim environment
6+
* options like location, network, statusbar and appearance.
67
*/
78

89
export const workflow = {
910
name: 'Simulator Management',
1011
description:
11-
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators and setting simulator environment options like location, network, statusbar and appearance.',
12+
'Tools for managing simulators from booting, opening simulators, listing simulators, stopping simulators, erasing simulator content and settings, and setting simulator environment options like location, network, statusbar and appearance.',
1213
platforms: ['iOS'],
1314
targets: ['simulator'],
1415
projectTypes: ['project', 'workspace'],
15-
capabilities: ['boot', 'open', 'list', 'appearance', 'location', 'network', 'statusbar'],
16+
capabilities: ['boot', 'open', 'list', 'appearance', 'location', 'network', 'statusbar', 'erase'],
1617
};

0 commit comments

Comments
 (0)