@leejungkiin/awkit 1.7.0 → 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.
Files changed (241) hide show
  1. package/bin/awk.js +576 -84
  2. package/core/CLAUDE.md +1 -1
  3. package/core/GEMINI.md +148 -167
  4. package/core/GEMINI.md.bak +149 -116
  5. package/core/skill-runtime-manifest.json +3 -0
  6. package/docs/Claude Fable 5.md +3826 -0
  7. package/docs/android_kotlin_system_instruction.md +210 -0
  8. package/docs/brainstorm_ponytail_integration.md +146 -0
  9. package/docs/brainstorm_smart_setup.md +113 -0
  10. package/docs/deep-research-report (1).md +293 -0
  11. package/docs/history/GEMINI.v1.md +135 -0
  12. package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
  13. package/docs/history/implementation_plan.v1.md +58 -0
  14. package/package.json +4 -1
  15. package/scripts/artifact-storage.js +130 -0
  16. package/scripts/automation-gate.js +40 -7
  17. package/scripts/claude-plan.js +76 -0
  18. package/scripts/dependency-manager.js +210 -0
  19. package/scripts/exec-rtk.js +11 -5
  20. package/scripts/i18n-helper.js +381 -0
  21. package/scripts/multi-model-pipeline.js +144 -0
  22. package/skill-packs/mobile-ios/pack.json +4 -2
  23. package/skill-packs/reverse-engineering/pack.json +1 -0
  24. package/skills/CATALOG.md +20 -0
  25. package/skills/GEMINI.md +9 -1
  26. package/skills/TRIGGER_INDEX.md +10 -0
  27. package/skills/ai-music/SKILL.md +275 -0
  28. package/skills/android-re-analyzer/SKILL.md +238 -0
  29. package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
  30. package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
  31. package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
  32. package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
  33. package/skills/android-re-analyzer/references/setup-guide.md +221 -0
  34. package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
  35. package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
  36. package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
  37. package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
  38. package/skills/animal-island-ui-style/SKILL.md +1450 -0
  39. package/skills/app-store-review-agent/SKILL.md +164 -0
  40. package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
  41. package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
  42. package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
  43. package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
  44. package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
  45. package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
  46. package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
  47. package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
  48. package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
  49. package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
  50. package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
  51. package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
  52. package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
  53. package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
  54. package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
  55. package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
  56. package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
  57. package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
  58. package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
  59. package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
  60. package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
  61. package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
  62. package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
  63. package/skills/awf-ponytail/SKILL.md +91 -0
  64. package/skills/awf-ponytail-review/SKILL.md +67 -0
  65. package/skills/awf-session-restore/SKILL.md +3 -3
  66. package/skills/brainstorm-agent/SKILL.md +11 -2
  67. package/skills/brainstorm-agent/templates/brief-template.md +8 -0
  68. package/skills/claude-planner/SKILL.md +47 -0
  69. package/skills/code-review/SKILL.md +87 -0
  70. package/skills/expo-game-development/SKILL.md +163 -0
  71. package/skills/flutter/LICENSE.txt +202 -0
  72. package/skills/flutter/SKILL.md +127 -0
  73. package/skills/flutter-project-creater/LICENSE.txt +202 -0
  74. package/skills/flutter-project-creater/SKILL.md +106 -0
  75. package/skills/game-developer/SKILL.md +163 -0
  76. package/skills/game-developer/references/ecs-patterns.md +501 -0
  77. package/skills/game-developer/references/multiplayer-networking.md +475 -0
  78. package/skills/game-developer/references/performance-optimization.md +422 -0
  79. package/skills/game-developer/references/unity-patterns.md +271 -0
  80. package/skills/game-developer/references/unreal-cpp.md +352 -0
  81. package/skills/generate-gui-assets/SKILL.md +305 -0
  82. package/skills/generate-gui-assets/agents/openai.yaml +4 -0
  83. package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
  84. package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
  85. package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
  86. package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
  87. package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
  88. package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
  89. package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
  90. package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
  91. package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
  92. package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
  93. package/skills/godot-game-development/SKILL.md +142 -0
  94. package/skills/hatch-pet/LICENSE.txt +201 -0
  95. package/skills/hatch-pet/SKILL.md +420 -0
  96. package/skills/hatch-pet/agents/openai.yaml +4 -0
  97. package/skills/hatch-pet/references/animation-rows.md +29 -0
  98. package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
  99. package/skills/hatch-pet/references/qa-rubric.md +60 -0
  100. package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
  101. package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
  102. package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
  103. package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
  104. package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
  105. package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
  106. package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
  107. package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
  108. package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
  109. package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
  110. package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
  111. package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
  112. package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
  113. package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
  114. package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
  115. package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
  116. package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
  117. package/skills/i18n-orchestrator/SKILL.md +37 -0
  118. package/skills/ios-simulator-skill/SKILL.md +390 -0
  119. package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
  120. package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
  121. package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
  122. package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
  123. package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
  124. package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
  125. package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
  126. package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
  127. package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
  128. package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
  129. package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
  130. package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
  131. package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
  132. package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
  133. package/skills/ios-simulator-skill/scripts/container.py +668 -0
  134. package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
  135. package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
  136. package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
  137. package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
  138. package/skills/ios-simulator-skill/scripts/location.py +467 -0
  139. package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
  140. package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
  141. package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
  142. package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
  143. package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
  144. package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
  145. package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
  146. package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
  147. package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
  148. package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
  149. package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
  150. package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
  151. package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
  152. package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
  153. package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
  154. package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
  155. package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
  156. package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
  157. package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
  158. package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
  159. package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
  160. package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
  161. package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
  162. package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
  163. package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
  164. package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
  165. package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
  166. package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
  167. package/skills/orchestrator/SKILL.md +0 -20
  168. package/skills/persistent-storage/SKILL.md +55 -0
  169. package/skills/short-maker/SKILL.md +23 -0
  170. package/skills/short-maker/scripts/effects.js +56 -0
  171. package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
  172. package/skills/short-maker/scripts/videomix.js +601 -0
  173. package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
  174. package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
  175. package/skills/smali-to-kotlin/SKILL.md +128 -0
  176. package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
  177. package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
  178. package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
  179. package/skills/smali-to-kotlin/library-patterns.md +189 -0
  180. package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
  181. package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
  182. package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
  183. package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
  184. package/skills/smali-to-kotlin/phase-3-build.md +248 -0
  185. package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
  186. package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
  187. package/skills/smali-to-kotlin/templates/app-map.md +101 -0
  188. package/skills/smali-to-kotlin/templates/architecture.md +142 -0
  189. package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
  190. package/skills/spec-gate/SKILL.md +6 -2
  191. package/skills/symphony-enforcer/SKILL.md +8 -0
  192. package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
  193. package/skills/symphony-enforcer/examples/three-phase.md +16 -0
  194. package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
  195. package/skills/unity-game-development/SKILL.md +231 -0
  196. package/skills/verification-gate/SKILL.md +4 -2
  197. package/skills/video-edit/SKILL.md +36 -0
  198. package/skills/video-edit/scripts/video_edit.py +324 -0
  199. package/templates/setup-mapping.json +48 -0
  200. package/templates/specs/design-template.md +161 -71
  201. package/templates/specs/requirements-template.md +65 -133
  202. package/templates/specs/task-spec-template.xml +3 -0
  203. package/workflows/_uncategorized/critic.md +40 -0
  204. package/workflows/_uncategorized/git-rebase-flow.md +81 -0
  205. package/workflows/_uncategorized/image-gen.md +118 -0
  206. package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
  207. package/workflows/_uncategorized/pixel-gen.md +86 -0
  208. package/workflows/_uncategorized/pixel-setup.md +90 -0
  209. package/workflows/_uncategorized/ponytail-review.md +59 -0
  210. package/workflows/_uncategorized/reverse-android-build.md +222 -0
  211. package/workflows/_uncategorized/reverse-android-design.md +139 -0
  212. package/workflows/_uncategorized/reverse-android-discover.md +150 -0
  213. package/workflows/_uncategorized/reverse-android-scan.md +158 -0
  214. package/workflows/_uncategorized/reverse-android.md +143 -0
  215. package/workflows/_uncategorized/reverse-ios-build.md +240 -0
  216. package/workflows/_uncategorized/reverse-ios-design.md +112 -0
  217. package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
  218. package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
  219. package/workflows/_uncategorized/reverse-ios.md +152 -0
  220. package/workflows/_uncategorized/safety-router.md +34 -0
  221. package/workflows/_uncategorized/teach.md +89 -0
  222. package/workflows/_uncategorized/verify-ui.md +53 -0
  223. package/workflows/_uncategorized/visualize-screenshots.md +34 -0
  224. package/workflows/ads/ads-analyst.md +201 -0
  225. package/workflows/ads/ads-audit.md +106 -0
  226. package/workflows/ads/ads-optimize.md +97 -0
  227. package/workflows/ads/ads-targeting.md +241 -0
  228. package/workflows/ads/adsExpert.md +160 -0
  229. package/workflows/ads/smali-ads-config.md +400 -0
  230. package/workflows/ads/smali-ads-flow.md +331 -0
  231. package/workflows/ads/smali-ads-interstitial.md +377 -0
  232. package/workflows/ads/smali-ads-native.md +382 -0
  233. package/workflows/context/teach.md +89 -0
  234. package/workflows/gitnexus.md +8 -8
  235. package/workflows/lifecycle/brainstorm.md +43 -0
  236. package/workflows/lifecycle/code.md +5 -0
  237. package/workflows/lifecycle/init.md +23 -5
  238. package/workflows/lifecycle/multi-model-pipeline.md +60 -0
  239. package/workflows/quality/ponytail-review.md +59 -0
  240. package/workflows/roles/critic.md +40 -0
  241. 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)