npx skills add ...
npx skills add reason-machines/codex-skills --skill codex-plusplus-ios-simulator-tweak
Embed a headless iOS Simulator in Codex++ right panel with mirroring, touch input, and UI element annotations
npx skills add reason-machines/codex-skills --skill codex-plusplus-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.
Verify Xcode path:
First install Codex++, the Codex extension framework.
Clone or download this tweak into the Codex++ tweaks directory:
Or manually place the folder:
The tweak runs a preflight check on first launch and shows fix hints if dependencies are missing.
Via UI:
+ menu in Codex's right panelVia Keyboard:
Cmd+YOnce the panel is open:
Annotation mode allows you to reference specific UI elements in Codex comments:
The tweak automatically includes:
Example annotation payload:
Use cases:
The main tweak file exports a Codex++ tweak object:
The Swift helper uses CoreSimulator to capture IOSurface frames:
The Objective-C helper sends touch and keyboard events:
List available simulators:
Boot a simulator:
Shutdown:
The tweak compiles helper binaries on first use and caches them:
No network requests are made. All compilation happens locally.
Error: "Xcode not found"
Error: "No simulators available"
Error: "Swift compiler not found"
Error: "Framework not found: SimulatorKit"
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:
Error: "Failed to boot device"
Check simulator logs:
Symptom: Black screen in Codex panel
Verify simulator is booted:
Check helper process is running:
Restart the panel (close and reopen with Cmd+Y)
Simulator.app (conflicting input)sim-input helper is running:
Cause: App under test has poor accessibility labeling
Solution: Add accessibility labels to your UI elements:
Cmd+Y)MIT
~/Library/Application Support/codex-plusplus/tweaks/ios-simulator/{
"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 }
}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.mdmodule.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 };
}
};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
}#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];
}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;
}function bootSimulator(udid) {
execSync(`xcrun simctl boot ${udid}`, { encoding: 'utf8' });
}function shutdownSimulator(udid) {
execSync(`xcrun simctl shutdown ${udid}`, { encoding: 'utf8' });
}~/Library/Caches/co.bennett.ios-simulator/
├── sim-capture # Compiled Swift helper
└── sim-input # Compiled Objective-C helper{
"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"
}
]
}# Ensure Xcode is installed
xcode-select -p
# If pointing to Command Line Tools, fix with:
sudo xcode-select -s /Applications/Xcode.app# List available simulators
xcrun simctl list devices
# Download iOS runtimes in Xcode:
# Xcode → Settings → Platforms → [Download iOS runtime]# Verify swiftc is available
which swiftc
# Should be under Xcode.app/Contents/Developer/
# If not, run: sudo xcode-select -s /Applications/Xcode.app# 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.frameworkxcrun simctl shutdown <UDID>xcrun simctl spawn booted log stream --predicate 'subsystem == "com.apple.CoreSimulator"'