@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,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()
|