mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-02-28 00:22:41 -08:00
update
This commit is contained in:
375
.agents/skills/ios-simulator-skill/scripts/simulator_selector.py
Executable file
375
.agents/skills/ios-simulator-skill/scripts/simulator_selector.py
Executable file
@@ -0,0 +1,375 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Intelligent Simulator Selector
|
||||
|
||||
Suggests the best available iOS simulators based on:
|
||||
- Recently used (from config)
|
||||
- Latest iOS version
|
||||
- Common models for testing
|
||||
- Boot status
|
||||
|
||||
Usage Examples:
|
||||
# Get suggestions for user selection
|
||||
python scripts/simulator_selector.py --suggest
|
||||
|
||||
# List all available simulators
|
||||
python scripts/simulator_selector.py --list
|
||||
|
||||
# Boot a specific simulator
|
||||
python scripts/simulator_selector.py --boot "67A99DF0-27BD-4507-A3DE-B7D8C38F764A"
|
||||
|
||||
# Get suggestions as JSON for programmatic use
|
||||
python scripts/simulator_selector.py --suggest --json
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# Try to import config from build_and_test if available
|
||||
try:
|
||||
from xcode.config import Config
|
||||
except ImportError:
|
||||
Config = None
|
||||
|
||||
|
||||
class SimulatorInfo:
|
||||
"""Information about an iOS simulator."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
udid: str,
|
||||
ios_version: str,
|
||||
status: str,
|
||||
):
|
||||
"""Initialize simulator info."""
|
||||
self.name = name
|
||||
self.udid = udid
|
||||
self.ios_version = ios_version
|
||||
self.status = status
|
||||
self.reasons: list[str] = []
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""Convert to dictionary."""
|
||||
return {
|
||||
"device": self.name,
|
||||
"udid": self.udid,
|
||||
"ios": self.ios_version,
|
||||
"status": self.status,
|
||||
"reasons": self.reasons,
|
||||
}
|
||||
|
||||
|
||||
class SimulatorSelector:
|
||||
"""Intelligent simulator selection."""
|
||||
|
||||
# Common iPhone models ranked by testing priority
|
||||
COMMON_MODELS = [
|
||||
"iPhone 16 Pro",
|
||||
"iPhone 16",
|
||||
"iPhone 15 Pro",
|
||||
"iPhone 15",
|
||||
"iPhone SE (3rd generation)",
|
||||
]
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize selector."""
|
||||
self.simulators: list[SimulatorInfo] = []
|
||||
self.config: dict | None = None
|
||||
self.last_used_simulator: str | None = None
|
||||
|
||||
# Load config if available
|
||||
if Config:
|
||||
try:
|
||||
config = Config.load()
|
||||
self.last_used_simulator = config.get_preferred_simulator()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def list_simulators(self) -> list[SimulatorInfo]:
|
||||
"""
|
||||
List all available simulators.
|
||||
|
||||
Returns:
|
||||
List of SimulatorInfo objects
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["xcrun", "simctl", "list", "devices", "--json"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
data = json.loads(result.stdout)
|
||||
simulators = []
|
||||
|
||||
# Parse devices by iOS version
|
||||
for runtime, devices in data.get("devices", {}).items():
|
||||
# Extract iOS version from runtime (e.g., "com.apple.CoreSimulator.SimRuntime.iOS-18-0")
|
||||
ios_version_match = re.search(r"iOS-(\d+-\d+)", runtime)
|
||||
if not ios_version_match:
|
||||
continue
|
||||
|
||||
ios_version = ios_version_match.group(1).replace("-", ".")
|
||||
|
||||
for device in devices:
|
||||
name = device.get("name", "")
|
||||
udid = device.get("udid", "")
|
||||
is_available = device.get("isAvailable", False)
|
||||
|
||||
if not is_available or "iPhone" not in name:
|
||||
continue
|
||||
|
||||
status = device.get("state", "").capitalize()
|
||||
sim_info = SimulatorInfo(name, udid, ios_version, status)
|
||||
simulators.append(sim_info)
|
||||
|
||||
self.simulators = simulators
|
||||
return simulators
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error listing simulators: {e.stderr}", file=sys.stderr)
|
||||
return []
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error parsing simulator list: {e}", file=sys.stderr)
|
||||
return []
|
||||
|
||||
def get_suggestions(self, count: int = 4) -> list[SimulatorInfo]:
|
||||
"""
|
||||
Get top N suggested simulators.
|
||||
|
||||
Ranking factors:
|
||||
1. Recently used (from config)
|
||||
2. Latest iOS version
|
||||
3. Common models
|
||||
4. Boot status (Booted preferred)
|
||||
|
||||
Args:
|
||||
count: Number of suggestions to return
|
||||
|
||||
Returns:
|
||||
List of suggested SimulatorInfo objects
|
||||
"""
|
||||
if not self.simulators:
|
||||
return []
|
||||
|
||||
# Score each simulator
|
||||
scored = []
|
||||
for sim in self.simulators:
|
||||
score = self._score_simulator(sim)
|
||||
scored.append((score, sim))
|
||||
|
||||
# Sort by score (descending)
|
||||
scored.sort(key=lambda x: x[0], reverse=True)
|
||||
|
||||
# Return top N
|
||||
suggestions = [sim for _, sim in scored[:count]]
|
||||
|
||||
# Add reasons to each suggestion
|
||||
for i, sim in enumerate(suggestions, 1):
|
||||
if i == 1:
|
||||
sim.reasons.append("Recommended")
|
||||
|
||||
# Check if recently used
|
||||
if self.last_used_simulator and self.last_used_simulator == sim.name:
|
||||
sim.reasons.append("Recently used")
|
||||
|
||||
# Check if latest iOS
|
||||
latest_ios = max(s.ios_version for s in self.simulators)
|
||||
if sim.ios_version == latest_ios:
|
||||
sim.reasons.append("Latest iOS")
|
||||
|
||||
# Check if common model
|
||||
for j, model in enumerate(self.COMMON_MODELS):
|
||||
if model in sim.name:
|
||||
sim.reasons.append(f"#{j+1} common model")
|
||||
break
|
||||
|
||||
# Check if booted
|
||||
if sim.status == "Booted":
|
||||
sim.reasons.append("Currently running")
|
||||
|
||||
return suggestions
|
||||
|
||||
def _score_simulator(self, sim: SimulatorInfo) -> float:
|
||||
"""
|
||||
Score a simulator for ranking.
|
||||
|
||||
Higher score = better recommendation.
|
||||
|
||||
Args:
|
||||
sim: Simulator to score
|
||||
|
||||
Returns:
|
||||
Score value
|
||||
"""
|
||||
score = 0.0
|
||||
|
||||
# Recently used gets highest priority (100 points)
|
||||
if self.last_used_simulator and self.last_used_simulator == sim.name:
|
||||
score += 100
|
||||
|
||||
# Latest iOS version (50 points)
|
||||
latest_ios = max(s.ios_version for s in self.simulators)
|
||||
if sim.ios_version == latest_ios:
|
||||
score += 50
|
||||
|
||||
# Common models (30-20 points based on ranking)
|
||||
for i, model in enumerate(self.COMMON_MODELS):
|
||||
if model in sim.name:
|
||||
score += 30 - (i * 2) # Higher ranking models get more points
|
||||
break
|
||||
|
||||
# Currently booted (10 points)
|
||||
if sim.status == "Booted":
|
||||
score += 10
|
||||
|
||||
# iOS version number (minor factor for breaking ties)
|
||||
ios_numeric = float(sim.ios_version.replace(".", ""))
|
||||
score += ios_numeric * 0.1
|
||||
|
||||
return score
|
||||
|
||||
def boot_simulator(self, udid: str) -> bool:
|
||||
"""
|
||||
Boot a simulator.
|
||||
|
||||
Args:
|
||||
udid: Simulator UDID
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
try:
|
||||
subprocess.run(
|
||||
["xcrun", "simctl", "boot", udid],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error booting simulator: {e.stderr}", file=sys.stderr)
|
||||
return False
|
||||
|
||||
|
||||
def format_suggestions(suggestions: list[SimulatorInfo], json_format: bool = False) -> str:
|
||||
"""
|
||||
Format suggestions for output.
|
||||
|
||||
Args:
|
||||
suggestions: List of suggestions
|
||||
json_format: If True, output as JSON
|
||||
|
||||
Returns:
|
||||
Formatted string
|
||||
"""
|
||||
if json_format:
|
||||
data = {"suggestions": [s.to_dict() for s in suggestions]}
|
||||
return json.dumps(data, indent=2)
|
||||
|
||||
if not suggestions:
|
||||
return "No simulators available"
|
||||
|
||||
lines = ["Available Simulators:\n"]
|
||||
for i, sim in enumerate(suggestions, 1):
|
||||
lines.append(f"{i}. {sim.name} (iOS {sim.ios_version})")
|
||||
if sim.reasons:
|
||||
lines.append(f" {', '.join(sim.reasons)}")
|
||||
lines.append(f" UDID: {sim.udid}")
|
||||
lines.append("")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Intelligent iOS simulator selector",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
epilog="""
|
||||
Examples:
|
||||
# Get suggestions for user selection
|
||||
python scripts/simulator_selector.py --suggest
|
||||
|
||||
# List all available simulators
|
||||
python scripts/simulator_selector.py --list
|
||||
|
||||
# Boot a specific simulator
|
||||
python scripts/simulator_selector.py --boot <UDID>
|
||||
|
||||
# Get suggestions as JSON
|
||||
python scripts/simulator_selector.py --suggest --json
|
||||
""",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--suggest",
|
||||
action="store_true",
|
||||
help="Get top simulator suggestions",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--list",
|
||||
action="store_true",
|
||||
help="List all available simulators",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--boot",
|
||||
metavar="UDID",
|
||||
help="Boot specific simulator by UDID",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--json",
|
||||
action="store_true",
|
||||
help="Output as JSON",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--count",
|
||||
type=int,
|
||||
default=4,
|
||||
help="Number of suggestions (default: 4)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
selector = SimulatorSelector()
|
||||
|
||||
if args.boot:
|
||||
# Boot specific simulator
|
||||
success = selector.boot_simulator(args.boot)
|
||||
if success:
|
||||
print(f"Booted simulator: {args.boot}")
|
||||
return 0
|
||||
return 1
|
||||
|
||||
if args.list:
|
||||
# List all simulators
|
||||
simulators = selector.list_simulators()
|
||||
output = format_suggestions(simulators, args.json)
|
||||
print(output)
|
||||
return 0
|
||||
|
||||
if args.suggest:
|
||||
# Get suggestions
|
||||
selector.list_simulators()
|
||||
suggestions = selector.get_suggestions(args.count)
|
||||
output = format_suggestions(suggestions, args.json)
|
||||
print(output)
|
||||
return 0
|
||||
|
||||
# Default: show suggestions
|
||||
selector.list_simulators()
|
||||
suggestions = selector.get_suggestions(args.count)
|
||||
output = format_suggestions(suggestions, args.json)
|
||||
print(output)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user