Imported from reason-machines/codex-skills (
skills/codex-plusplus-ios-simulator-tweak/SKILL.md). Install upstream withnpx skills add reason-machines/codex-skills --skill codex-plusplus-ios-simulator-tweak. Copyright stays with the author.
Codex++ iOS Simulator Tweak
Skill by ara.so — Codex Skills collection.
A Codex++ tweak that adds an iOS Simulator tab to Codex's right panel. It mirrors a booted iOS simulator without opening Simulator.app, forwards touch and keyboard input, and lets you annotate simulator UI elements directly into Codex comments.
What It Does
- Embeds a live iOS simulator view in Codex's right panel
- Mirrors simulator output through CoreSimulator IOSurface (headless)
- Forwards touch, drag, swipe, keyboard, and hardware button input
- Provides UI element annotations using the accessibility tree
- Device picker to switch between installed simulators
- Auto-boot capability when no simulator is running
- Screenshot and hardware control (Home, Lock, Side Button, Siri)
Installation
Prerequisites
- macOS with full Xcode installed (not just Command Line Tools)
- At least one iOS simulator runtime/device downloaded
- Xcode command-line tools properly configured:
sudo xcode-select -s /Applications/Xcode.app
Verify Xcode path:
xcode-select -p
# Should output: /Applications/Xcode.app/Contents/Developer
Install Codex++
First install Codex++, the Codex extension framework.
Install the Tweak
Clone or download this tweak into the Codex++ tweaks directory:
mkdir -p ~/Library/Application\ Support/codex-plusplus/tweaks/
cd ~/Library/Application\ Support/codex-plusplus/tweaks/
git clone https://github.com/b-nnett/codex-plusplus-ios-simulator.git ios-simulator
Or manually place the folder:
~/Library/Application Support/codex-plusplus/tweaks/ios-simulator/
The tweak runs a preflight check on first launch and shows fix hints if dependencies are missing.
Usage
Opening the Simulator Panel
Via UI:
- Click the
+menu in Codex's right panel - Select iOS Simulator (appears below the divider)
Via Keyboard:
- Press
Cmd+Y
Controls
Once the panel is open:
- Tap/Click: Click anywhere on the simulator screen
- Drag/Swipe: Click and drag
- Keyboard: Type directly (focus is captured)
- Hardware Buttons:
- Home button
- Lock button
- Side button
- Siri button
- Screenshot: Capture current simulator state
- Device Picker: Dropdown to switch simulators
- Auto-boot: Toggle to automatically boot simulator when none is running
Annotations
Annotation mode allows you to reference specific UI elements in Codex comments:
- Click the annotation button in the simulator panel
- Click on a UI element in the simulator
- Write your comment in Codex's native comment UI
The tweak automatically includes:
- Element label and accessibility role
- Simulator device ID
- Element frame coordinates
- Marker point
- Viewport size
Example annotation payload:
{
"element": {
"label": "Sign In",
"role": "Button",
"frame": { "x": 120, "y": 450, "width": 175, "height": 44 }
},
"simulator": "iPhone 15 Pro",
"marker": { "x": 207.5, "y": 472 },
"viewport": { "width": 393, "height": 852 }
}
Use cases:
- "Fix this button layout" → points to specific button
- "Why is this label truncated?" → includes label frame and text
- "Adjust spacing here" → marks exact coordinates
File Structure
ios-simulator/
├── index.js # Main Codex++ tweak entry point
├── helpers/
│ ├── sim-capture.swift # Headless frame capture helper
│ └── sim-input.m # Touch, keyboard, hardware button helper
├── manifest.json # Tweak metadata
└── README.md
Key Code Patterns
Tweak Entry Point (index.js)
The main tweak file exports a Codex++ tweak object:
module.exports = {
name: 'iOS Simulator',
icon: 'phone.fill',
// Called when panel is opened
onActivate(panel) {
panel.setTitle('iOS Simulator');
initializeSimulator(panel);
},
// Called when panel is closed
onDeactivate() {
stopHelpers();
cleanupResources();
},
// Panel UI setup
renderPanel(container) {
const canvas = document.createElement('canvas');
const controls = createControlBar();
container.append(canvas, controls);
return { canvas, controls };
}
};
Capturing Simulator Frames (sim-capture.swift)
The Swift helper uses CoreSimulator to capture IOSurface frames:
import Foundation
import CoreSimulator
import IOSurface
// Connect to simulator framebuffer
let device = SimDevice(udid: deviceUDID)
let surface = device.surface // IOSurface
// Capture loop
while isRunning {
let surfaceRef = device.io.surface
let baseAddress = IOSurfaceGetBaseAddress(surfaceRef)
let width = IOSurfaceGetWidth(surfaceRef)
let height = IOSurfaceGetHeight(surfaceRef)
// Write raw frame to stdout
fwrite(baseAddress, 1, width * height * 4, stdout)
fflush(stdout)
usleep(16667) // ~60fps
}
Sending Input Events (sim-input.m)
The Objective-C helper sends touch and keyboard events:
#import <Foundation/Foundation.h>
#import <SimulatorKit/SimulatorKit.h>
// Send touch event
- (void)sendTouchAtX:(CGFloat)x y:(CGFloat)y phase:(NSString*)phase {
SimDevice *device = [self deviceWithUDID:deviceUDID];
SimDeviceIOClient *io = device.io;
SimDeviceIOTouchEvent *event = [SimDeviceIOTouchEvent new];
event.x = x;
event.y = y;
event.phase = [self phaseFromString:phase]; // began, moved, ended
[io sendTouchEvent:event];
}
// Send keyboard input
- (void)sendKeyPress:(NSString*)key {
SimDevice *device = [self deviceWithUDID:deviceUDID];
[device.io sendKeyboardEvent:key];
}
// Hardware buttons
- (void)pressHomeButton {
SimDevice *device = [self deviceWithUDID:deviceUDID];
[device.io pressButton:SimDeviceIOButtonHome];
}
Device Management
List available simulators:
const { execSync } = require('child_process');
function getAvailableSimulators() {
const output = execSync('xcrun simctl list devices --json', { encoding: 'utf8' });
const data = JSON.parse(output);
const devices = [];
for (const runtime in data.devices) {
for (const device of data.devices[runtime]) {
if (device.isAvailable) {
devices.push({
udid: device.udid,
name: device.name,
state: device.state,
runtime: runtime
});
}
}
}
return devices;
}
Boot a simulator:
function bootSimulator(udid) {
execSync(`xcrun simctl boot ${udid}`, { encoding: 'utf8' });
}
Shutdown:
function shutdownSimulator(udid) {
execSync(`xcrun simctl shutdown ${udid}`, { encoding: 'utf8' });
}
Configuration
The tweak compiles helper binaries on first use and caches them:
~/Library/Caches/co.bennett.ios-simulator/
├── sim-capture # Compiled Swift helper
└── sim-input # Compiled Objective-C helper
No network requests are made. All compilation happens locally.
manifest.json
{
"name": "iOS Simulator",
"version": "1.0.0",
"description": "Headless iOS Simulator for Codex++",
"entry": "index.js",
"permissions": [
"process",
"filesystem"
],
"shortcuts": [
{
"key": "cmd+y",
"action": "toggleSimulator"
}
]
}
Troubleshooting
Preflight Check Failed
Error: "Xcode not found"
# Ensure Xcode is installed
xcode-select -p
# If pointing to Command Line Tools, fix with:
sudo xcode-select -s /Applications/Xcode.app
Error: "No simulators available"
# List available simulators
xcrun simctl list devices
# Download iOS runtimes in Xcode:
# Xcode → Settings → Platforms → [Download iOS runtime]
Helper Compilation Fails
Error: "Swift compiler not found"
# Verify swiftc is available
which swiftc
# Should be under Xcode.app/Contents/Developer/
# If not, run: sudo xcode-select -s /Applications/Xcode.app
Error: "Framework not found: SimulatorKit"
# SimulatorKit is a private framework included with Xcode
# Ensure full Xcode is installed, not just Command Line Tools
# Check framework exists:
ls /Applications/Xcode.app/Contents/Developer/Library/PrivateFrameworks/SimulatorKit.framework
Simulator Won't Boot
Error: "Unable to boot device in current state: Booted"
The simulator is already running. Use the device picker to select it, or shut it down first:
xcrun simctl shutdown <UDID>
Error: "Failed to boot device"
Check simulator logs:
xcrun simctl spawn booted log stream --predicate 'subsystem == "com.apple.CoreSimulator"'
No Frame Output
Symptom: Black screen in Codex panel
-
Verify simulator is booted:
xcrun simctl list devices | grep Booted -
Check helper process is running:
ps aux | grep sim-capture -
Restart the panel (close and reopen with
Cmd+Y)
Touch Input Not Working
- Ensure simulator window is not open in
Simulator.app(conflicting input) - Check
sim-inputhelper is running:ps aux | grep sim-input - Verify device UDID matches between capture and input helpers
Annotations Return Empty Labels
Cause: App under test has poor accessibility labeling
Solution: Add accessibility labels to your UI elements:
// SwiftUI
Text("Sign In")
.accessibilityLabel("Sign In Button")
.accessibilityIdentifier("signInButton")
// UIKit
button.accessibilityLabel = "Sign In Button"
button.accessibilityIdentifier = "signInButton"
Common Workflows
Testing a SwiftUI App
- Open simulator panel (
Cmd+Y) - Select device from picker (e.g., "iPhone 15 Pro")
- Launch your app from Xcode or command line:
xcrun simctl launch <UDID> com.yourcompany.yourapp - Interact with UI in Codex panel
- Annotate elements to document issues
Debugging Layout Issues
- Enable annotation mode
- Click on the element with incorrect layout
- Write comment: "This button is clipped on iPhone SE"
- The annotation includes frame and viewport size for the agent to analyze
Recording Interaction Sequences
// Custom extension to record touch sequences
const touches = [];
panel.on('touch', (x, y, phase) => {
touches.push({ x, y, phase, timestamp: Date.now() });
});
panel.on('annotate', () => {
// Include touch sequence in annotation
return { touches, viewport, element };
});
Privacy & Security
- No Screen Recording permission required (uses CoreSimulator IOSurface directly)
- No network requests (all helpers compiled locally)
- Helper processes stopped when tweak deactivates
- Cached binaries stored in user cache directory only
License
MIT