mirror of
https://github.com/ksyasuda/dotfiles.git
synced 2026-02-28 00:22:41 -08:00
update
This commit is contained in:
299
.agents/skills/ios-simulator-skill/scripts/sim_list.py
Normal file
299
.agents/skills/ios-simulator-skill/scripts/sim_list.py
Normal file
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
iOS Simulator Listing with Progressive Disclosure
|
||||
|
||||
Lists available simulators with token-efficient summaries.
|
||||
Full details available on demand via cache IDs.
|
||||
|
||||
Achieves 96% token reduction (57k→2k tokens) for common queries.
|
||||
|
||||
Usage Examples:
|
||||
# Concise summary (default)
|
||||
python scripts/sim_list.py
|
||||
|
||||
# Get full details for cached list
|
||||
python scripts/sim_list.py --get-details <cache-id>
|
||||
|
||||
# Get recommendations
|
||||
python scripts/sim_list.py --suggest
|
||||
|
||||
# Filter by device type
|
||||
python scripts/sim_list.py --device-type iPhone
|
||||
|
||||
Output (default):
|
||||
Simulator Summary [cache-sim-20251028-143052]
|
||||
├─ Total: 47 devices
|
||||
├─ Available: 31
|
||||
└─ Booted: 1
|
||||
|
||||
✓ iPhone 16 Pro (iOS 18.1) [ABC-123...]
|
||||
|
||||
Use --get-details cache-sim-20251028-143052 for full list
|
||||
|
||||
Technical Details:
|
||||
- Uses xcrun simctl list devices
|
||||
- Caches results with 1-hour TTL
|
||||
- Reduces output by 96% by default
|
||||
- Token efficiency: summary = ~30 tokens, full list = ~1500 tokens
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from common import get_cache
|
||||
|
||||
|
||||
class SimulatorLister:
|
||||
"""Lists iOS simulators with progressive disclosure."""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize lister with cache."""
|
||||
self.cache = get_cache()
|
||||
|
||||
def list_simulators(self) -> dict:
|
||||
"""
|
||||
Get list of all simulators.
|
||||
|
||||
Returns:
|
||||
Dict with structure:
|
||||
{
|
||||
"devices": [...],
|
||||
"runtimes": [...],
|
||||
"total_devices": int,
|
||||
"available_devices": int,
|
||||
"booted_devices": [...]
|
||||
}
|
||||
"""
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["xcrun", "simctl", "list", "devices", "--json"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
|
||||
return json.loads(result.stdout)
|
||||
except (subprocess.CalledProcessError, json.JSONDecodeError):
|
||||
return {"devices": {}, "runtimes": []}
|
||||
|
||||
def parse_devices(self, sim_data: dict) -> list[dict]:
|
||||
"""
|
||||
Parse simulator data into flat list.
|
||||
|
||||
Returns:
|
||||
List of device dicts with runtime info
|
||||
"""
|
||||
devices = []
|
||||
|
||||
devices_by_runtime = sim_data.get("devices", {})
|
||||
|
||||
for runtime_str, device_list in devices_by_runtime.items():
|
||||
# Extract iOS version from runtime string
|
||||
# Format: "iOS 18.1", "tvOS 18", etc.
|
||||
runtime_name = runtime_str.replace(" Simulator", "").strip()
|
||||
|
||||
for device in device_list:
|
||||
devices.append(
|
||||
{
|
||||
"name": device.get("name"),
|
||||
"udid": device.get("udid"),
|
||||
"state": device.get("state"),
|
||||
"runtime": runtime_name,
|
||||
"is_available": device.get("isAvailable", False),
|
||||
}
|
||||
)
|
||||
|
||||
return devices
|
||||
|
||||
def get_concise_summary(self, devices: list[dict]) -> dict:
|
||||
"""
|
||||
Generate concise summary with cache ID.
|
||||
|
||||
Returns 96% fewer tokens than full list.
|
||||
"""
|
||||
booted = [d for d in devices if d["state"] == "Booted"]
|
||||
available = [d for d in devices if d["is_available"]]
|
||||
iphone = [d for d in available if "iPhone" in d["name"]]
|
||||
|
||||
# Cache full list for later retrieval
|
||||
cache_id = self.cache.save(
|
||||
{
|
||||
"devices": devices,
|
||||
"timestamp": __import__("datetime").datetime.now().isoformat(),
|
||||
},
|
||||
"simulator-list",
|
||||
)
|
||||
|
||||
return {
|
||||
"cache_id": cache_id,
|
||||
"summary": {
|
||||
"total_devices": len(devices),
|
||||
"available_devices": len(available),
|
||||
"booted_devices": len(booted),
|
||||
},
|
||||
"quick_access": {
|
||||
"booted": booted[:3] if booted else [],
|
||||
"recommended_iphone": iphone[:3] if iphone else [],
|
||||
},
|
||||
}
|
||||
|
||||
def get_full_list(
|
||||
self,
|
||||
cache_id: str,
|
||||
device_type: str | None = None,
|
||||
runtime: str | None = None,
|
||||
) -> list[dict] | None:
|
||||
"""
|
||||
Retrieve full simulator list from cache.
|
||||
|
||||
Args:
|
||||
cache_id: Cache ID from concise summary
|
||||
device_type: Filter by type (iPhone, iPad, etc.)
|
||||
runtime: Filter by iOS version
|
||||
|
||||
Returns:
|
||||
List of devices matching filters
|
||||
"""
|
||||
data = self.cache.get(cache_id)
|
||||
if not data:
|
||||
return None
|
||||
|
||||
devices = data.get("devices", [])
|
||||
|
||||
# Apply filters
|
||||
if device_type:
|
||||
devices = [d for d in devices if device_type in d["name"]]
|
||||
if runtime:
|
||||
devices = [d for d in devices if runtime.lower() in d["runtime"].lower()]
|
||||
|
||||
return devices
|
||||
|
||||
def suggest_simulators(self, limit: int = 4) -> list[dict]:
|
||||
"""
|
||||
Get simulator recommendations.
|
||||
|
||||
Returns:
|
||||
List of recommended simulators (best candidates for building)
|
||||
"""
|
||||
all_sims = self.list_simulators()
|
||||
devices = self.parse_devices(all_sims)
|
||||
|
||||
# Score devices for recommendations
|
||||
scored = []
|
||||
for device in devices:
|
||||
score = 0
|
||||
|
||||
# Prefer booted
|
||||
if device["state"] == "Booted":
|
||||
score += 10
|
||||
# Prefer available
|
||||
if device["is_available"]:
|
||||
score += 5
|
||||
# Prefer recent iOS versions
|
||||
ios_version = device["runtime"]
|
||||
if "18" in ios_version:
|
||||
score += 3
|
||||
elif "17" in ios_version:
|
||||
score += 2
|
||||
# Prefer iPhones over other types
|
||||
if "iPhone" in device["name"]:
|
||||
score += 1
|
||||
|
||||
scored.append({"device": device, "score": score})
|
||||
|
||||
# Sort by score and return top N
|
||||
scored.sort(key=lambda x: x["score"], reverse=True)
|
||||
return [s["device"] for s in scored[:limit]]
|
||||
|
||||
|
||||
def format_device(device: dict) -> str:
|
||||
"""Format device for display."""
|
||||
state_icon = "✓" if device["state"] == "Booted" else " "
|
||||
avail_icon = "●" if device["is_available"] else "○"
|
||||
name = device["name"]
|
||||
runtime = device["runtime"]
|
||||
udid_short = device["udid"][:8] + "..."
|
||||
return f"{state_icon} {avail_icon} {name} ({runtime}) [{udid_short}]"
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
parser = argparse.ArgumentParser(description="List iOS simulators with progressive disclosure")
|
||||
parser.add_argument(
|
||||
"--get-details",
|
||||
metavar="CACHE_ID",
|
||||
help="Get full details for cached simulator list",
|
||||
)
|
||||
parser.add_argument("--suggest", action="store_true", help="Get simulator recommendations")
|
||||
parser.add_argument(
|
||||
"--device-type",
|
||||
help="Filter by device type (iPhone, iPad, Apple Watch, etc.)",
|
||||
)
|
||||
parser.add_argument("--runtime", help="Filter by iOS version (e.g., iOS-18, iOS-17)")
|
||||
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
lister = SimulatorLister()
|
||||
|
||||
# Get full list with details
|
||||
if args.get_details:
|
||||
devices = lister.get_full_list(
|
||||
args.get_details, device_type=args.device_type, runtime=args.runtime
|
||||
)
|
||||
|
||||
if devices is None:
|
||||
print(f"Error: Cache ID not found or expired: {args.get_details}")
|
||||
sys.exit(1)
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(devices, indent=2))
|
||||
else:
|
||||
print(f"Simulators ({len(devices)}):\n")
|
||||
for device in devices:
|
||||
print(f" {format_device(device)}")
|
||||
|
||||
# Get recommendations
|
||||
elif args.suggest:
|
||||
suggestions = lister.suggest_simulators()
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(suggestions, indent=2))
|
||||
else:
|
||||
print("Recommended Simulators:\n")
|
||||
for i, device in enumerate(suggestions, 1):
|
||||
print(f"{i}. {format_device(device)}")
|
||||
|
||||
# Default: concise summary
|
||||
else:
|
||||
all_sims = lister.list_simulators()
|
||||
devices = lister.parse_devices(all_sims)
|
||||
summary = lister.get_concise_summary(devices)
|
||||
|
||||
if args.json:
|
||||
print(json.dumps(summary, indent=2))
|
||||
else:
|
||||
# Human-readable concise output
|
||||
cache_id = summary["cache_id"]
|
||||
s = summary["summary"]
|
||||
q = summary["quick_access"]
|
||||
|
||||
print(f"Simulator Summary [{cache_id}]")
|
||||
print(f"├─ Total: {s['total_devices']} devices")
|
||||
print(f"├─ Available: {s['available_devices']}")
|
||||
print(f"└─ Booted: {s['booted_devices']}")
|
||||
|
||||
if q["booted"]:
|
||||
print()
|
||||
for device in q["booted"]:
|
||||
print(f" {format_device(device)}")
|
||||
|
||||
print()
|
||||
print(f"Use --get-details {cache_id} for full list")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user