@leejungkiin/awkit 1.7.1 → 1.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/awk.js +576 -84
- package/core/CLAUDE.md +1 -1
- package/core/GEMINI.md +148 -167
- package/core/GEMINI.md.bak +149 -116
- package/core/skill-runtime-manifest.json +3 -0
- package/docs/Claude Fable 5.md +3826 -0
- package/docs/android_kotlin_system_instruction.md +210 -0
- package/docs/brainstorm_ponytail_integration.md +146 -0
- package/docs/brainstorm_smart_setup.md +113 -0
- package/docs/deep-research-report (1).md +293 -0
- package/docs/history/GEMINI.v1.md +135 -0
- package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
- package/docs/history/implementation_plan.v1.md +58 -0
- package/package.json +4 -1
- package/scripts/artifact-storage.js +130 -0
- package/scripts/automation-gate.js +35 -2
- package/scripts/claude-plan.js +76 -0
- package/scripts/dependency-manager.js +210 -0
- package/scripts/exec-rtk.js +11 -5
- package/scripts/i18n-helper.js +381 -0
- package/scripts/multi-model-pipeline.js +144 -0
- package/skill-packs/mobile-ios/pack.json +4 -2
- package/skill-packs/reverse-engineering/pack.json +1 -0
- package/skills/CATALOG.md +20 -0
- package/skills/GEMINI.md +9 -1
- package/skills/TRIGGER_INDEX.md +10 -0
- package/skills/ai-music/SKILL.md +275 -0
- package/skills/android-re-analyzer/SKILL.md +238 -0
- package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
- package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
- package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
- package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
- package/skills/android-re-analyzer/references/setup-guide.md +221 -0
- package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
- package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
- package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
- package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
- package/skills/animal-island-ui-style/SKILL.md +1450 -0
- package/skills/app-store-review-agent/SKILL.md +164 -0
- package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
- package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
- package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
- package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
- package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
- package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
- package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
- package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
- package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
- package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
- package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
- package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
- package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
- package/skills/awf-ponytail/SKILL.md +91 -0
- package/skills/awf-ponytail-review/SKILL.md +67 -0
- package/skills/awf-session-restore/SKILL.md +3 -3
- package/skills/brainstorm-agent/SKILL.md +11 -2
- package/skills/brainstorm-agent/templates/brief-template.md +8 -0
- package/skills/claude-planner/SKILL.md +47 -0
- package/skills/code-review/SKILL.md +87 -0
- package/skills/expo-game-development/SKILL.md +163 -0
- package/skills/flutter/LICENSE.txt +202 -0
- package/skills/flutter/SKILL.md +127 -0
- package/skills/flutter-project-creater/LICENSE.txt +202 -0
- package/skills/flutter-project-creater/SKILL.md +106 -0
- package/skills/game-developer/SKILL.md +163 -0
- package/skills/game-developer/references/ecs-patterns.md +501 -0
- package/skills/game-developer/references/multiplayer-networking.md +475 -0
- package/skills/game-developer/references/performance-optimization.md +422 -0
- package/skills/game-developer/references/unity-patterns.md +271 -0
- package/skills/game-developer/references/unreal-cpp.md +352 -0
- package/skills/generate-gui-assets/SKILL.md +305 -0
- package/skills/generate-gui-assets/agents/openai.yaml +4 -0
- package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
- package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
- package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
- package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
- package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
- package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
- package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
- package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
- package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
- package/skills/godot-game-development/SKILL.md +142 -0
- package/skills/hatch-pet/LICENSE.txt +201 -0
- package/skills/hatch-pet/SKILL.md +420 -0
- package/skills/hatch-pet/agents/openai.yaml +4 -0
- package/skills/hatch-pet/references/animation-rows.md +29 -0
- package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
- package/skills/hatch-pet/references/qa-rubric.md +60 -0
- package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
- package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
- package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
- package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
- package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
- package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
- package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
- package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
- package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
- package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
- package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
- package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
- package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
- package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
- package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
- package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
- package/skills/i18n-orchestrator/SKILL.md +37 -0
- package/skills/ios-simulator-skill/SKILL.md +390 -0
- package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
- package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
- package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
- package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
- package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
- package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
- package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
- package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
- package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
- package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
- package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
- package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
- package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
- package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
- package/skills/ios-simulator-skill/scripts/container.py +668 -0
- package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
- package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
- package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
- package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
- package/skills/ios-simulator-skill/scripts/location.py +467 -0
- package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
- package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
- package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
- package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
- package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
- package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
- package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
- package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
- package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
- package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
- package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
- package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
- package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
- package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
- package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
- package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
- package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
- package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
- package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
- package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
- package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
- package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
- package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
- package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
- package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
- package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
- package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
- package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
- package/skills/orchestrator/SKILL.md +0 -20
- package/skills/persistent-storage/SKILL.md +55 -0
- package/skills/short-maker/SKILL.md +23 -0
- package/skills/short-maker/scripts/effects.js +56 -0
- package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
- package/skills/short-maker/scripts/videomix.js +601 -0
- package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
- package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
- package/skills/smali-to-kotlin/SKILL.md +128 -0
- package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
- package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
- package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
- package/skills/smali-to-kotlin/library-patterns.md +189 -0
- package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
- package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
- package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
- package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
- package/skills/smali-to-kotlin/phase-3-build.md +248 -0
- package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
- package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
- package/skills/smali-to-kotlin/templates/app-map.md +101 -0
- package/skills/smali-to-kotlin/templates/architecture.md +142 -0
- package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
- package/skills/spec-gate/SKILL.md +6 -2
- package/skills/symphony-enforcer/SKILL.md +8 -0
- package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
- package/skills/symphony-enforcer/examples/three-phase.md +16 -0
- package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
- package/skills/unity-game-development/SKILL.md +231 -0
- package/skills/video-edit/SKILL.md +36 -0
- package/skills/video-edit/scripts/video_edit.py +324 -0
- package/templates/project-identity/android.json +2 -2
- package/templates/project-identity/backend-nestjs.json +2 -2
- package/templates/project-identity/expo.json +2 -2
- package/templates/project-identity/ios.json +2 -2
- package/templates/project-identity/web-nextjs.json +2 -2
- package/templates/setup-mapping.json +48 -0
- package/templates/specs/design-template.md +161 -71
- package/templates/specs/requirements-template.md +65 -133
- package/templates/specs/task-spec-template.xml +3 -0
- package/workflows/_uncategorized/critic.md +40 -0
- package/workflows/_uncategorized/git-rebase-flow.md +81 -0
- package/workflows/_uncategorized/image-gen.md +118 -0
- package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
- package/workflows/_uncategorized/pixel-gen.md +86 -0
- package/workflows/_uncategorized/pixel-setup.md +90 -0
- package/workflows/_uncategorized/ponytail-review.md +59 -0
- package/workflows/_uncategorized/reverse-android-build.md +222 -0
- package/workflows/_uncategorized/reverse-android-design.md +139 -0
- package/workflows/_uncategorized/reverse-android-discover.md +150 -0
- package/workflows/_uncategorized/reverse-android-scan.md +158 -0
- package/workflows/_uncategorized/reverse-android.md +143 -0
- package/workflows/_uncategorized/reverse-ios-build.md +240 -0
- package/workflows/_uncategorized/reverse-ios-design.md +112 -0
- package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
- package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
- package/workflows/_uncategorized/reverse-ios.md +152 -0
- package/workflows/_uncategorized/safety-router.md +34 -0
- package/workflows/_uncategorized/teach.md +89 -0
- package/workflows/_uncategorized/verify-ui.md +53 -0
- package/workflows/_uncategorized/visualize-screenshots.md +34 -0
- package/workflows/ads/ads-analyst.md +201 -0
- package/workflows/ads/ads-audit.md +106 -0
- package/workflows/ads/ads-optimize.md +97 -0
- package/workflows/ads/ads-targeting.md +241 -0
- package/workflows/ads/adsExpert.md +160 -0
- package/workflows/ads/smali-ads-config.md +400 -0
- package/workflows/ads/smali-ads-flow.md +331 -0
- package/workflows/ads/smali-ads-interstitial.md +377 -0
- package/workflows/ads/smali-ads-native.md +382 -0
- package/workflows/context/teach.md +89 -0
- package/workflows/gitnexus.md +8 -8
- package/workflows/lifecycle/brainstorm.md +43 -0
- package/workflows/lifecycle/code.md +5 -0
- package/workflows/lifecycle/init.md +23 -5
- package/workflows/lifecycle/multi-model-pipeline.md +60 -0
- package/workflows/quality/ponytail-review.md +59 -0
- package/workflows/roles/critic.md +40 -0
- package/workflows/roles/safety-router.md +34 -0
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Shared device and simulator utilities.
|
|
4
|
+
|
|
5
|
+
Common patterns for interacting with simulators via xcrun simctl and IDB.
|
|
6
|
+
Standardizes command building and device targeting to prevent errors.
|
|
7
|
+
|
|
8
|
+
Follows Jackson's Law - only extracts genuinely reused patterns.
|
|
9
|
+
|
|
10
|
+
Used by:
|
|
11
|
+
- app_launcher.py (8 call sites) - App lifecycle commands
|
|
12
|
+
- Multiple scripts (15+ locations) - IDB command building
|
|
13
|
+
- navigator.py, gesture.py - Coordinate transformation
|
|
14
|
+
- test_recorder.py, app_state_capture.py - Auto-UDID detection
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import re
|
|
19
|
+
import subprocess
|
|
20
|
+
import sys
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def build_simctl_command(
|
|
24
|
+
operation: str,
|
|
25
|
+
udid: str | None = None,
|
|
26
|
+
*args,
|
|
27
|
+
) -> list[str]:
|
|
28
|
+
"""
|
|
29
|
+
Build xcrun simctl command with proper device handling.
|
|
30
|
+
|
|
31
|
+
Standardizes command building to prevent device targeting bugs.
|
|
32
|
+
Automatically uses "booted" if no UDID provided.
|
|
33
|
+
|
|
34
|
+
Used by:
|
|
35
|
+
- app_launcher.py: launch, terminate, install, uninstall, openurl, listapps, spawn
|
|
36
|
+
- Multiple scripts: generic simctl operations
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
operation: simctl operation (launch, terminate, install, etc.)
|
|
40
|
+
udid: Device UDID (uses 'booted' if None)
|
|
41
|
+
*args: Additional command arguments
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Complete command list ready for subprocess.run()
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
# Launch app on booted simulator
|
|
48
|
+
cmd = build_simctl_command("launch", None, "com.app.bundle")
|
|
49
|
+
# Returns: ["xcrun", "simctl", "launch", "booted", "com.app.bundle"]
|
|
50
|
+
|
|
51
|
+
# Launch on specific device
|
|
52
|
+
cmd = build_simctl_command("launch", "ABC123", "com.app.bundle")
|
|
53
|
+
# Returns: ["xcrun", "simctl", "launch", "ABC123", "com.app.bundle"]
|
|
54
|
+
|
|
55
|
+
# Install app on specific device
|
|
56
|
+
cmd = build_simctl_command("install", "ABC123", "/path/to/app.app")
|
|
57
|
+
# Returns: ["xcrun", "simctl", "install", "ABC123", "/path/to/app.app"]
|
|
58
|
+
"""
|
|
59
|
+
cmd = ["xcrun", "simctl", operation]
|
|
60
|
+
|
|
61
|
+
# Add device (booted or specific UDID)
|
|
62
|
+
cmd.append(udid if udid else "booted")
|
|
63
|
+
|
|
64
|
+
# Add remaining arguments
|
|
65
|
+
cmd.extend(str(arg) for arg in args)
|
|
66
|
+
|
|
67
|
+
return cmd
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def build_idb_command(
|
|
71
|
+
operation: str,
|
|
72
|
+
udid: str | None = None,
|
|
73
|
+
*args,
|
|
74
|
+
) -> list[str]:
|
|
75
|
+
"""
|
|
76
|
+
Build IDB command with proper device targeting.
|
|
77
|
+
|
|
78
|
+
Standardizes IDB command building across all scripts using IDB.
|
|
79
|
+
Handles device UDID consistently.
|
|
80
|
+
|
|
81
|
+
Used by:
|
|
82
|
+
- navigator.py: ui tap, ui text, ui describe-all
|
|
83
|
+
- gesture.py: ui swipe, ui tap
|
|
84
|
+
- keyboard.py: ui key, ui text, ui tap
|
|
85
|
+
- And more: 15+ locations
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
operation: IDB operation path (e.g., "ui tap", "ui text", "ui describe-all")
|
|
89
|
+
udid: Device UDID (omits --udid flag if None, IDB uses booted by default)
|
|
90
|
+
*args: Additional command arguments
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Complete command list ready for subprocess.run()
|
|
94
|
+
|
|
95
|
+
Examples:
|
|
96
|
+
# Tap on booted simulator
|
|
97
|
+
cmd = build_idb_command("ui tap", None, "200", "400")
|
|
98
|
+
# Returns: ["idb", "ui", "tap", "200", "400"]
|
|
99
|
+
|
|
100
|
+
# Tap on specific device
|
|
101
|
+
cmd = build_idb_command("ui tap", "ABC123", "200", "400")
|
|
102
|
+
# Returns: ["idb", "ui", "tap", "200", "400", "--udid", "ABC123"]
|
|
103
|
+
|
|
104
|
+
# Get accessibility tree
|
|
105
|
+
cmd = build_idb_command("ui describe-all", "ABC123", "--json", "--nested")
|
|
106
|
+
# Returns: ["idb", "ui", "describe-all", "--json", "--nested", "--udid", "ABC123"]
|
|
107
|
+
|
|
108
|
+
# Enter text
|
|
109
|
+
cmd = build_idb_command("ui text", None, "hello world")
|
|
110
|
+
# Returns: ["idb", "ui", "text", "hello world"]
|
|
111
|
+
"""
|
|
112
|
+
# Split operation into parts (e.g., "ui tap" -> ["ui", "tap"])
|
|
113
|
+
cmd = ["idb"] + operation.split()
|
|
114
|
+
|
|
115
|
+
# Add arguments
|
|
116
|
+
cmd.extend(str(arg) for arg in args)
|
|
117
|
+
|
|
118
|
+
# Add device targeting if specified (optional for IDB, uses booted by default)
|
|
119
|
+
if udid:
|
|
120
|
+
cmd.extend(["--udid", udid])
|
|
121
|
+
|
|
122
|
+
return cmd
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_booted_device_udids() -> list[str]:
|
|
126
|
+
"""
|
|
127
|
+
List the UDIDs of every currently booted simulator.
|
|
128
|
+
|
|
129
|
+
Queries `xcrun simctl list devices booted` and extracts each UDID in the
|
|
130
|
+
order reported.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
UDIDs of all booted simulators, or an empty list if none are booted
|
|
134
|
+
(or the query fails).
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
udids = get_booted_device_udids()
|
|
138
|
+
# ["ABC123-...", "DEF456-..."] when two simulators are running
|
|
139
|
+
"""
|
|
140
|
+
try:
|
|
141
|
+
result = subprocess.run(
|
|
142
|
+
["xcrun", "simctl", "list", "devices", "booted"],
|
|
143
|
+
capture_output=True,
|
|
144
|
+
text=True,
|
|
145
|
+
check=True,
|
|
146
|
+
)
|
|
147
|
+
except subprocess.CalledProcessError:
|
|
148
|
+
return []
|
|
149
|
+
|
|
150
|
+
# Format: " iPhone 16 Pro (ABC123-DEF456) (Booted)"
|
|
151
|
+
udids: list[str] = []
|
|
152
|
+
for line in result.stdout.split("\n"):
|
|
153
|
+
match = re.search(r"\(([A-F0-9\-]{36})\)", line)
|
|
154
|
+
if match:
|
|
155
|
+
udids.append(match.group(1))
|
|
156
|
+
return udids
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def get_booted_device_udid() -> str | None:
|
|
160
|
+
"""
|
|
161
|
+
Auto-detect a booted simulator UDID.
|
|
162
|
+
|
|
163
|
+
Returns the first booted simulator. When more than one simulator is booted
|
|
164
|
+
the choice is ambiguous — gesture/tap commands can silently target a device
|
|
165
|
+
other than the one you are watching (idb reports success on the wrong
|
|
166
|
+
device, so nothing appears to happen). In that case a warning is printed to
|
|
167
|
+
stderr naming the selected device and advising an explicit ``--udid``.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
UDID of a booted simulator, or None if no simulator is booted.
|
|
171
|
+
|
|
172
|
+
Example:
|
|
173
|
+
udid = get_booted_device_udid()
|
|
174
|
+
if udid:
|
|
175
|
+
print(f"Booted simulator: {udid}")
|
|
176
|
+
else:
|
|
177
|
+
print("No simulator is currently booted")
|
|
178
|
+
"""
|
|
179
|
+
udids = get_booted_device_udids()
|
|
180
|
+
if not udids:
|
|
181
|
+
return None
|
|
182
|
+
if len(udids) > 1:
|
|
183
|
+
print(
|
|
184
|
+
f"Warning: {len(udids)} booted simulators detected ({', '.join(udids)}). "
|
|
185
|
+
f"Auto-selecting {udids[0]} — pass --udid to target a specific device.",
|
|
186
|
+
file=sys.stderr,
|
|
187
|
+
)
|
|
188
|
+
return udids[0]
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def resolve_udid(udid_arg: str | None) -> str:
|
|
192
|
+
"""
|
|
193
|
+
Resolve device UDID with auto-detection fallback.
|
|
194
|
+
|
|
195
|
+
If udid_arg is provided, returns it immediately.
|
|
196
|
+
If None, attempts to auto-detect booted simulator.
|
|
197
|
+
Raises error if neither is available.
|
|
198
|
+
|
|
199
|
+
Args:
|
|
200
|
+
udid_arg: Explicit UDID from command line, or None
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
Valid UDID string
|
|
204
|
+
|
|
205
|
+
Raises:
|
|
206
|
+
RuntimeError: If no UDID provided and no booted simulator found
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
try:
|
|
210
|
+
udid = resolve_udid(args.udid) # args.udid might be None
|
|
211
|
+
print(f"Using device: {udid}")
|
|
212
|
+
except RuntimeError as e:
|
|
213
|
+
print(f"Error: {e}")
|
|
214
|
+
sys.exit(1)
|
|
215
|
+
"""
|
|
216
|
+
if udid_arg:
|
|
217
|
+
return udid_arg
|
|
218
|
+
|
|
219
|
+
booted_udid = get_booted_device_udid()
|
|
220
|
+
if booted_udid:
|
|
221
|
+
return booted_udid
|
|
222
|
+
|
|
223
|
+
raise RuntimeError(
|
|
224
|
+
"No device UDID provided and no simulator is currently booted.\n"
|
|
225
|
+
"Boot a simulator or provide --udid explicitly:\n"
|
|
226
|
+
" xcrun simctl boot <device-name>\n"
|
|
227
|
+
" python scripts/script_name.py --udid <device-udid>"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def get_device_screen_size(udid: str) -> tuple[int, int]:
|
|
232
|
+
"""
|
|
233
|
+
Get actual screen dimensions for device via accessibility tree.
|
|
234
|
+
|
|
235
|
+
Queries IDB accessibility tree to determine actual device resolution.
|
|
236
|
+
Falls back to iPhone 14 defaults (390x844) if detection fails.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
udid: Device UDID
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
Tuple of (width, height) in pixels
|
|
243
|
+
|
|
244
|
+
Example:
|
|
245
|
+
width, height = get_device_screen_size("ABC123")
|
|
246
|
+
print(f"Device screen: {width}x{height}")
|
|
247
|
+
"""
|
|
248
|
+
try:
|
|
249
|
+
cmd = build_idb_command("ui describe-all", udid, "--json")
|
|
250
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
251
|
+
|
|
252
|
+
# Parse JSON response
|
|
253
|
+
data = json.loads(result.stdout)
|
|
254
|
+
tree = data[0] if isinstance(data, list) and len(data) > 0 else data
|
|
255
|
+
|
|
256
|
+
# Get frame size from root element
|
|
257
|
+
if tree and "frame" in tree:
|
|
258
|
+
frame = tree["frame"]
|
|
259
|
+
width = int(frame.get("width", 390))
|
|
260
|
+
height = int(frame.get("height", 844))
|
|
261
|
+
return (width, height)
|
|
262
|
+
|
|
263
|
+
# Fallback
|
|
264
|
+
return (390, 844)
|
|
265
|
+
except Exception:
|
|
266
|
+
# Graceful fallback to iPhone 14 Pro defaults
|
|
267
|
+
return (390, 844)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def resolve_device_identifier(identifier: str) -> str:
|
|
271
|
+
"""
|
|
272
|
+
Resolve device name or partial UDID to full UDID.
|
|
273
|
+
|
|
274
|
+
Supports multiple identifier formats:
|
|
275
|
+
- Full UDID: "ABC-123-DEF456..." (36 character UUID)
|
|
276
|
+
- Device name: "iPhone 16 Pro" (matches full name)
|
|
277
|
+
- Partial match: "iPhone 16" (matches first device containing this string)
|
|
278
|
+
- Special: "booted" (resolves to currently booted device)
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
identifier: Device UDID, name, or special value "booted"
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Full device UDID
|
|
285
|
+
|
|
286
|
+
Raises:
|
|
287
|
+
RuntimeError: If identifier cannot be resolved
|
|
288
|
+
|
|
289
|
+
Example:
|
|
290
|
+
udid = resolve_device_identifier("iPhone 16 Pro")
|
|
291
|
+
# Returns: "ABC123DEF456..."
|
|
292
|
+
|
|
293
|
+
udid = resolve_device_identifier("booted")
|
|
294
|
+
# Returns UDID of booted simulator
|
|
295
|
+
"""
|
|
296
|
+
# Handle "booted" special case
|
|
297
|
+
if identifier.lower() == "booted":
|
|
298
|
+
booted = get_booted_device_udid()
|
|
299
|
+
if booted:
|
|
300
|
+
return booted
|
|
301
|
+
raise RuntimeError(
|
|
302
|
+
"No simulator is currently booted. "
|
|
303
|
+
"Boot a simulator first: xcrun simctl boot <device-udid>"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
# Check if already a full UDID (36 character UUID format)
|
|
307
|
+
if re.match(r"^[A-F0-9\-]{36}$", identifier, re.IGNORECASE):
|
|
308
|
+
return identifier.upper()
|
|
309
|
+
|
|
310
|
+
# Try to match by device name
|
|
311
|
+
simulators = list_simulators(state=None)
|
|
312
|
+
exact_matches = [s for s in simulators if s["name"].lower() == identifier.lower()]
|
|
313
|
+
if exact_matches:
|
|
314
|
+
return exact_matches[0]["udid"]
|
|
315
|
+
|
|
316
|
+
# Try partial match
|
|
317
|
+
partial_matches = [s for s in simulators if identifier.lower() in s["name"].lower()]
|
|
318
|
+
if partial_matches:
|
|
319
|
+
return partial_matches[0]["udid"]
|
|
320
|
+
|
|
321
|
+
# No match found
|
|
322
|
+
raise RuntimeError(
|
|
323
|
+
f"Device '{identifier}' not found. "
|
|
324
|
+
f"Use 'xcrun simctl list devices' to see available simulators."
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def list_simulators(state: str | None = None) -> list[dict]:
|
|
329
|
+
"""
|
|
330
|
+
List iOS simulators with optional state filtering.
|
|
331
|
+
|
|
332
|
+
Queries xcrun simctl and returns structured list of simulators.
|
|
333
|
+
Optionally filters by state (available, booted, all).
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
state: Optional filter - "available", "booted", or None for all
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
List of simulator dicts with keys:
|
|
340
|
+
- "name": Device name (e.g., "iPhone 16 Pro")
|
|
341
|
+
- "udid": Device UDID (36 char UUID)
|
|
342
|
+
- "state": Device state ("Booted", "Shutdown", "Unavailable")
|
|
343
|
+
- "runtime": iOS version (e.g., "iOS 18.0", "unavailable")
|
|
344
|
+
- "type": Device type ("iPhone", "iPad", "Apple Watch", etc.)
|
|
345
|
+
|
|
346
|
+
Example:
|
|
347
|
+
# List all simulators
|
|
348
|
+
all_sims = list_simulators()
|
|
349
|
+
print(f"Total simulators: {len(all_sims)}")
|
|
350
|
+
|
|
351
|
+
# List only available simulators
|
|
352
|
+
available = list_simulators(state="available")
|
|
353
|
+
for sim in available:
|
|
354
|
+
print(f"{sim['name']} ({sim['state']}) - {sim['udid']}")
|
|
355
|
+
|
|
356
|
+
# List only booted simulators
|
|
357
|
+
booted = list_simulators(state="booted")
|
|
358
|
+
for sim in booted:
|
|
359
|
+
print(f"Booted: {sim['name']}")
|
|
360
|
+
"""
|
|
361
|
+
try:
|
|
362
|
+
# Query simctl for device list
|
|
363
|
+
cmd = ["xcrun", "simctl", "list", "devices", "-j"]
|
|
364
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
365
|
+
|
|
366
|
+
data = json.loads(result.stdout)
|
|
367
|
+
simulators = []
|
|
368
|
+
|
|
369
|
+
# Parse JSON response
|
|
370
|
+
# Format: {"devices": {"iOS 18.0": [{...}, {...}], "iOS 17.0": [...], ...}}
|
|
371
|
+
for ios_version, devices in data.get("devices", {}).items():
|
|
372
|
+
for device in devices:
|
|
373
|
+
sim = {
|
|
374
|
+
"name": device.get("name", "Unknown"),
|
|
375
|
+
"udid": device.get("udid", ""),
|
|
376
|
+
"state": device.get("state", "Unknown"),
|
|
377
|
+
"runtime": ios_version,
|
|
378
|
+
"type": _extract_device_type(device.get("name", "")),
|
|
379
|
+
}
|
|
380
|
+
simulators.append(sim)
|
|
381
|
+
|
|
382
|
+
# Apply state filtering
|
|
383
|
+
if state == "booted":
|
|
384
|
+
return [s for s in simulators if s["state"] == "Booted"]
|
|
385
|
+
if state == "available":
|
|
386
|
+
return [s for s in simulators if s["state"] == "Shutdown"] # Available to boot
|
|
387
|
+
if state is None:
|
|
388
|
+
return simulators
|
|
389
|
+
return [s for s in simulators if s["state"].lower() == state.lower()]
|
|
390
|
+
|
|
391
|
+
except (subprocess.CalledProcessError, json.JSONDecodeError, KeyError) as e:
|
|
392
|
+
raise RuntimeError(f"Failed to list simulators: {e}") from e
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def _extract_device_type(device_name: str) -> str:
|
|
396
|
+
"""
|
|
397
|
+
Extract device type from device name.
|
|
398
|
+
|
|
399
|
+
Parses device name to determine type (iPhone, iPad, Watch, etc.).
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
device_name: Full device name (e.g., "iPhone 16 Pro")
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
Device type string
|
|
406
|
+
|
|
407
|
+
Example:
|
|
408
|
+
_extract_device_type("iPhone 16 Pro") # Returns "iPhone"
|
|
409
|
+
_extract_device_type("iPad Air") # Returns "iPad"
|
|
410
|
+
_extract_device_type("Apple Watch Series 9") # Returns "Watch"
|
|
411
|
+
"""
|
|
412
|
+
if "iPhone" in device_name:
|
|
413
|
+
return "iPhone"
|
|
414
|
+
if "iPad" in device_name:
|
|
415
|
+
return "iPad"
|
|
416
|
+
if "Watch" in device_name or "Apple Watch" in device_name:
|
|
417
|
+
return "Watch"
|
|
418
|
+
if "TV" in device_name or "Apple TV" in device_name:
|
|
419
|
+
return "TV"
|
|
420
|
+
return "Unknown"
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def transform_screenshot_coords(
|
|
424
|
+
x: float,
|
|
425
|
+
y: float,
|
|
426
|
+
screenshot_width: int,
|
|
427
|
+
screenshot_height: int,
|
|
428
|
+
device_width: int,
|
|
429
|
+
device_height: int,
|
|
430
|
+
) -> tuple[int, int]:
|
|
431
|
+
"""
|
|
432
|
+
Transform screenshot coordinates to device coordinates.
|
|
433
|
+
|
|
434
|
+
Handles the case where a screenshot was downscaled (e.g., to 'half' size)
|
|
435
|
+
and needs to be transformed back to actual device pixel coordinates
|
|
436
|
+
for accurate tapping.
|
|
437
|
+
|
|
438
|
+
The transformation is linear:
|
|
439
|
+
device_x = (screenshot_x / screenshot_width) * device_width
|
|
440
|
+
device_y = (screenshot_y / screenshot_height) * device_height
|
|
441
|
+
|
|
442
|
+
Args:
|
|
443
|
+
x, y: Coordinates in the screenshot
|
|
444
|
+
screenshot_width, screenshot_height: Screenshot dimensions (e.g., 195, 422)
|
|
445
|
+
device_width, device_height: Actual device dimensions (e.g., 390, 844)
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Tuple of (device_x, device_y) in device pixels
|
|
449
|
+
|
|
450
|
+
Example:
|
|
451
|
+
# Screenshot taken at 'half' size: 195x422 (from 390x844 device)
|
|
452
|
+
device_x, device_y = transform_screenshot_coords(
|
|
453
|
+
100, 200, # Tap point in screenshot
|
|
454
|
+
195, 422, # Screenshot dimensions
|
|
455
|
+
390, 844 # Device dimensions
|
|
456
|
+
)
|
|
457
|
+
print(f"Tap at device coords: ({device_x}, {device_y})")
|
|
458
|
+
# Output: Tap at device coords: (200, 400)
|
|
459
|
+
"""
|
|
460
|
+
device_x = int((x / screenshot_width) * device_width)
|
|
461
|
+
device_y = int((y / screenshot_height) * device_height)
|
|
462
|
+
return (device_x, device_y)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Env-var overrides for tunable defaults.
|
|
3
|
+
|
|
4
|
+
All overrides use the ``IOS_SIM_`` prefix. See SKILL.md → Configuration
|
|
5
|
+
for the canonical list of supported variables.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def env_int(name: str, default: int, min_value: int = 1) -> int:
|
|
13
|
+
"""Read ``name`` as an int, falling back to ``default`` on miss or parse error."""
|
|
14
|
+
raw = os.environ.get(name)
|
|
15
|
+
if raw is None or raw == "":
|
|
16
|
+
return default
|
|
17
|
+
try:
|
|
18
|
+
value = int(raw)
|
|
19
|
+
except ValueError:
|
|
20
|
+
print(f"warning: {name}={raw!r} is not an int; using default {default}", file=sys.stderr)
|
|
21
|
+
return default
|
|
22
|
+
return max(value, min_value)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def env_float(name: str, default: float, min_value: float = 0.0) -> float:
|
|
26
|
+
"""Read ``name`` as a float, falling back to ``default`` on miss or parse error."""
|
|
27
|
+
raw = os.environ.get(name)
|
|
28
|
+
if raw is None or raw == "":
|
|
29
|
+
return default
|
|
30
|
+
try:
|
|
31
|
+
value = float(raw)
|
|
32
|
+
except ValueError:
|
|
33
|
+
print(f"warning: {name}={raw!r} is not a float; using default {default}", file=sys.stderr)
|
|
34
|
+
return default
|
|
35
|
+
return max(value, min_value)
|