| name | codex-plusplus-ios-simulator-tweak |
| description | Embed a headless iOS Simulator in Codex++ right panel with mirroring, touch input, and UI element annotations |
| triggers | ["add iOS simulator to codex","embed simulator in codex panel","mirror ios simulator headless","annotate simulator UI elements","capture ios simulator frame","control simulator from codex","install codex++ ios simulator tweak","send touch events to simulator"] |
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
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:
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',
onActivate(panel) {
panel.setTitle('iOS Simulator');
initializeSimulator(panel);
},
onDeactivate() {
stopHelpers();
cleanupResources();
},
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
let device = SimDevice(udid: deviceUDID)
let surface = device.surface
while isRunning {
let surfaceRef = device.io.surface
let baseAddress = IOSurfaceGetBaseAddress(surfaceRef)
let width = IOSurfaceGetWidth(surfaceRef)
let height = IOSurfaceGetHeight(surfaceRef)
fwrite(baseAddress, 1, width * height * 4, stdout)
fflush(stdout)
usleep(16667)
}
Sending Input Events (sim-input.m)
The Objective-C helper sends touch and keyboard events:
#import <Foundation/Foundation.h>
#import <SimulatorKit/SimulatorKit.h>
- (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];
[io sendTouchEvent:event];
}
- (void)sendKeyPress:(NSString*)key {
SimDevice *device = [self deviceWithUDID:deviceUDID];
[device.io sendKeyboardEvent:key];
}
- (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"
xcode-select -p
sudo xcode-select -s /Applications/Xcode.app
Error: "No simulators available"
xcrun simctl list devices
Helper Compilation Fails
Error: "Swift compiler not found"
which swiftc
Error: "Framework not found: SimulatorKit"
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-input helper 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:
Text("Sign In")
.accessibilityLabel("Sign In Button")
.accessibilityIdentifier("signInButton")
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
const touches = [];
panel.on('touch', (x, y, phase) => {
touches.push({ x, y, phase, timestamp: Date.now() });
});
panel.on('annotate', () => {
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