@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.
Files changed (245) 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 +35 -2
  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/video-edit/SKILL.md +36 -0
  197. package/skills/video-edit/scripts/video_edit.py +324 -0
  198. package/templates/project-identity/android.json +2 -2
  199. package/templates/project-identity/backend-nestjs.json +2 -2
  200. package/templates/project-identity/expo.json +2 -2
  201. package/templates/project-identity/ios.json +2 -2
  202. package/templates/project-identity/web-nextjs.json +2 -2
  203. package/templates/setup-mapping.json +48 -0
  204. package/templates/specs/design-template.md +161 -71
  205. package/templates/specs/requirements-template.md +65 -133
  206. package/templates/specs/task-spec-template.xml +3 -0
  207. package/workflows/_uncategorized/critic.md +40 -0
  208. package/workflows/_uncategorized/git-rebase-flow.md +81 -0
  209. package/workflows/_uncategorized/image-gen.md +118 -0
  210. package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
  211. package/workflows/_uncategorized/pixel-gen.md +86 -0
  212. package/workflows/_uncategorized/pixel-setup.md +90 -0
  213. package/workflows/_uncategorized/ponytail-review.md +59 -0
  214. package/workflows/_uncategorized/reverse-android-build.md +222 -0
  215. package/workflows/_uncategorized/reverse-android-design.md +139 -0
  216. package/workflows/_uncategorized/reverse-android-discover.md +150 -0
  217. package/workflows/_uncategorized/reverse-android-scan.md +158 -0
  218. package/workflows/_uncategorized/reverse-android.md +143 -0
  219. package/workflows/_uncategorized/reverse-ios-build.md +240 -0
  220. package/workflows/_uncategorized/reverse-ios-design.md +112 -0
  221. package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
  222. package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
  223. package/workflows/_uncategorized/reverse-ios.md +152 -0
  224. package/workflows/_uncategorized/safety-router.md +34 -0
  225. package/workflows/_uncategorized/teach.md +89 -0
  226. package/workflows/_uncategorized/verify-ui.md +53 -0
  227. package/workflows/_uncategorized/visualize-screenshots.md +34 -0
  228. package/workflows/ads/ads-analyst.md +201 -0
  229. package/workflows/ads/ads-audit.md +106 -0
  230. package/workflows/ads/ads-optimize.md +97 -0
  231. package/workflows/ads/ads-targeting.md +241 -0
  232. package/workflows/ads/adsExpert.md +160 -0
  233. package/workflows/ads/smali-ads-config.md +400 -0
  234. package/workflows/ads/smali-ads-flow.md +331 -0
  235. package/workflows/ads/smali-ads-interstitial.md +377 -0
  236. package/workflows/ads/smali-ads-native.md +382 -0
  237. package/workflows/context/teach.md +89 -0
  238. package/workflows/gitnexus.md +8 -8
  239. package/workflows/lifecycle/brainstorm.md +43 -0
  240. package/workflows/lifecycle/code.md +5 -0
  241. package/workflows/lifecycle/init.md +23 -5
  242. package/workflows/lifecycle/multi-model-pipeline.md +60 -0
  243. package/workflows/quality/ponytail-review.md +59 -0
  244. package/workflows/roles/critic.md +40 -0
  245. package/workflows/roles/safety-router.md +34 -0
@@ -0,0 +1,351 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Erase iOS simulators (factory reset).
4
+
5
+ This script performs a factory reset on simulators, returning them to
6
+ a clean state while preserving the device UUID. Much faster than
7
+ delete + create for CI/CD cleanup.
8
+
9
+ Key features:
10
+ - Erase by UDID or device name
11
+ - Preserve device UUID (faster than delete)
12
+ - Verify erase completion
13
+ - Batch erase operations (all, by type)
14
+ """
15
+
16
+ import argparse
17
+ import subprocess
18
+ import sys
19
+ import time
20
+ from typing import Optional
21
+
22
+ from common.device_utils import (
23
+ list_simulators,
24
+ resolve_device_identifier,
25
+ )
26
+ from common.env_config import env_float, env_int
27
+
28
+ DEFAULT_ERASE_TIMEOUT = env_int("IOS_SIM_ERASE_TIMEOUT", 90)
29
+ POLL_INTERVAL_SECONDS = env_float("IOS_SIM_POLL_INTERVAL", 0.5, min_value=0.05)
30
+
31
+
32
+ class SimulatorEraser:
33
+ """Erase iOS simulators with optional verification."""
34
+
35
+ def __init__(self, udid: str | None = None):
36
+ """Initialize with optional device UDID."""
37
+ self.udid = udid
38
+
39
+ def erase(
40
+ self, verify: bool = True, timeout_seconds: int = DEFAULT_ERASE_TIMEOUT
41
+ ) -> tuple[bool, str]:
42
+ """
43
+ Erase simulator and optionally verify completion.
44
+
45
+ Performs a factory reset, clearing all app data and settings
46
+ while preserving the simulator UUID.
47
+
48
+ Args:
49
+ verify: Wait for erase to complete and verify state
50
+ timeout_seconds: Maximum seconds to wait for verification
51
+
52
+ Returns:
53
+ (success, message) tuple
54
+ """
55
+ if not self.udid:
56
+ return False, "Error: Device UDID not specified"
57
+
58
+ start_time = time.time()
59
+
60
+ # Execute erase command
61
+ try:
62
+ cmd = ["xcrun", "simctl", "erase", self.udid]
63
+ result = subprocess.run(cmd, check=False, capture_output=True, text=True, timeout=60)
64
+
65
+ if result.returncode != 0:
66
+ error = result.stderr.strip()
67
+ return False, f"Erase failed: {error}"
68
+ except subprocess.TimeoutExpired:
69
+ return False, "Erase command timed out"
70
+ except Exception as e:
71
+ return False, f"Erase error: {e}"
72
+
73
+ # Optionally verify erase completion
74
+ if verify:
75
+ ready, verify_message = self._verify_erase(timeout_seconds)
76
+ elapsed = time.time() - start_time
77
+ if ready:
78
+ return True, (
79
+ f"Device erased: {self.udid} " f"[factory reset complete, {elapsed:.1f}s]"
80
+ )
81
+ return False, verify_message
82
+
83
+ elapsed = time.time() - start_time
84
+ return True, (
85
+ f"Device erase initiated: {self.udid} [{elapsed:.1f}s] "
86
+ "(use --verify to wait for completion)"
87
+ )
88
+
89
+ def _verify_erase(self, timeout_seconds: int = DEFAULT_ERASE_TIMEOUT) -> tuple[bool, str]:
90
+ """
91
+ Verify erase has completed.
92
+
93
+ Polls device state to confirm erase finished successfully.
94
+
95
+ Args:
96
+ timeout_seconds: Maximum seconds to wait
97
+
98
+ Returns:
99
+ (success, message) tuple
100
+ """
101
+ start_time = time.time()
102
+ poll_interval = POLL_INTERVAL_SECONDS
103
+ checks = 0
104
+
105
+ while time.time() - start_time < timeout_seconds:
106
+ try:
107
+ checks += 1
108
+ # Check if device can be queried (indicates boot status)
109
+ result = subprocess.run(
110
+ ["xcrun", "simctl", "spawn", self.udid, "launchctl", "list"],
111
+ check=False,
112
+ capture_output=True,
113
+ text=True,
114
+ timeout=5,
115
+ )
116
+
117
+ # Device responding = erase likely complete
118
+ if result.returncode == 0:
119
+ elapsed = time.time() - start_time
120
+ return True, (
121
+ f"Erase verified: {self.udid} " f"[{elapsed:.1f}s, {checks} checks]"
122
+ )
123
+ except (subprocess.TimeoutExpired, RuntimeError):
124
+ pass # Not ready yet, keep polling
125
+
126
+ time.sleep(poll_interval)
127
+
128
+ elapsed = time.time() - start_time
129
+ return False, (
130
+ f"Erase verification timeout: Device did not respond "
131
+ f"within {elapsed:.1f}s ({checks} checks)"
132
+ )
133
+
134
+ @staticmethod
135
+ def erase_all() -> tuple[int, int]:
136
+ """
137
+ Erase all simulators (factory reset).
138
+
139
+ Returns:
140
+ (succeeded, failed) tuple with counts
141
+ """
142
+ simulators = list_simulators(state=None)
143
+ succeeded = 0
144
+ failed = 0
145
+
146
+ for sim in simulators:
147
+ eraser = SimulatorEraser(udid=sim["udid"])
148
+ success, _message = eraser.erase(verify=False)
149
+ if success:
150
+ succeeded += 1
151
+ else:
152
+ failed += 1
153
+
154
+ return succeeded, failed
155
+
156
+ @staticmethod
157
+ def erase_by_type(device_type: str) -> tuple[int, int]:
158
+ """
159
+ Erase all simulators of a specific type.
160
+
161
+ Args:
162
+ device_type: Device type filter (e.g., "iPhone", "iPad")
163
+
164
+ Returns:
165
+ (succeeded, failed) tuple with counts
166
+ """
167
+ simulators = list_simulators(state=None)
168
+ succeeded = 0
169
+ failed = 0
170
+
171
+ for sim in simulators:
172
+ if device_type.lower() in sim["name"].lower():
173
+ eraser = SimulatorEraser(udid=sim["udid"])
174
+ success, _message = eraser.erase(verify=False)
175
+ if success:
176
+ succeeded += 1
177
+ else:
178
+ failed += 1
179
+
180
+ return succeeded, failed
181
+
182
+ @staticmethod
183
+ def erase_booted() -> tuple[int, int]:
184
+ """
185
+ Erase all currently booted simulators.
186
+
187
+ Returns:
188
+ (succeeded, failed) tuple with counts
189
+ """
190
+ simulators = list_simulators(state="booted")
191
+ succeeded = 0
192
+ failed = 0
193
+
194
+ for sim in simulators:
195
+ eraser = SimulatorEraser(udid=sim["udid"])
196
+ success, _message = eraser.erase(verify=False)
197
+ if success:
198
+ succeeded += 1
199
+ else:
200
+ failed += 1
201
+
202
+ return succeeded, failed
203
+
204
+
205
+ def main():
206
+ """Main entry point."""
207
+ parser = argparse.ArgumentParser(description="Erase iOS simulators (factory reset)")
208
+ parser.add_argument(
209
+ "--udid",
210
+ help="Device UDID or name (required unless using --all, --type, or --booted)",
211
+ )
212
+ parser.add_argument(
213
+ "--name",
214
+ help="Device name (alternative to --udid)",
215
+ )
216
+ parser.add_argument(
217
+ "--verify",
218
+ action="store_true",
219
+ help="Wait for erase to complete and verify state",
220
+ )
221
+ parser.add_argument(
222
+ "--timeout",
223
+ type=int,
224
+ default=DEFAULT_ERASE_TIMEOUT,
225
+ help=(
226
+ f"Timeout for --verify in seconds "
227
+ f"(default: {DEFAULT_ERASE_TIMEOUT}, override via IOS_SIM_ERASE_TIMEOUT)"
228
+ ),
229
+ )
230
+ parser.add_argument(
231
+ "--all",
232
+ action="store_true",
233
+ help="Erase all simulators (factory reset)",
234
+ )
235
+ parser.add_argument(
236
+ "--type",
237
+ help="Erase all simulators of a specific type (e.g., iPhone)",
238
+ )
239
+ parser.add_argument(
240
+ "--booted",
241
+ action="store_true",
242
+ help="Erase all currently booted simulators",
243
+ )
244
+ parser.add_argument(
245
+ "--json",
246
+ action="store_true",
247
+ help="Output as JSON",
248
+ )
249
+
250
+ args = parser.parse_args()
251
+
252
+ # Handle batch operations
253
+ if args.all:
254
+ succeeded, failed = SimulatorEraser.erase_all()
255
+ if args.json:
256
+ import json
257
+
258
+ print(
259
+ json.dumps(
260
+ {
261
+ "action": "erase_all",
262
+ "succeeded": succeeded,
263
+ "failed": failed,
264
+ "total": succeeded + failed,
265
+ }
266
+ )
267
+ )
268
+ else:
269
+ total = succeeded + failed
270
+ print(f"Erase summary: {succeeded}/{total} succeeded, " f"{failed} failed")
271
+ sys.exit(0 if failed == 0 else 1)
272
+
273
+ if args.type:
274
+ succeeded, failed = SimulatorEraser.erase_by_type(args.type)
275
+ if args.json:
276
+ import json
277
+
278
+ print(
279
+ json.dumps(
280
+ {
281
+ "action": "erase_by_type",
282
+ "type": args.type,
283
+ "succeeded": succeeded,
284
+ "failed": failed,
285
+ "total": succeeded + failed,
286
+ }
287
+ )
288
+ )
289
+ else:
290
+ total = succeeded + failed
291
+ print(f"Erase {args.type} summary: {succeeded}/{total} succeeded, " f"{failed} failed")
292
+ sys.exit(0 if failed == 0 else 1)
293
+
294
+ if args.booted:
295
+ succeeded, failed = SimulatorEraser.erase_booted()
296
+ if args.json:
297
+ import json
298
+
299
+ print(
300
+ json.dumps(
301
+ {
302
+ "action": "erase_booted",
303
+ "succeeded": succeeded,
304
+ "failed": failed,
305
+ "total": succeeded + failed,
306
+ }
307
+ )
308
+ )
309
+ else:
310
+ total = succeeded + failed
311
+ print(f"Erase booted summary: {succeeded}/{total} succeeded, " f"{failed} failed")
312
+ sys.exit(0 if failed == 0 else 1)
313
+
314
+ # Erase single device
315
+ device_id = args.udid or args.name
316
+ if not device_id:
317
+ print("Error: Specify --udid, --name, --all, --type, or --booted", file=sys.stderr)
318
+ sys.exit(1)
319
+
320
+ try:
321
+ udid = resolve_device_identifier(device_id)
322
+ except RuntimeError as e:
323
+ print(f"Error: {e}", file=sys.stderr)
324
+ sys.exit(1)
325
+
326
+ # Erase device
327
+ eraser = SimulatorEraser(udid=udid)
328
+ success, message = eraser.erase(verify=args.verify, timeout_seconds=args.timeout)
329
+
330
+ if args.json:
331
+ import json
332
+
333
+ print(
334
+ json.dumps(
335
+ {
336
+ "action": "erase",
337
+ "device_id": device_id,
338
+ "udid": udid,
339
+ "success": success,
340
+ "message": message,
341
+ }
342
+ )
343
+ )
344
+ else:
345
+ print(message)
346
+
347
+ sys.exit(0 if success else 1)
348
+
349
+
350
+ if __name__ == "__main__":
351
+ main()
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Shutdown iOS simulators with optional state verification.
4
+
5
+ This script shuts down one or more running simulators and optionally
6
+ verifies completion. Supports batch operations for efficient cleanup.
7
+
8
+ Key features:
9
+ - Shutdown by UDID or device name
10
+ - Verify shutdown completion with timeout
11
+ - Batch shutdown operations (all, by type)
12
+ - Progress reporting for CI/CD pipelines
13
+ """
14
+
15
+ import argparse
16
+ import subprocess
17
+ import sys
18
+ import time
19
+ from typing import Optional
20
+
21
+ from common.device_utils import (
22
+ list_simulators,
23
+ resolve_device_identifier,
24
+ )
25
+
26
+
27
+ class SimulatorShutdown:
28
+ """Shutdown iOS simulators with optional verification."""
29
+
30
+ def __init__(self, udid: str | None = None):
31
+ """Initialize with optional device UDID."""
32
+ self.udid = udid
33
+
34
+ def shutdown(self, verify: bool = True, timeout_seconds: int = 30) -> tuple[bool, str]:
35
+ """
36
+ Shutdown simulator and optionally verify completion.
37
+
38
+ Args:
39
+ verify: Wait for shutdown to complete
40
+ timeout_seconds: Maximum seconds to wait for shutdown
41
+
42
+ Returns:
43
+ (success, message) tuple
44
+ """
45
+ if not self.udid:
46
+ return False, "Error: Device UDID not specified"
47
+
48
+ start_time = time.time()
49
+
50
+ # Check if already shutdown
51
+ simulators = list_simulators(state="booted")
52
+ if not any(s["udid"] == self.udid for s in simulators):
53
+ elapsed = time.time() - start_time
54
+ return True, (f"Device already shutdown: {self.udid} " f"[checked in {elapsed:.1f}s]")
55
+
56
+ # Execute shutdown command
57
+ try:
58
+ cmd = ["xcrun", "simctl", "shutdown", self.udid]
59
+ result = subprocess.run(cmd, check=False, capture_output=True, text=True, timeout=30)
60
+
61
+ if result.returncode != 0:
62
+ error = result.stderr.strip()
63
+ return False, f"Shutdown failed: {error}"
64
+ except subprocess.TimeoutExpired:
65
+ return False, "Shutdown command timed out"
66
+ except Exception as e:
67
+ return False, f"Shutdown error: {e}"
68
+
69
+ # Optionally verify shutdown
70
+ if verify:
71
+ ready, verify_message = self._verify_shutdown(timeout_seconds)
72
+ elapsed = time.time() - start_time
73
+ if ready:
74
+ return True, (f"Device shutdown confirmed: {self.udid} " f"[{elapsed:.1f}s total]")
75
+ return False, verify_message
76
+
77
+ elapsed = time.time() - start_time
78
+ return True, (
79
+ f"Device shutdown: {self.udid} [{elapsed:.1f}s] "
80
+ "(use --verify to wait for confirmation)"
81
+ )
82
+
83
+ def _verify_shutdown(self, timeout_seconds: int = 30) -> tuple[bool, str]:
84
+ """
85
+ Verify device has fully shutdown.
86
+
87
+ Args:
88
+ timeout_seconds: Maximum seconds to wait
89
+
90
+ Returns:
91
+ (success, message) tuple
92
+ """
93
+ start_time = time.time()
94
+ poll_interval = 0.5
95
+ checks = 0
96
+
97
+ while time.time() - start_time < timeout_seconds:
98
+ try:
99
+ checks += 1
100
+ # Check booted devices
101
+ simulators = list_simulators(state="booted")
102
+ if not any(s["udid"] == self.udid for s in simulators):
103
+ elapsed = time.time() - start_time
104
+ return True, (
105
+ f"Device shutdown verified: {self.udid} "
106
+ f"[{elapsed:.1f}s, {checks} checks]"
107
+ )
108
+ except RuntimeError:
109
+ pass # Error checking, retry
110
+
111
+ time.sleep(poll_interval)
112
+
113
+ elapsed = time.time() - start_time
114
+ return False, (
115
+ f"Shutdown verification timeout: Device did not fully shutdown "
116
+ f"within {elapsed:.1f}s ({checks} checks)"
117
+ )
118
+
119
+ @staticmethod
120
+ def shutdown_all() -> tuple[int, int]:
121
+ """
122
+ Shutdown all booted simulators.
123
+
124
+ Returns:
125
+ (succeeded, failed) tuple with counts
126
+ """
127
+ simulators = list_simulators(state="booted")
128
+ succeeded = 0
129
+ failed = 0
130
+
131
+ for sim in simulators:
132
+ shutdown = SimulatorShutdown(udid=sim["udid"])
133
+ success, _message = shutdown.shutdown(verify=False)
134
+ if success:
135
+ succeeded += 1
136
+ else:
137
+ failed += 1
138
+
139
+ return succeeded, failed
140
+
141
+ @staticmethod
142
+ def shutdown_by_type(device_type: str) -> tuple[int, int]:
143
+ """
144
+ Shutdown all booted simulators of a specific type.
145
+
146
+ Args:
147
+ device_type: Device type filter (e.g., "iPhone", "iPad")
148
+
149
+ Returns:
150
+ (succeeded, failed) tuple with counts
151
+ """
152
+ simulators = list_simulators(state="booted")
153
+ succeeded = 0
154
+ failed = 0
155
+
156
+ for sim in simulators:
157
+ if device_type.lower() in sim["name"].lower():
158
+ shutdown = SimulatorShutdown(udid=sim["udid"])
159
+ success, _message = shutdown.shutdown(verify=False)
160
+ if success:
161
+ succeeded += 1
162
+ else:
163
+ failed += 1
164
+
165
+ return succeeded, failed
166
+
167
+
168
+ def main():
169
+ """Main entry point."""
170
+ parser = argparse.ArgumentParser(
171
+ description="Shutdown iOS simulators with optional verification"
172
+ )
173
+ parser.add_argument(
174
+ "--udid",
175
+ help="Device UDID or name (required unless using --all or --type)",
176
+ )
177
+ parser.add_argument(
178
+ "--name",
179
+ help="Device name (alternative to --udid)",
180
+ )
181
+ parser.add_argument(
182
+ "--verify",
183
+ action="store_true",
184
+ help="Wait for shutdown to complete and verify state",
185
+ )
186
+ parser.add_argument(
187
+ "--timeout",
188
+ type=int,
189
+ default=30,
190
+ help="Timeout for --verify in seconds (default: 30)",
191
+ )
192
+ parser.add_argument(
193
+ "--all",
194
+ action="store_true",
195
+ help="Shutdown all booted simulators",
196
+ )
197
+ parser.add_argument(
198
+ "--type",
199
+ help="Shutdown all booted simulators of a specific type (e.g., iPhone)",
200
+ )
201
+ parser.add_argument(
202
+ "--json",
203
+ action="store_true",
204
+ help="Output as JSON",
205
+ )
206
+
207
+ args = parser.parse_args()
208
+
209
+ # Handle batch operations
210
+ if args.all:
211
+ succeeded, failed = SimulatorShutdown.shutdown_all()
212
+ if args.json:
213
+ import json
214
+
215
+ print(
216
+ json.dumps(
217
+ {
218
+ "action": "shutdown_all",
219
+ "succeeded": succeeded,
220
+ "failed": failed,
221
+ "total": succeeded + failed,
222
+ }
223
+ )
224
+ )
225
+ else:
226
+ total = succeeded + failed
227
+ print(f"Shutdown summary: {succeeded}/{total} succeeded, " f"{failed} failed")
228
+ sys.exit(0 if failed == 0 else 1)
229
+
230
+ if args.type:
231
+ succeeded, failed = SimulatorShutdown.shutdown_by_type(args.type)
232
+ if args.json:
233
+ import json
234
+
235
+ print(
236
+ json.dumps(
237
+ {
238
+ "action": "shutdown_by_type",
239
+ "type": args.type,
240
+ "succeeded": succeeded,
241
+ "failed": failed,
242
+ "total": succeeded + failed,
243
+ }
244
+ )
245
+ )
246
+ else:
247
+ total = succeeded + failed
248
+ print(
249
+ f"Shutdown {args.type} summary: {succeeded}/{total} succeeded, " f"{failed} failed"
250
+ )
251
+ sys.exit(0 if failed == 0 else 1)
252
+
253
+ # Resolve device identifier
254
+ device_id = args.udid or args.name
255
+ if not device_id:
256
+ print("Error: Specify --udid, --name, --all, or --type", file=sys.stderr)
257
+ sys.exit(1)
258
+
259
+ try:
260
+ udid = resolve_device_identifier(device_id)
261
+ except RuntimeError as e:
262
+ print(f"Error: {e}", file=sys.stderr)
263
+ sys.exit(1)
264
+
265
+ # Shutdown device
266
+ shutdown = SimulatorShutdown(udid=udid)
267
+ success, message = shutdown.shutdown(verify=args.verify, timeout_seconds=args.timeout)
268
+
269
+ if args.json:
270
+ import json
271
+
272
+ print(
273
+ json.dumps(
274
+ {
275
+ "action": "shutdown",
276
+ "device_id": device_id,
277
+ "udid": udid,
278
+ "success": success,
279
+ "message": message,
280
+ }
281
+ )
282
+ )
283
+ else:
284
+ print(message)
285
+
286
+ sys.exit(0 if success else 1)
287
+
288
+
289
+ if __name__ == "__main__":
290
+ main()