@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,385 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ iOS Simulator Appearance Manager
4
+
5
+ Control dark mode, dynamic type size, locale, and region.
6
+ Wraps xcrun simctl ui and defaults write for appearance testing.
7
+
8
+ Usage:
9
+ python scripts/appearance.py --theme dark
10
+ python scripts/appearance.py --text-size AX3
11
+ python scripts/appearance.py --locale ar --region SA --bundle-id com.myapp
12
+ python scripts/appearance.py --reset
13
+
14
+ RTL locales: ar, he, fa, ur, yi (app must restart to reflow layout).
15
+ """
16
+
17
+ import argparse
18
+ import json
19
+ import subprocess
20
+ import sys
21
+
22
+ from common import resolve_udid
23
+
24
+ # === CONSTANTS ===
25
+
26
+ # Map friendly size aliases to xcrun simctl content_size tokens
27
+ TEXT_SIZE_MAP: dict[str, str] = {
28
+ "XS": "extra-small",
29
+ "S": "small",
30
+ "M": "medium",
31
+ "L": "large",
32
+ "XL": "extra-large",
33
+ "XXL": "extra-extra-large",
34
+ "XXXL": "extra-extra-extra-large",
35
+ "AX1": "accessibility-medium",
36
+ "AX2": "accessibility-large",
37
+ "AX3": "accessibility-extra-large",
38
+ "AX4": "accessibility-extra-extra-large",
39
+ "AX5": "accessibility-extra-extra-extra-large",
40
+ }
41
+
42
+ # Locales that require RTL layout direction
43
+ RTL_LOCALES: frozenset[str] = frozenset({"ar", "he", "fa", "ur", "yi"})
44
+
45
+ # Default appearance values used by --reset
46
+ DEFAULT_THEME = "light"
47
+ DEFAULT_TEXT_SIZE = "M"
48
+ DEFAULT_LOCALE = "en"
49
+ DEFAULT_REGION = "US"
50
+
51
+
52
+ # === APPEARANCE MANAGER ===
53
+
54
+
55
+ class AppearanceManager:
56
+ """Manages iOS simulator appearance: theme, dynamic type, and locale."""
57
+
58
+ def __init__(self, udid: str | None = None):
59
+ """Initialize appearance manager.
60
+
61
+ Args:
62
+ udid: Optional device UDID (auto-detects booted simulator if None)
63
+ """
64
+ self.udid = udid
65
+
66
+ # === PUBLIC API ===
67
+
68
+ def set_theme(self, theme: str) -> tuple[bool, str]:
69
+ """Switch simulator between light and dark appearance.
70
+
71
+ Args:
72
+ theme: 'light' or 'dark'
73
+
74
+ Returns:
75
+ (success, message)
76
+ """
77
+ cmd = ["xcrun", "simctl", "ui", self.udid, "appearance", theme]
78
+ return self._run(cmd, f"Theme set: {theme}")
79
+
80
+ def set_text_size(self, alias: str) -> tuple[bool, str]:
81
+ """Set dynamic type content size.
82
+
83
+ Args:
84
+ alias: Friendly alias (XS, S, M, L, XL, XXL, XXXL, AX1-AX5)
85
+
86
+ Returns:
87
+ (success, message)
88
+ """
89
+ token = TEXT_SIZE_MAP.get(alias.upper())
90
+ if not token:
91
+ valid = ", ".join(TEXT_SIZE_MAP.keys())
92
+ return False, f"Unknown text size '{alias}'. Valid: {valid}"
93
+
94
+ cmd = ["xcrun", "simctl", "ui", self.udid, "content_size", token]
95
+ return self._run(cmd, f"Text size set: {alias} ({token})")
96
+
97
+ def set_locale(
98
+ self,
99
+ locale: str,
100
+ region: str | None = None,
101
+ bundle_id: str | None = None,
102
+ ) -> tuple[bool, str]:
103
+ """Write AppleLanguages and AppleLocale to simulator global defaults.
104
+
105
+ Optionally restarts a specific app via bundle ID so the locale takes
106
+ effect immediately. Without --bundle-id the change applies on next
107
+ cold app launch.
108
+
109
+ Args:
110
+ locale: BCP-47 language code (e.g. 'en', 'ar', 'de')
111
+ region: ISO 3166-1 alpha-2 region code (e.g. 'US', 'SA', 'IE')
112
+ bundle_id: Optional app bundle ID — triggers terminate + launch
113
+
114
+ Returns:
115
+ (success, message)
116
+ """
117
+ apple_locale = f"{locale}_{region}" if region else locale
118
+
119
+ # Write AppleLanguages array
120
+ lang_cmd = [
121
+ "xcrun",
122
+ "simctl",
123
+ "spawn",
124
+ self.udid,
125
+ "defaults",
126
+ "write",
127
+ "-g",
128
+ "AppleLanguages",
129
+ "-array",
130
+ locale,
131
+ ]
132
+ ok, msg = self._run(lang_cmd, "")
133
+ if not ok:
134
+ return False, f"Failed to write AppleLanguages: {msg}"
135
+
136
+ # Write AppleLocale string
137
+ locale_cmd = [
138
+ "xcrun",
139
+ "simctl",
140
+ "spawn",
141
+ self.udid,
142
+ "defaults",
143
+ "write",
144
+ "-g",
145
+ "AppleLocale",
146
+ "-string",
147
+ apple_locale,
148
+ ]
149
+ ok, msg = self._run(locale_cmd, "")
150
+ if not ok:
151
+ return False, f"Failed to write AppleLocale: {msg}"
152
+
153
+ is_rtl = locale in RTL_LOCALES
154
+ rtl_note = " [RTL layout]" if is_rtl else ""
155
+ summary = f"Locale set: {apple_locale}{rtl_note}"
156
+
157
+ if bundle_id:
158
+ restart_ok, restart_msg = self._restart_app(bundle_id)
159
+ if restart_ok:
160
+ summary += f" — app restarted: {bundle_id}"
161
+ else:
162
+ summary += f" — locale written but app restart failed: {restart_msg}"
163
+ else:
164
+ summary += " — restart app to apply"
165
+
166
+ return True, summary
167
+
168
+ def reset(self) -> tuple[bool, str]:
169
+ """Reset theme, text size, and locale to system defaults.
170
+
171
+ Returns:
172
+ (success, message)
173
+ """
174
+ results: list[str] = []
175
+ errors: list[str] = []
176
+
177
+ ok, msg = self.set_theme(DEFAULT_THEME)
178
+ (results if ok else errors).append(msg)
179
+
180
+ ok, msg = self.set_text_size(DEFAULT_TEXT_SIZE)
181
+ (results if ok else errors).append(msg)
182
+
183
+ ok, msg = self.set_locale(DEFAULT_LOCALE, DEFAULT_REGION)
184
+ (results if ok else errors).append(msg)
185
+
186
+ if errors:
187
+ return False, f"Reset partial — errors: {'; '.join(errors)}"
188
+
189
+ return True, "Appearance reset to defaults (light / M / en_US)"
190
+
191
+ # === PRIVATE HELPERS ===
192
+
193
+ def _run(self, cmd: list[str], success_message: str) -> tuple[bool, str]:
194
+ """Run subprocess command and return (success, message).
195
+
196
+ Args:
197
+ cmd: Command list (never shell=True)
198
+ success_message: Human-readable success summary
199
+
200
+ Returns:
201
+ (success, message)
202
+ """
203
+ try:
204
+ subprocess.run(
205
+ cmd,
206
+ capture_output=True,
207
+ text=True,
208
+ check=True,
209
+ )
210
+ return True, success_message
211
+ except subprocess.CalledProcessError as error:
212
+ stderr = error.stderr.strip() if error.stderr else "unknown error"
213
+ return False, stderr
214
+
215
+ def _restart_app(self, bundle_id: str) -> tuple[bool, str]:
216
+ """Terminate then launch an app by bundle ID.
217
+
218
+ Args:
219
+ bundle_id: App bundle ID
220
+
221
+ Returns:
222
+ (success, message)
223
+ """
224
+ terminate_cmd = ["xcrun", "simctl", "terminate", self.udid, bundle_id]
225
+ # Terminate may fail if app is not running — that is acceptable
226
+ subprocess.run(terminate_cmd, capture_output=True, check=False)
227
+
228
+ launch_cmd = ["xcrun", "simctl", "launch", self.udid, bundle_id]
229
+ try:
230
+ subprocess.run(launch_cmd, capture_output=True, text=True, check=True)
231
+ return True, f"Launched {bundle_id}"
232
+ except subprocess.CalledProcessError as error:
233
+ stderr = error.stderr.strip() if error.stderr else "launch failed"
234
+ return False, stderr
235
+
236
+
237
+ # === CLI ===
238
+
239
+
240
+ def main() -> None:
241
+ """Main entry point."""
242
+ parser = argparse.ArgumentParser(
243
+ description=(
244
+ "iOS Simulator Appearance Manager — control dark mode, "
245
+ "dynamic type, locale, and region."
246
+ ),
247
+ formatter_class=argparse.RawDescriptionHelpFormatter,
248
+ epilog="""
249
+ Examples:
250
+ python scripts/appearance.py --theme dark
251
+ python scripts/appearance.py --text-size AX3
252
+ python scripts/appearance.py --locale ar --region SA
253
+ python scripts/appearance.py --locale de --region DE --bundle-id com.myapp
254
+ python scripts/appearance.py --reset
255
+
256
+ RTL locales (ar, he, fa, ur, yi): app must restart to reflow layout.
257
+ Text sizes: XS S M L XL XXL XXXL AX1 AX2 AX3 AX4 AX5
258
+ """,
259
+ )
260
+
261
+ parser.add_argument(
262
+ "--theme",
263
+ choices=["light", "dark"],
264
+ help="Set light or dark appearance",
265
+ )
266
+ parser.add_argument(
267
+ "--text-size",
268
+ choices=list(TEXT_SIZE_MAP.keys()),
269
+ metavar="{" + ",".join(TEXT_SIZE_MAP.keys()) + "}",
270
+ help="Set dynamic type size (XS smallest, AX5 largest)",
271
+ )
272
+ parser.add_argument(
273
+ "--locale",
274
+ metavar="CODE",
275
+ help="Set locale language code (e.g. en, de, ar, ja)",
276
+ )
277
+ parser.add_argument(
278
+ "--region",
279
+ metavar="CODE",
280
+ help="Set region code (e.g. US, IE, SA). Used with --locale.",
281
+ )
282
+ parser.add_argument(
283
+ "--bundle-id",
284
+ metavar="BUNDLE_ID",
285
+ help="App bundle ID to terminate+relaunch after locale change",
286
+ )
287
+ parser.add_argument(
288
+ "--reset",
289
+ action="store_true",
290
+ help="Reset theme, text size, and locale to system defaults",
291
+ )
292
+ parser.add_argument(
293
+ "--udid",
294
+ help="Device UDID (auto-detects booted simulator if not provided)",
295
+ )
296
+ parser.add_argument(
297
+ "--json",
298
+ action="store_true",
299
+ help="Output results as JSON",
300
+ )
301
+ parser.add_argument(
302
+ "--verbose",
303
+ action="store_true",
304
+ help="Show detailed output",
305
+ )
306
+
307
+ args = parser.parse_args()
308
+
309
+ # Guard: require at least one action
310
+ if not any([args.theme, args.text_size, args.locale, args.reset]):
311
+ parser.print_help()
312
+ sys.exit(1)
313
+
314
+ # Guard: --reset is incompatible with explicit appearance flags
315
+ if args.reset and any([args.theme, args.text_size, args.locale]):
316
+ print(
317
+ "Error: --reset cannot be combined with --theme, --text-size, or --locale",
318
+ file=sys.stderr,
319
+ )
320
+ sys.exit(1)
321
+
322
+ # Guard: --region without --locale is a no-op
323
+ if args.region and not args.locale:
324
+ print("Error: --region requires --locale", file=sys.stderr)
325
+ sys.exit(1)
326
+
327
+ # Guard: --bundle-id without --locale is ambiguous
328
+ if args.bundle_id and not args.locale:
329
+ print("Error: --bundle-id requires --locale", file=sys.stderr)
330
+ sys.exit(1)
331
+
332
+ try:
333
+ udid = resolve_udid(args.udid)
334
+ except RuntimeError as error:
335
+ print(f"Error: {error}", file=sys.stderr)
336
+ sys.exit(1)
337
+
338
+ manager = AppearanceManager(udid=udid)
339
+ device_label = udid if udid else "booted"
340
+
341
+ # Collect all requested operations
342
+ operations: list[tuple[str, tuple[bool, str]]] = []
343
+
344
+ if args.reset:
345
+ operations.append(("reset", manager.reset()))
346
+ else:
347
+ if args.theme:
348
+ operations.append(("theme", manager.set_theme(args.theme)))
349
+
350
+ if args.text_size:
351
+ operations.append(("text_size", manager.set_text_size(args.text_size)))
352
+
353
+ if args.locale:
354
+ operations.append(
355
+ (
356
+ "locale",
357
+ manager.set_locale(args.locale, args.region, args.bundle_id),
358
+ )
359
+ )
360
+
361
+ overall_success = all(ok for _, (ok, _) in operations)
362
+
363
+ if args.json:
364
+ results = {action: {"success": ok, "message": msg} for action, (ok, msg) in operations}
365
+ payload = {
366
+ "success": overall_success,
367
+ "udid": device_label,
368
+ "results": results,
369
+ }
370
+ print(json.dumps(payload))
371
+ elif args.verbose:
372
+ print(f"Device: {device_label}")
373
+ for action, (ok, msg) in operations:
374
+ status = "OK" if ok else "FAIL"
375
+ print(f" [{status}] {action}: {msg}")
376
+ else:
377
+ for _, (_ok, msg) in operations:
378
+ if msg:
379
+ print(msg)
380
+
381
+ sys.exit(0 if overall_success else 1)
382
+
383
+
384
+ if __name__ == "__main__":
385
+ main()