Ios simulator skill
29 production-ready scripts for iOS app testing, building, and automation.
by conorluddy·MIT license·★ 1,267 Stars on the repo·GitHub ↗
npx degit conorluddy/ios-simulator-skill/ios-simulator-skill/skills/ios-simulator-skill#main ~/.claude/skills/ios-simulator-skillChecked ·commit main
Files of Ios simulator skill
Show the full text414 lines
iOS Simulator Skill
Build, test, and automate iOS applications using accessibility-driven navigation and structured data instead of pixel coordinates.
Quick Start
# 1. Check environment
bash scripts/sim_health_check.sh
# 2. Launch app
python scripts/app_launcher.py --launch com.example.app
# 3. Map screen to see elements
python scripts/screen_mapper.py
# 4. Tap button
python scripts/navigator.py --find-text "Login" --tap
# 5. Enter text
python scripts/navigator.py --find-type TextField --enter-text "[email protected]"
All scripts support --help for detailed options and --json for machine-readable output.
Navigation Strategy
Always prefer the accessibility tree over screenshots for navigation. The accessibility tree gives you element types, labels, frames, and tap targets — structured data that's cheaper and more reliable than image analysis.
Use this priority:
screen_mapper.py→ structured element list (5-7 lines, ~10 tokens)navigator.py --find-text/--find-type/--find-id→ semantic interaction- Screenshots → only for visual verification, bug reports, or visual diff
Screenshots cost 1,600–6,300 tokens depending on size. The accessibility tree costs 10–50 tokens in default mode.
29 Production Scripts
Build & Development (2 scripts)
build_and_test.py - Build Xcode projects, run tests, parse results with progressive disclosure
- Build with live result streaming
- Parse errors and warnings from xcresult bundles
- Retrieve detailed build logs on demand
- Options:
--project,--scheme,--clean,--test,--verbose,--json
log_monitor.py - Real-time log monitoring with intelligent filtering
- Stream logs or capture by duration
- Filter by severity (error/warning/info/debug)
- Deduplicate repeated messages
- Options:
--app,--severity,--follow,--duration,--output,--json
Device State (2 scripts)
appearance.py - Control simulator appearance: dark mode, Dynamic Type size, and locale/region
- Toggle light/dark theme via
xcrun simctl ui - Set Dynamic Type size with friendly aliases (XS through AX5)
- Write locale and region defaults; optional app restart via
--bundle-id - RTL flagged automatically for ar/he/fa/ur/yi locales
- Options:
--theme,--text-size,--locale,--region,--reset,--bundle-id,--udid,--json,--verbose
- Toggle light/dark theme via
location.py - Simulate GPS coordinates, named city presets, and GPX scenario playback
- Fix a coordinate with
--lat/--lngor pick a city with--city - Play a built-in scenario (City Run, Freeway Drive, etc.) via
--gpx <scenario> - Animate multi-waypoint paths with configurable speed via
--waypointsand--speed - Clear simulated location with
--clear; list available scenarios with--list-scenarios - Options:
--lat,--lng,--city,--gpx,--waypoints,--speed,--clear,--list-scenarios,--udid,--json,--verbose
- Fix a coordinate with
Navigation & Interaction (5 scripts)
screen_mapper.py - Analyze current screen and list interactive elements
- Element type breakdown
- Interactive button list
- Text field status
- Options:
--verbose,--hints,--json
navigator.py - Find and interact with elements semantically
- Find by text (fuzzy matching)
- Find by element type
- Find by accessibility ID
- Enter text or tap elements
- Options:
--find-text,--find-type,--find-id,--tap,--enter-text,--json
gesture.py - Perform swipes, scrolls, pinches, and complex gestures
- Directional swipes (up/down/left/right)
- Multi-swipe scrolling
- Pinch zoom
- Long press
- Pull to refresh
- Options:
--swipe,--scroll,--pinch,--long-press,--refresh,--json
keyboard.py - Text input and hardware button control
- Type text (fast or slow)
- Special keys (return, delete, tab, space, arrows)
- Hardware buttons (home, lock, volume, screenshot)
- Key combinations
- Options:
--type,--key,--button,--slow,--clear,--dismiss,--json
app_launcher.py - App lifecycle management
- Launch apps by bundle ID
- Terminate apps
- Install/uninstall from .app bundles
- Deep link navigation
- List installed apps
- Check app state
- Pass launch arguments (
--args) and environment variables (--env KEY=VALUE, injected asSIMCTL_CHILD_*) to the app on launch/restart - Options:
--launch,--terminate,--restart,--install,--uninstall,--open-url,--list,--state,--args,--env,--wait-for-debugger
Testing & Analysis (9 scripts)
accessibility_audit.py - Check WCAG compliance on current screen
- Critical issues (missing labels, empty buttons, no alt text)
- Warnings (missing hints, small touch targets)
- Info (missing IDs, deep nesting)
- Options:
--verbose,--output,--json
visual_diff.py - Compare two screenshots for visual changes
- Pixel-by-pixel comparison
- Threshold-based pass/fail
- Generate diff images
- Options:
--threshold,--output,--details,--json
test_recorder.py - Automatically document test execution
- Capture screenshots and accessibility trees per step
- Generate markdown reports with timing data
- Options:
--test-name,--output,--verbose,--json
app_state_capture.py - Create comprehensive debugging snapshots
- Screenshot, UI hierarchy, app logs, device info
- Markdown summary for bug reports
- Options:
--app-bundle-id,--output,--log-lines,--json
sim_health_check.sh - Verify environment is properly configured
- Check macOS, Xcode, simctl, IDB, Python
- List available and booted simulators
- Verify Python packages (Pillow)
model_inspector.py - Inspect Core Data and SwiftData models from project files
- Parse .xcdatamodeld packages (entities, attributes, relationships)
- Detect model versions and current active version
- Best-effort SwiftData @Model class extraction
- Raw source dump for any model on demand (
--raw ModelName) - Options:
--project-path,--core-data-only,--swiftdata-only,--show-versions,--raw,--verbose,--json
container.py - Inspect app sandbox: files, UserDefaults, and Core Data store paths
- List data container files at configurable depth via
--ls - Read files with auto-detected plist decoding via
--cat(large files cached) - Dump UserDefaults as key=value or JSON via
--userdefaults - Locate
.sqlite/.sqlite-wal/.sqlite-shmstores via--core-data-path - Export full container snapshot via
--export - Options:
--ls,--cat,--userdefaults,--core-data-path,--export,--udid,--json,--verbose
- List data container files at configurable depth via
hang_watcher.py (HangBuster) - Record + summarise os_log hang events with progressive disclosure
- Session mode (HangBuster, agent-native): start a detached recorder, interact with the simulator, stop for a token-tight summary
--start→ returns a session ID; detached worker normalises + thresholds events on the fly--stop SESSION_ID→ emits ~80–120 token L1 summary (header + top-N clusters + drill hint)--get-details SESSION_ID [--cluster N | --raw]→ L2 full clusters or L3 per-event detail--list-sessions/--clear-sessions [--older-than 24h]/--diff A B(cross-session regression report)- Filter pipeline: parse → normalise → threshold → bucket → cluster → aggregate → rank → format (in
common/hang_pipeline.py) --budget-tokens Npicks the densest level (L0/L1/L2) that fits;--terseforces L0--auto-samplecaptures a main-thread stack on first event per cluster (soft dependency:main_thread_sampler.py#62; graceful no-op if absent)
- Raw capture mode (full fidelity for
jqexploration): skip the clustering pipeline, dump every matching log line verbatim toraw.ndjson--start --raw-capture [--max-size-mb 10] [--no-gzip]— spawnslog stream --style ndjson- Per-session size cap (
--max-size-mb, default 10) — worker stops cleanly on cap;extras.truncated=true --stopgzipsraw.ndjson→raw.ndjson.gz(~15–19× compression;--no-gzipopts out)--get-details SESSION_IDon a raw session prints the path with azcat | jq ...hint
- Resilience (auto-restart on stream death): EOF or subprocess death triggers a
stream_diedevent then a bounded restart with 2s backoff. AfterIOS_SIM_HANG_MAX_RESTARTS(default 3) the session is markedcrashed, never left in stalerunningstate.--list-sessionsshowscapture=Xsandrestarts=N. - Cleanup is automatic: TTL prune (
IOS_SIM_HANG_SESSION_TTL_HOURS, default 24h) + aggregate cap (IOS_SIM_HANG_TOTAL_CAP_MB, default 100 MB, oldest-first eviction) both run on every--start. - Legacy modes (unchanged for backward compat):
--watch [--duration N](live stream) and--since 5m(historical) - Filters:
--bundle-id(post-parse — hang capture stays simulator-global so RunningBoard/SpringBoard events are kept),--predicate(also viaIOS_SIM_HANG_PREDICATE) - All output supports
--json; session storage at~/.ios-simulator-skill/sessions/<id>/{meta.json,events.jsonl,summary.json,raw.ndjson.gz}
Quick start (summarised mode):
SID=$(python scripts/hang_watcher.py --start --min-hang-ms 200) # ... interact with the simulator (open sheets, scroll, navigate) ... python scripts/hang_watcher.py --stop $SID # token-tight L1 summary python scripts/hang_watcher.py --get-details $SID --cluster 1 # drill into cluster 1 python scripts/hang_watcher.py --diff $SID_BASELINE $SID # cross-session regressionQuick start (raw capture +
jqexploration):SID=$(python scripts/hang_watcher.py --start --raw-capture --max-size-mb 5) # ... interact with the simulator ... python scripts/hang_watcher.py --stop $SID # → "Session ...: raw mode, 737 lines, 0.96 MB → 0.05 MB gzipped" # Top processes by event count: zcat ~/.ios-simulator-skill/sessions/$SID/raw.ndjson.gz \ | jq -s 'group_by(.processImagePath) | map({proc: (.[0].processImagePath | split("/") | last), n: length}) | sort_by(-.n) | .[:5]' # All RunningBoard assertion invalidations: zcat .../raw.ndjson.gz | jq -c 'select(.subsystem == "com.apple.runningboard" and (.eventMessage | startswith("Invalidating")))' # Hangs per minute: zcat .../raw.ndjson.gz | jq -r '.timestamp[:16]' | sort | uniq -c- Session mode (HangBuster, agent-native): start a detached recorder, interact with the simulator, stop for a token-tight summary
localization_audit.py - Detect string catalog gaps, missing keys, and placeholder mismatches
- Report missing and
needs_review/newkeys per locale in.xcstringscatalogs - Cross-reference catalog keys against Swift source (
String(localized:)/NSLocalizedString) via--source - Flag placeholder count mismatches (
%d,%@,%s,%lld) across locales - Legacy
.stringsand.stringsdictsupport viaplistlib - CI-friendly
--strictexits 2 on any finding - Options:
--catalog,--source,--locale,--strict,--json,--verbose
- Report missing and
Advanced Testing & Permissions (4 scripts)
clipboard.py - Manage simulator clipboard for paste testing
- Copy text to clipboard
- Test paste flows without manual entry
- Options:
--copy,--test-name,--expected,--json
status_bar.py - Override simulator status bar appearance
- Presets: clean (9:41, 100% battery), testing (11:11, 50%), low-battery (20%), airplane (offline)
- Custom time, network, battery, WiFi settings
- Options:
--preset,--time,--data-network,--battery-level,--clear,--json
push_notification.py - Send simulated push notifications
- Simple mode (title + body + badge)
- Custom JSON payloads
- Test notification handling and deep links
- Options:
--bundle-id,--title,--body,--badge,--payload,--json
privacy_manager.py - Grant, revoke, and reset app permissions
- 13 supported services (camera, microphone, location, contacts, photos, calendar, health, etc.)
- Batch operations (comma-separated services)
- Audit trail with test scenario tracking
- Options:
--bundle-id,--grant,--revoke,--reset,--list,--json
Simulator Discovery (2 scripts)
sim_list.py - List simulators with progressive disclosure
- Concise summary by default (total / available / booted)
- Full details on demand via cache IDs
- Filter by device type
- Suggest recommended simulators with
--suggest - 96% token reduction vs raw
simctl list(57k → 2k tokens) - Options:
--get-details,--suggest,--device-type,--json
simulator_selector.py - Suggest the best simulator for the job
- Ranks candidates by recent use (from
config.json), latest iOS, common test models, and boot status - List all available simulators with
--list - Boot a selected simulator directly with
--boot - JSON output for programmatic use
- Options:
--suggest,--list,--boot,--json
- Ranks candidates by recent use (from
Device Lifecycle Management (5 scripts)
simctl_boot.py - Boot simulators with optional readiness verification
- Boot by UDID or device name
- Wait for device ready with timeout
- Batch boot operations (--all, --type)
- Performance timing
- Options:
--udid,--name,--wait-ready,--timeout,--all,--type,--json
simctl_shutdown.py - Gracefully shutdown simulators
- Shutdown by UDID or device name
- Optional verification of shutdown completion
- Batch shutdown operations
- Options:
--udid,--name,--verify,--timeout,--all,--type,--json
simctl_create.py - Create simulators dynamically
- Create by device type and iOS version
- List available device types and runtimes
- Custom device naming
- Returns UDID for CI/CD integration
- Options:
--device,--runtime,--name,--list-devices,--list-runtimes,--json
simctl_delete.py - Permanently delete simulators
- Delete by UDID or device name
- Safety confirmation by default (skip with --yes)
- Batch delete operations
- Smart deletion (--old N to keep N per device type)
- Options:
--udid,--name,--yes,--all,--type,--old,--json
simctl_erase.py - Factory reset simulators without deletion
- Preserve device UUID (faster than delete+create)
- Erase all, by type, or booted simulators
- Optional verification
- Options:
--udid,--name,--verify,--timeout,--all,--type,--booted,--json
Common Patterns
Auto-UDID Detection: Most scripts auto-detect the booted simulator if --udid is not provided.
Device Name Resolution: Use device names (e.g., "iPhone 16 Pro") instead of UDIDs - scripts resolve automatically.
Batch Operations: Many scripts support --all for all simulators or --type iPhone for device type filtering.
Output Formats: Default is concise human-readable output. Use --json for machine-readable output in CI/CD.
Help: All scripts support --help for detailed options and examples.
Screenshot Sizing: Screenshots are resized to save tokens. Presets: full (3-4 tiles, ~5K tokens), half (1 tile, ~1.6K tokens, default), quarter (1 tile, ~800 tokens, less detail). Use quarter for quick visual checks, half for readable UI, full only when pixel-level detail matters. Scripts that capture screenshots (app_state_capture.py, test_recorder.py) default to half.
Typical Workflow
- Verify environment:
bash scripts/sim_health_check.sh - Launch app:
python scripts/app_launcher.py --launch com.example.app - Analyze screen:
python scripts/screen_mapper.py - Interact:
python scripts/navigator.py --find-text "Button" --tap - Verify:
python scripts/accessibility_audit.py - Debug if needed:
python scripts/app_state_capture.py --app-bundle-id com.example.app
Configuration
Most operational limits can be tuned via environment variables. Defaults work for typical local development; raise them for slow CI runners, large monorepo builds, or accessibility audits on complex screens.
| Variable | Default | Controls |
|---|---|---|
IOS_SIM_A11Y_LABEL_MAX |
80 |
Max chars of AXLabel retained in accessibility audit output |
IOS_SIM_A11Y_TOP_ISSUES |
10 |
Top accessibility issues surfaced per audit |
IOS_SIM_APPS_PREVIEW |
30 |
App entries listed by app_launcher.py before truncation |
IOS_SIM_BOOT_SUBPROCESS_TIMEOUT |
60 |
Timeout for the simctl boot subprocess itself (seconds) |
IOS_SIM_BOOT_TIMEOUT |
300 |
Wait-for-ready timeout after boot (seconds) |
IOS_SIM_BUILD_JSON_CAP |
50 |
Max build errors / failed tests in JSON output |
IOS_SIM_BUILD_LOG_PREVIEW |
4000 |
Chars of build log preview in default output |
IOS_SIM_BUILD_TIMEOUT |
1800 |
Max seconds for an xcodebuild build invocation before kill |
IOS_SIM_INTROSPECT_TIMEOUT |
60 |
Timeout for xcodebuild -list and simctl list lookups (seconds) |
IOS_SIM_TEST_TIMEOUT |
2700 |
Max seconds for an xcodebuild test invocation before kill |
IOS_SIM_BUILD_SUMMARY_CAP |
15 |
Errors/failures in default build summary |
IOS_SIM_BUILD_VERBOSE_CAP |
100 |
Errors/warnings in verbose build output |
IOS_SIM_CACHE_MAX_ENTRIES |
500 |
Max entries in progressive disclosure cache (LRU eviction) |
IOS_SIM_CACHE_TTL_HOURS |
1 |
Cache entry expiration |
IOS_SIM_ERASE_TIMEOUT |
90 |
Wait-for-erase timeout (seconds) |
IOS_SIM_HANG_PREDICATE |
(default) | Override the os_log predicate used by hang_watcher.py (default catches RunningBoard kills + "Hang detected" + main-thread hangs). Hang events originate from system daemons (RunningBoard, SpringBoard) so the predicate stays simulator-global — --bundle-id is applied post-parse, not ANDed in. |
IOS_SIM_HANG_MIN_MS |
250 |
HangBuster threshold — events below this duration never reach disk (smaller = more sensitive, larger summaries) |
IOS_SIM_HANG_SESSION_TTL_HOURS |
24 |
HangBuster session prune age; pruning runs on every --start |
IOS_SIM_HANG_DEFAULT_TOP_N |
3 |
Default top-N clusters in --stop L1 output |
IOS_SIM_HANG_BUDGET_TOKENS |
(unset) | Default token budget for --stop (picks L0/L1/L2 to fit) |
IOS_SIM_HANG_MAX_RESTARTS |
3 |
HangBuster worker: max log stream respawn attempts on EOF/subprocess death before the session is marked crashed |
IOS_SIM_HANG_TOTAL_CAP_MB |
100 |
HangBuster aggregate disk cap. When total session-state exceeds this on --start, oldest sessions are dropped first. Set to 0 to disable. |
IOS_SIM_LOG_JSON_CAP |
100 |
Max errors/warnings in log_monitor.py JSON output |
IOS_SIM_LOG_LINE_MAX |
300 |
Per-line truncation in log summaries |
IOS_SIM_LOG_TAIL |
200 |
Lines of log tail in verbose / sample output |
IOS_SIM_LOG_TEXT_SUMMARY |
15 |
Errors/warnings shown in text-mode log summary |
IOS_SIM_MAX_ELEMENTS |
25 |
Tappable elements listed by navigator.py |
IOS_SIM_POLL_INTERVAL |
0.5 |
Boot/erase state polling interval (seconds) |
IOS_SIM_RELAUNCH_DELAY_MS |
1000 |
Delay between terminate and re-launch in app_launcher.py |
IOS_SIM_SCREEN_BUTTONS_PREVIEW |
15 |
Button names listed by screen_mapper.py |
IOS_SIM_SCREEN_SECTION_ITEMS |
10 |
Items per section shown by screen_mapper.py |
IOS_SIM_STATE_SUBPROCESS_TIMEOUT |
15 |
Subprocess timeout in app_state_capture.py (seconds) |
IOS_SIM_TAP_SETTLE_MS |
500 |
Post-tap settle delay in navigator.py |
Example:
# Slow GitHub Actions runner: give boot 10 minutes
IOS_SIM_BOOT_TIMEOUT=600 python scripts/simctl_boot.py --wait-ready
Requirements
- macOS 15 (Sequoia)+
- Xcode 26+ and Command Line Tools
- Python 3.12+
idb1.5.1+ - required for every interactive script (tap, swipe, type):brew tap facebook/fb && brew install facebook/fb/idb-companion facebook/fb/idb-cli- Pillow, for visual diffs only:
pip3 install pillow
Verify with bash scripts/sim_health_check.sh (add --json for structured output).
Troubleshooting
Taps, swipes and typing do nothing, but reads work. idb reports success and the screen
never changes. On Xcode 27 this means idb-companion is older than 1.5.1: it looks for
SimulatorKit.framework at the path Xcode 26 used. Upgrade:
brew upgrade facebook/fb/idb-companion.
SimulatorKit is required for HID interactions. Same cause, same fix.
Connection refused or No such file from every idb call. A dead companion is still in
idb's registry at /tmp/idb/state, so idb dials a socket nobody is listening on instead of
starting a fresh companion. Fix: idb disconnect <udid>.
open -a Simulator fails. Xcode 27 has no Simulator.app; it was replaced by
DeviceHub.app in Xcode.app/Contents/Applications/. Boot headlessly instead:
xcrun simctl boot <udid>. Note that quitting DeviceHub shuts down the simulator it hosts.
idb: command not found. The companion and the CLI are separate packages; install both
(see Requirements). If which -a idb shows more than one, the first on PATH wins.
Documentation
- SKILL.md (this file) - Script reference, requirements, troubleshooting
- README.md - Installation, updating idb, Xcode 27 notes
- CLAUDE.md - Architecture and implementation details
Key Design Principles
Semantic Navigation: Find elements by meaning (text, type, ID) not pixel coordinates. Survives UI changes.
Token Efficiency: Concise default output (3-5 lines) with optional verbose and JSON modes for detailed results.
Accessibility-First: Built on standard accessibility APIs for reliability and compatibility.
Zero Configuration: Works immediately on any macOS with Xcode. No setup required.
Structured Data: Scripts output JSON or formatted text, not raw logs. Easy to parse and integrate.
Auto-Learning: Build system remembers your device preference. Configuration stored per-project.
Use these scripts directly or let Claude Code invoke them automatically when your request matches the skill description.
| 1 | |
| 2 | name ios-simulator-skill |
| 3 | version 1.5.0 |
| 4 | description 29 production-ready scripts for iOS app testing, building, and automation. Provides semantic UI navigation, build automation, accessibility testing, and simulator lifecycle management. Optimized for AI agents with minimal token output. |
| 5 | |
| 6 | |
| 7 | # iOS Simulator Skill |
| 8 | |
| 9 | Build, test, and automate iOS applications using accessibility-driven navigation and structured data instead of pixel coordinates. |
| 10 | |
| 11 | ## Quick Start |
| 12 | |
| 13 | |
| 14 | # 1. Check environment |
| 15 | bash scripts/sim_health_check.sh |
| 16 | |
| 17 | # 2. Launch app |
| 18 | python scripts/app_launcher.py --launch com.example.app |
| 19 | |
| 20 | # 3. Map screen to see elements |
| 21 | python scripts/screen_mapper.py |
| 22 | |
| 23 | # 4. Tap button |
| 24 | python scripts/navigator.py --find-text "Login" --tap |
| 25 | |
| 26 | # 5. Enter text |
| 27 | python scripts/navigator.py --find-type TextField --enter-text "[email protected]" |
| 28 | |
| 29 | |
| 30 | All scripts support `--help` for detailed options and `--json` for machine-readable output. |
| 31 | |
| 32 | ## Navigation Strategy |
| 33 | |
| 34 | **Always prefer the accessibility tree over screenshots for navigation.** The accessibility tree gives you element types, labels, frames, and tap targets — structured data that's cheaper and more reliable than image analysis. |
| 35 | |
| 36 | Use this priority: |
| 37 | `screen_mapper.py` → structured element list (5-7 lines, ~10 tokens) |
| 38 | `navigator.py --find-text/--find-type/--find-id` → semantic interaction |
| 39 | Screenshots → only for visual verification, bug reports, or visual diff |
| 40 | |
| 41 | Screenshots cost 1,600–6,300 tokens depending on size. The accessibility tree costs 10–50 tokens in default mode. |
| 42 | |
| 43 | ## 29 Production Scripts |
| 44 | |
| 45 | ### Build & Development (2 scripts) |
| 46 | |
| 47 | **build_and_test.py** - Build Xcode projects, run tests, parse results with progressive disclosure |
| 48 | Build with live result streaming |
| 49 | Parse errors and warnings from xcresult bundles |
| 50 | Retrieve detailed build logs on demand |
| 51 | Options: `--project`, `--scheme`, `--clean`, `--test`, `--verbose`, `--json` |
| 52 | |
| 53 | **log_monitor.py** - Real-time log monitoring with intelligent filtering |
| 54 | Stream logs or capture by duration |
| 55 | Filter by severity (error/warning/info/debug) |
| 56 | Deduplicate repeated messages |
| 57 | Options: `--app`, `--severity`, `--follow`, `--duration`, `--output`, `--json` |
| 58 | |
| 59 | ### Device State (2 scripts) |
| 60 | |
| 61 | **appearance.py** - Control simulator appearance: dark mode, Dynamic Type size, and locale/region |
| 62 | Toggle light/dark theme via `xcrun simctl ui` |
| 63 | Set Dynamic Type size with friendly aliases (XS through AX5) |
| 64 | Write locale and region defaults; optional app restart via `--bundle-id` |
| 65 | RTL flagged automatically for ar/he/fa/ur/yi locales |
| 66 | Options: `--theme`, `--text-size`, `--locale`, `--region`, `--reset`, `--bundle-id`, `--udid`, `--json`, `--verbose` |
| 67 | |
| 68 | **location.py** - Simulate GPS coordinates, named city presets, and GPX scenario playback |
| 69 | Fix a coordinate with `--lat`/`--lng` or pick a city with `--city` |
| 70 | Play a built-in scenario (City Run, Freeway Drive, etc.) via `--gpx <scenario>` |
| 71 | Animate multi-waypoint paths with configurable speed via `--waypoints` and `--speed` |
| 72 | Clear simulated location with `--clear`; list available scenarios with `--list-scenarios` |
| 73 | Options: `--lat`, `--lng`, `--city`, `--gpx`, `--waypoints`, `--speed`, `--clear`, `--list-scenarios`, `--udid`, `--json`, `--verbose` |
| 74 | |
| 75 | ### Navigation & Interaction (5 scripts) |
| 76 | |
| 77 | **screen_mapper.py** - Analyze current screen and list interactive elements |
| 78 | Element type breakdown |
| 79 | Interactive button list |
| 80 | Text field status |
| 81 | Options: `--verbose`, `--hints`, `--json` |
| 82 | |
| 83 | **navigator.py** - Find and interact with elements semantically |
| 84 | Find by text (fuzzy matching) |
| 85 | Find by element type |
| 86 | Find by accessibility ID |
| 87 | Enter text or tap elements |
| 88 | Options: `--find-text`, `--find-type`, `--find-id`, `--tap`, `--enter-text`, `--json` |
| 89 | |
| 90 | **gesture.py** - Perform swipes, scrolls, pinches, and complex gestures |
| 91 | Directional swipes (up/down/left/right) |
| 92 | Multi-swipe scrolling |
| 93 | Pinch zoom |
| 94 | Long press |
| 95 | Pull to refresh |
| 96 | Options: `--swipe`, `--scroll`, `--pinch`, `--long-press`, `--refresh`, `--json` |
| 97 | |
| 98 | **keyboard.py** - Text input and hardware button control |
| 99 | Type text (fast or slow) |
| 100 | Special keys (return, delete, tab, space, arrows) |
| 101 | Hardware buttons (home, lock, volume, screenshot) |
| 102 | Key combinations |
| 103 | Options: `--type`, `--key`, `--button`, `--slow`, `--clear`, `--dismiss`, `--json` |
| 104 | |
| 105 | **app_launcher.py** - App lifecycle management |
| 106 | Launch apps by bundle ID |
| 107 | Terminate apps |
| 108 | Install/uninstall from .app bundles |
| 109 | Deep link navigation |
| 110 | List installed apps |
| 111 | Check app state |
| 112 | Pass launch arguments (`--args`) and environment variables (`--env KEY=VALUE`, injected as `SIMCTL_CHILD_*`) to the app on launch/restart |
| 113 | Options: `--launch`, `--terminate`, `--restart`, `--install`, `--uninstall`, `--open-url`, `--list`, `--state`, `--args`, `--env`, `--wait-for-debugger` |
| 114 | |
| 115 | ### Testing & Analysis (9 scripts) |
| 116 | |
| 117 | **accessibility_audit.py** - Check WCAG compliance on current screen |
| 118 | Critical issues (missing labels, empty buttons, no alt text) |
| 119 | Warnings (missing hints, small touch targets) |
| 120 | Info (missing IDs, deep nesting) |
| 121 | Options: `--verbose`, `--output`, `--json` |
| 122 | |
| 123 | **visual_diff.py** - Compare two screenshots for visual changes |
| 124 | Pixel-by-pixel comparison |
| 125 | Threshold-based pass/fail |
| 126 | Generate diff images |
| 127 | Options: `--threshold`, `--output`, `--details`, `--json` |
| 128 | |
| 129 | **test_recorder.py** - Automatically document test execution |
| 130 | Capture screenshots and accessibility trees per step |
| 131 | Generate markdown reports with timing data |
| 132 | Options: `--test-name`, `--output`, `--verbose`, `--json` |
| 133 | |
| 134 | **app_state_capture.py** - Create comprehensive debugging snapshots |
| 135 | Screenshot, UI hierarchy, app logs, device info |
| 136 | Markdown summary for bug reports |
| 137 | Options: `--app-bundle-id`, `--output`, `--log-lines`, `--json` |
| 138 | |
| 139 | **sim_health_check.sh** - Verify environment is properly configured |
| 140 | Check macOS, Xcode, simctl, IDB, Python |
| 141 | List available and booted simulators |
| 142 | Verify Python packages (Pillow) |
| 143 | |
| 144 | **model_inspector.py** - Inspect Core Data and SwiftData models from project files |
| 145 | Parse .xcdatamodeld packages (entities, attributes, relationships) |
| 146 | Detect model versions and current active version |
| 147 | Best-effort SwiftData @Model class extraction |
| 148 | Raw source dump for any model on demand (`--raw ModelName`) |
| 149 | Options: `--project-path`, `--core-data-only`, `--swiftdata-only`, `--show-versions`, `--raw`, `--verbose`, `--json` |
| 150 | |
| 151 | **container.py** - Inspect app sandbox: files, UserDefaults, and Core Data store paths |
| 152 | List data container files at configurable depth via `--ls` |
| 153 | Read files with auto-detected plist decoding via `--cat` (large files cached) |
| 154 | Dump UserDefaults as key=value or JSON via `--userdefaults` |
| 155 | Locate `.sqlite` / `.sqlite-wal` / `.sqlite-shm` stores via `--core-data-path` |
| 156 | Export full container snapshot via `--export` |
| 157 | Options: `--ls`, `--cat`, `--userdefaults`, `--core-data-path`, `--export`, `--udid`, `--json`, `--verbose` |
| 158 | |
| 159 | **hang_watcher.py** (HangBuster) - Record + summarise os_log hang events with progressive disclosure |
| 160 | **Session mode (HangBuster, agent-native):** start a detached recorder, interact with the simulator, stop for a token-tight summary |
| 161 | `--start` → returns a session ID; detached worker normalises + thresholds events on the fly |
| 162 | `--stop SESSION_ID` → emits ~80–120 token L1 summary (header + top-N clusters + drill hint) |
| 163 | `--get-details SESSION_ID [--cluster N | --raw]` → L2 full clusters or L3 per-event detail |
| 164 | `--list-sessions` / `--clear-sessions [--older-than 24h]` / `--diff A B` (cross-session regression report) |
| 165 | Filter pipeline: parse → normalise → threshold → bucket → cluster → aggregate → rank → format (in `common/hang_pipeline.py`) |
| 166 | `--budget-tokens N` picks the densest level (L0/L1/L2) that fits; `--terse` forces L0 |
| 167 | `--auto-sample` captures a main-thread stack on first event per cluster (soft dependency: `main_thread_sampler.py` #62; graceful no-op if absent) |
| 168 | **Raw capture mode (full fidelity for `jq` exploration):** skip the clustering pipeline, dump every matching log line verbatim to `raw.ndjson` |
| 169 | `--start --raw-capture [--max-size-mb 10] [--no-gzip]` — spawns `log stream --style ndjson` |
| 170 | Per-session size cap (`--max-size-mb`, default 10) — worker stops cleanly on cap; `extras.truncated=true` |
| 171 | `--stop` gzips `raw.ndjson` → `raw.ndjson.gz` (~15–19× compression; `--no-gzip` opts out) |
| 172 | `--get-details SESSION_ID` on a raw session prints the path with a `zcat | jq ...` hint |
| 173 | **Resilience (auto-restart on stream death):** EOF or subprocess death triggers a `stream_died` event then a bounded restart with 2s backoff. After `IOS_SIM_HANG_MAX_RESTARTS` (default 3) the session is marked `crashed`, never left in stale `running` state. `--list-sessions` shows `capture=Xs` and `restarts=N`. |
| 174 | **Cleanup is automatic:** TTL prune (`IOS_SIM_HANG_SESSION_TTL_HOURS`, default 24h) + aggregate cap (`IOS_SIM_HANG_TOTAL_CAP_MB`, default 100 MB, oldest-first eviction) both run on every `--start`. |
| 175 | **Legacy modes (unchanged for backward compat):** `--watch [--duration N]` (live stream) and `--since 5m` (historical) |
| 176 | Filters: `--bundle-id` (post-parse — hang capture stays simulator-global so RunningBoard/SpringBoard events are kept), `--predicate` (also via `IOS_SIM_HANG_PREDICATE`) |
| 177 | All output supports `--json`; session storage at `~/.ios-simulator-skill/sessions/<id>/{meta.json,events.jsonl,summary.json,raw.ndjson.gz}` |
| 178 | |
| 179 | **Quick start (summarised mode):** |
| 180 | |
| 181 | SID=$(python scripts/hang_watcher.py --start --min-hang-ms 200) |
| 182 | # ... interact with the simulator (open sheets, scroll, navigate) ... |
| 183 | python scripts/hang_watcher.py --stop $SID # token-tight L1 summary |
| 184 | python scripts/hang_watcher.py --get-details $SID --cluster 1 # drill into cluster 1 |
| 185 | python scripts/hang_watcher.py --diff $SID_BASELINE $SID # cross-session regression |
| 186 | |
| 187 | |
| 188 | **Quick start (raw capture + `jq` exploration):** |
| 189 | |
| 190 | SID=$(python scripts/hang_watcher.py --start --raw-capture --max-size-mb 5) |
| 191 | # ... interact with the simulator ... |
| 192 | python scripts/hang_watcher.py --stop $SID |
| 193 | # → "Session ...: raw mode, 737 lines, 0.96 MB → 0.05 MB gzipped" |
| 194 | |
| 195 | # Top processes by event count: |
| 196 | zcat ~/.ios-simulator-skill/sessions/$SID/raw.ndjson.gz \ |
| 197 | | jq -s 'group_by(.processImagePath) | map({proc: (.[0].processImagePath | split("/") | last), n: length}) | sort_by(-.n) | .[:5]' |
| 198 | |
| 199 | # All RunningBoard assertion invalidations: |
| 200 | zcat .../raw.ndjson.gz | jq -c 'select(.subsystem == "com.apple.runningboard" and (.eventMessage | startswith("Invalidating")))' |
| 201 | |
| 202 | # Hangs per minute: |
| 203 | zcat .../raw.ndjson.gz | jq -r '.timestamp[:16]' | sort | uniq -c |
| 204 | |
| 205 | |
| 206 | **localization_audit.py** - Detect string catalog gaps, missing keys, and placeholder mismatches |
| 207 | Report missing and `needs_review`/`new` keys per locale in `.xcstrings` catalogs |
| 208 | Cross-reference catalog keys against Swift source (`String(localized:)` / `NSLocalizedString`) via `--source` |
| 209 | Flag placeholder count mismatches (`%d`, `%@`, `%s`, `%lld`) across locales |
| 210 | Legacy `.strings` and `.stringsdict` support via `plistlib` |
| 211 | CI-friendly `--strict` exits 2 on any finding |
| 212 | Options: `--catalog`, `--source`, `--locale`, `--strict`, `--json`, `--verbose` |
| 213 | |
| 214 | ### Advanced Testing & Permissions (4 scripts) |
| 215 | |
| 216 | **clipboard.py** - Manage simulator clipboard for paste testing |
| 217 | Copy text to clipboard |
| 218 | Test paste flows without manual entry |
| 219 | Options: `--copy`, `--test-name`, `--expected`, `--json` |
| 220 | |
| 221 | **status_bar.py** - Override simulator status bar appearance |
| 222 | Presets: clean (9:41, 100% battery), testing (11:11, 50%), low-battery (20%), airplane (offline) |
| 223 | Custom time, network, battery, WiFi settings |
| 224 | Options: `--preset`, `--time`, `--data-network`, `--battery-level`, `--clear`, `--json` |
| 225 | |
| 226 | **push_notification.py** - Send simulated push notifications |
| 227 | Simple mode (title + body + badge) |
| 228 | Custom JSON payloads |
| 229 | Test notification handling and deep links |
| 230 | Options: `--bundle-id`, `--title`, `--body`, `--badge`, `--payload`, `--json` |
| 231 | |
| 232 | **privacy_manager.py** - Grant, revoke, and reset app permissions |
| 233 | 13 supported services (camera, microphone, location, contacts, photos, calendar, health, etc.) |
| 234 | Batch operations (comma-separated services) |
| 235 | Audit trail with test scenario tracking |
| 236 | Options: `--bundle-id`, `--grant`, `--revoke`, `--reset`, `--list`, `--json` |
| 237 | |
| 238 | ### Simulator Discovery (2 scripts) |
| 239 | |
| 240 | **sim_list.py** - List simulators with progressive disclosure |
| 241 | Concise summary by default (total / available / booted) |
| 242 | Full details on demand via cache IDs |
| 243 | Filter by device type |
| 244 | Suggest recommended simulators with `--suggest` |
| 245 | 96% token reduction vs raw `simctl list` (57k → 2k tokens) |
| 246 | Options: `--get-details`, `--suggest`, `--device-type`, `--json` |
| 247 | |
| 248 | **simulator_selector.py** - Suggest the best simulator for the job |
| 249 | Ranks candidates by recent use (from `config.json`), latest iOS, common test models, and boot status |
| 250 | List all available simulators with `--list` |
| 251 | Boot a selected simulator directly with `--boot` |
| 252 | JSON output for programmatic use |
| 253 | Options: `--suggest`, `--list`, `--boot`, `--json` |
| 254 | |
| 255 | ### Device Lifecycle Management (5 scripts) |
| 256 | |
| 257 | **simctl_boot.py** - Boot simulators with optional readiness verification |
| 258 | Boot by UDID or device name |
| 259 | Wait for device ready with timeout |
| 260 | Batch boot operations (--all, --type) |
| 261 | Performance timing |
| 262 | Options: `--udid`, `--name`, `--wait-ready`, `--timeout`, `--all`, `--type`, `--json` |
| 263 | |
| 264 | **simctl_shutdown.py** - Gracefully shutdown simulators |
| 265 | Shutdown by UDID or device name |
| 266 | Optional verification of shutdown completion |
| 267 | Batch shutdown operations |
| 268 | Options: `--udid`, `--name`, `--verify`, `--timeout`, `--all`, `--type`, `--json` |
| 269 | |
| 270 | **simctl_create.py** - Create simulators dynamically |
| 271 | Create by device type and iOS version |
| 272 | List available device types and runtimes |
| 273 | Custom device naming |
| 274 | Returns UDID for CI/CD integration |
| 275 | Options: `--device`, `--runtime`, `--name`, `--list-devices`, `--list-runtimes`, `--json` |
| 276 | |
| 277 | **simctl_delete.py** - Permanently delete simulators |
| 278 | Delete by UDID or device name |
| 279 | Safety confirmation by default (skip with --yes) |
| 280 | Batch delete operations |
| 281 | Smart deletion (--old N to keep N per device type) |
| 282 | Options: `--udid`, `--name`, `--yes`, `--all`, `--type`, `--old`, `--json` |
| 283 | |
| 284 | **simctl_erase.py** - Factory reset simulators without deletion |
| 285 | Preserve device UUID (faster than delete+create) |
| 286 | Erase all, by type, or booted simulators |
| 287 | Optional verification |
| 288 | Options: `--udid`, `--name`, `--verify`, `--timeout`, `--all`, `--type`, `--booted`, `--json` |
| 289 | |
| 290 | ## Common Patterns |
| 291 | |
| 292 | **Auto-UDID Detection**: Most scripts auto-detect the booted simulator if --udid is not provided. |
| 293 | |
| 294 | **Device Name Resolution**: Use device names (e.g., "iPhone 16 Pro") instead of UDIDs - scripts resolve automatically. |
| 295 | |
| 296 | **Batch Operations**: Many scripts support `--all` for all simulators or `--type iPhone` for device type filtering. |
| 297 | |
| 298 | **Output Formats**: Default is concise human-readable output. Use `--json` for machine-readable output in CI/CD. |
| 299 | |
| 300 | **Help**: All scripts support `--help` for detailed options and examples. |
| 301 | |
| 302 | **Screenshot Sizing**: Screenshots are resized to save tokens. Presets: `full` (3-4 tiles, ~5K tokens), `half` (1 tile, ~1.6K tokens, default), `quarter` (1 tile, ~800 tokens, less detail). Use `quarter` for quick visual checks, `half` for readable UI, `full` only when pixel-level detail matters. Scripts that capture screenshots (`app_state_capture.py`, `test_recorder.py`) default to `half`. |
| 303 | |
| 304 | ## Typical Workflow |
| 305 | |
| 306 | Verify environment: `bash scripts/sim_health_check.sh` |
| 307 | Launch app: `python scripts/app_launcher.py --launch com.example.app` |
| 308 | Analyze screen: `python scripts/screen_mapper.py` |
| 309 | Interact: `python scripts/navigator.py --find-text "Button" --tap` |
| 310 | Verify: `python scripts/accessibility_audit.py` |
| 311 | Debug if needed: `python scripts/app_state_capture.py --app-bundle-id com.example.app` |
| 312 | |
| 313 | ## Configuration |
| 314 | |
| 315 | Most operational limits can be tuned via environment variables. Defaults work for typical local development; raise them for slow CI runners, large monorepo builds, or accessibility audits on complex screens. |
| 316 | |
| 317 | | Variable | Default | Controls | |
| 318 | |---|---|---| |
| 319 | | `IOS_SIM_A11Y_LABEL_MAX` | `80` | Max chars of `AXLabel` retained in accessibility audit output | |
| 320 | | `IOS_SIM_A11Y_TOP_ISSUES` | `10` | Top accessibility issues surfaced per audit | |
| 321 | | `IOS_SIM_APPS_PREVIEW` | `30` | App entries listed by `app_launcher.py` before truncation | |
| 322 | | `IOS_SIM_BOOT_SUBPROCESS_TIMEOUT` | `60` | Timeout for the `simctl boot` subprocess itself (seconds) | |
| 323 | | `IOS_SIM_BOOT_TIMEOUT` | `300` | Wait-for-ready timeout after boot (seconds) | |
| 324 | | `IOS_SIM_BUILD_JSON_CAP` | `50` | Max build errors / failed tests in JSON output | |
| 325 | | `IOS_SIM_BUILD_LOG_PREVIEW` | `4000` | Chars of build log preview in default output | |
| 326 | | `IOS_SIM_BUILD_TIMEOUT` | `1800` | Max seconds for an `xcodebuild build` invocation before kill | |
| 327 | | `IOS_SIM_INTROSPECT_TIMEOUT` | `60` | Timeout for `xcodebuild -list` and `simctl list` lookups (seconds) | |
| 328 | | `IOS_SIM_TEST_TIMEOUT` | `2700` | Max seconds for an `xcodebuild test` invocation before kill | |
| 329 | | `IOS_SIM_BUILD_SUMMARY_CAP` | `15` | Errors/failures in default build summary | |
| 330 | | `IOS_SIM_BUILD_VERBOSE_CAP` | `100` | Errors/warnings in verbose build output | |
| 331 | | `IOS_SIM_CACHE_MAX_ENTRIES` | `500` | Max entries in progressive disclosure cache (LRU eviction) | |
| 332 | | `IOS_SIM_CACHE_TTL_HOURS` | `1` | Cache entry expiration | |
| 333 | | `IOS_SIM_ERASE_TIMEOUT` | `90` | Wait-for-erase timeout (seconds) | |
| 334 | | `IOS_SIM_HANG_PREDICATE` | _(default)_ | Override the `os_log` predicate used by `hang_watcher.py` (default catches RunningBoard kills + "Hang detected" + main-thread hangs). Hang events originate from system daemons (RunningBoard, SpringBoard) so the predicate stays simulator-global — `--bundle-id` is applied post-parse, not ANDed in. | |
| 335 | | `IOS_SIM_HANG_MIN_MS` | `250` | HangBuster threshold — events below this duration never reach disk (smaller = more sensitive, larger summaries) | |
| 336 | | `IOS_SIM_HANG_SESSION_TTL_HOURS` | `24` | HangBuster session prune age; pruning runs on every `--start` | |
| 337 | | `IOS_SIM_HANG_DEFAULT_TOP_N` | `3` | Default top-N clusters in `--stop` L1 output | |
| 338 | | `IOS_SIM_HANG_BUDGET_TOKENS` | _(unset)_ | Default token budget for `--stop` (picks L0/L1/L2 to fit) | |
| 339 | | `IOS_SIM_HANG_MAX_RESTARTS` | `3` | HangBuster worker: max `log stream` respawn attempts on EOF/subprocess death before the session is marked `crashed` | |
| 340 | | `IOS_SIM_HANG_TOTAL_CAP_MB` | `100` | HangBuster aggregate disk cap. When total session-state exceeds this on `--start`, oldest sessions are dropped first. Set to `0` to disable. | |
| 341 | | `IOS_SIM_LOG_JSON_CAP` | `100` | Max errors/warnings in `log_monitor.py` JSON output | |
| 342 | | `IOS_SIM_LOG_LINE_MAX` | `300` | Per-line truncation in log summaries | |
| 343 | | `IOS_SIM_LOG_TAIL` | `200` | Lines of log tail in verbose / sample output | |
| 344 | | `IOS_SIM_LOG_TEXT_SUMMARY` | `15` | Errors/warnings shown in text-mode log summary | |
| 345 | | `IOS_SIM_MAX_ELEMENTS` | `25` | Tappable elements listed by `navigator.py` | |
| 346 | | `IOS_SIM_POLL_INTERVAL` | `0.5` | Boot/erase state polling interval (seconds) | |
| 347 | | `IOS_SIM_RELAUNCH_DELAY_MS` | `1000` | Delay between terminate and re-launch in `app_launcher.py` | |
| 348 | | `IOS_SIM_SCREEN_BUTTONS_PREVIEW` | `15` | Button names listed by `screen_mapper.py` | |
| 349 | | `IOS_SIM_SCREEN_SECTION_ITEMS` | `10` | Items per section shown by `screen_mapper.py` | |
| 350 | | `IOS_SIM_STATE_SUBPROCESS_TIMEOUT` | `15` | Subprocess timeout in `app_state_capture.py` (seconds) | |
| 351 | | `IOS_SIM_TAP_SETTLE_MS` | `500` | Post-tap settle delay in `navigator.py` | |
| 352 | |
| 353 | Example: |
| 354 | |
| 355 | |
| 356 | # Slow GitHub Actions runner: give boot 10 minutes |
| 357 | IOS_SIM_BOOT_TIMEOUT=600 python scripts/simctl_boot.py --wait-ready |
| 358 | |
| 359 | |
| 360 | ## Requirements |
| 361 | |
| 362 | macOS 15 (Sequoia)+ |
| 363 | Xcode 26+ and Command Line Tools |
| 364 | Python 3.12+ |
| 365 | `idb` **1.5.1+** - required for every interactive script (tap, swipe, type): |
| 366 | `brew tap facebook/fb && brew install facebook/fb/idb-companion facebook/fb/idb-cli` |
| 367 | Pillow, for visual diffs only: `pip3 install pillow` |
| 368 | |
| 369 | Verify with `bash scripts/sim_health_check.sh` (add `--json` for structured output). |
| 370 | |
| 371 | ## Troubleshooting |
| 372 | |
| 373 | **Taps, swipes and typing do nothing, but reads work.** `idb` reports success and the screen |
| 374 | never changes. On Xcode 27 this means `idb-companion` is older than 1.5.1: it looks for |
| 375 | `SimulatorKit.framework` at the path Xcode 26 used. Upgrade: |
| 376 | `brew upgrade facebook/fb/idb-companion`. |
| 377 | |
| 378 | **`SimulatorKit is required for HID interactions`.** Same cause, same fix. |
| 379 | |
| 380 | **`Connection refused` or `No such file` from every idb call.** A dead companion is still in |
| 381 | idb's registry at `/tmp/idb/state`, so idb dials a socket nobody is listening on instead of |
| 382 | starting a fresh companion. Fix: `idb disconnect <udid>`. |
| 383 | |
| 384 | **`open -a Simulator` fails.** Xcode 27 has no `Simulator.app`; it was replaced by |
| 385 | `DeviceHub.app` in `Xcode.app/Contents/Applications/`. Boot headlessly instead: |
| 386 | `xcrun simctl boot <udid>`. Note that quitting DeviceHub shuts down the simulator it hosts. |
| 387 | |
| 388 | **`idb: command not found`.** The companion and the CLI are separate packages; install both |
| 389 | (see Requirements). If `which -a idb` shows more than one, the first on `PATH` wins. |
| 390 | |
| 391 | ## Documentation |
| 392 | |
| 393 | **SKILL.md** (this file) - Script reference, requirements, troubleshooting |
| 394 | **README.md** - Installation, updating idb, Xcode 27 notes |
| 395 | **CLAUDE.md** - Architecture and implementation details |
| 396 | |
| 397 | ## Key Design Principles |
| 398 | |
| 399 | **Semantic Navigation**: Find elements by meaning (text, type, ID) not pixel coordinates. Survives UI changes. |
| 400 | |
| 401 | **Token Efficiency**: Concise default output (3-5 lines) with optional verbose and JSON modes for detailed results. |
| 402 | |
| 403 | **Accessibility-First**: Built on standard accessibility APIs for reliability and compatibility. |
| 404 | |
| 405 | **Zero Configuration**: Works immediately on any macOS with Xcode. No setup required. |
| 406 | |
| 407 | **Structured Data**: Scripts output JSON or formatted text, not raw logs. Easy to parse and integrate. |
| 408 | |
| 409 | **Auto-Learning**: Build system remembers your device preference. Configuration stored per-project. |
| 410 | |
| 411 | |
| 412 | |
| 413 | Use these scripts directly or let Claude Code invoke them automatically when your request matches the skill description. |
| 414 |
Discussion
Alternatives
Browse more free Claude skills or everything in Development.