@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,338 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Screenshot utilities with dual-mode support.
4
+
5
+ Provides unified screenshot handling with:
6
+ - File-based mode: Persistent artifacts for test documentation
7
+ - Inline base64 mode: Vision-based automation for agent analysis
8
+ - Size presets: Token optimization (full/half/quarter/thumb)
9
+ - Semantic naming: {appName}_{screenName}_{state}_{timestamp}.png
10
+
11
+ Supports resize operations via PIL (optional dependency).
12
+
13
+ Used by:
14
+ - test_recorder.py - Step-based screenshot recording
15
+ - app_state_capture.py - State snapshot captures
16
+ """
17
+
18
+ import base64
19
+ import os
20
+ import subprocess
21
+ import sys
22
+ from datetime import datetime
23
+ from pathlib import Path
24
+ from typing import Any
25
+
26
+ # Try to import PIL for resizing, but make it optional
27
+ try:
28
+ from PIL import Image
29
+
30
+ HAS_PIL = True
31
+ except ImportError:
32
+ HAS_PIL = False
33
+
34
+
35
+ def generate_screenshot_name(
36
+ app_name: str | None = None,
37
+ screen_name: str | None = None,
38
+ state: str | None = None,
39
+ timestamp: str | None = None,
40
+ extension: str = "png",
41
+ ) -> str:
42
+ """Generate semantic screenshot filename.
43
+
44
+ Format: {appName}_{screenName}_{state}_{timestamp}.{ext}
45
+ Falls back to: screenshot_{timestamp}.{ext}
46
+
47
+ Args:
48
+ app_name: Application name (e.g., 'MyApp')
49
+ screen_name: Screen name (e.g., 'Login')
50
+ state: State description (e.g., 'Empty', 'Filled', 'Error')
51
+ timestamp: ISO timestamp (uses current time if None)
52
+ extension: File extension (default: 'png')
53
+
54
+ Returns:
55
+ Semantic filename ready for safe file creation
56
+
57
+ Example:
58
+ name = generate_screenshot_name('MyApp', 'Login', 'Empty')
59
+ # Returns: 'MyApp_Login_Empty_20251028-143052.png'
60
+
61
+ name = generate_screenshot_name()
62
+ # Returns: 'screenshot_20251028-143052.png'
63
+ """
64
+ if timestamp is None:
65
+ timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
66
+
67
+ # Build semantic name
68
+ if app_name or screen_name or state:
69
+ parts = [app_name, screen_name, state]
70
+ parts = [p for p in parts if p] # Filter None/empty
71
+ name = "_".join(parts) + f"_{timestamp}"
72
+ else:
73
+ name = f"screenshot_{timestamp}"
74
+
75
+ return f"{name}.{extension}"
76
+
77
+
78
+ def get_size_preset(size: str = "half") -> tuple[float, float]:
79
+ """Get scale factors for size preset.
80
+
81
+ Args:
82
+ size: 'full', 'half', 'quarter', 'thumb'
83
+
84
+ Returns:
85
+ Tuple of (scale_x, scale_y) for resizing
86
+
87
+ Example:
88
+ scale_x, scale_y = get_size_preset('half')
89
+ # Returns: (0.5, 0.5)
90
+ """
91
+ presets = {
92
+ "full": (1.0, 1.0),
93
+ "half": (0.5, 0.5),
94
+ "quarter": (0.25, 0.25),
95
+ "thumb": (0.1, 0.1),
96
+ }
97
+ return presets.get(size, (0.5, 0.5))
98
+
99
+
100
+ def resize_screenshot(
101
+ input_path: str,
102
+ output_path: str | None = None,
103
+ size: str = "half",
104
+ quality: int = 85,
105
+ ) -> tuple[str, int, int]:
106
+ """Resize screenshot for token optimization.
107
+
108
+ Requires PIL (Pillow). Falls back gracefully without it.
109
+
110
+ Args:
111
+ input_path: Path to original screenshot
112
+ output_path: Output path (uses input_path if None)
113
+ size: 'full', 'half', 'quarter', 'thumb'
114
+ quality: JPEG quality (1-100, default: 85)
115
+
116
+ Returns:
117
+ Tuple of (output_path, width, height) of resized image
118
+
119
+ Raises:
120
+ FileNotFoundError: If input file doesn't exist
121
+ ValueError: If PIL not installed and size != 'full'
122
+
123
+ Example:
124
+ output, w, h = resize_screenshot(
125
+ 'screenshot.png',
126
+ 'screenshot_half.png',
127
+ 'half'
128
+ )
129
+ print(f"Resized to {w}x{h}")
130
+ """
131
+ input_file = Path(input_path)
132
+ if not input_file.exists():
133
+ raise FileNotFoundError(f"Screenshot not found: {input_path}")
134
+
135
+ # If full size, just copy
136
+ if size == "full":
137
+ if output_path:
138
+ import shutil
139
+
140
+ shutil.copy(input_path, output_path)
141
+ output_file = Path(output_path)
142
+ else:
143
+ output_file = input_file
144
+
145
+ # Get original dimensions
146
+ if HAS_PIL:
147
+ img = Image.open(str(output_file))
148
+ return (str(output_file), img.width, img.height)
149
+ return (str(output_file), 0, 0) # Dimensions unknown without PIL
150
+
151
+ # Need PIL to resize
152
+ if not HAS_PIL:
153
+ raise ValueError(
154
+ f"Size preset '{size}' requires PIL (Pillow). " "Install with: pip3 install pillow"
155
+ )
156
+
157
+ # Open original image
158
+ img = Image.open(str(input_file))
159
+ orig_w, orig_h = img.size
160
+
161
+ # Calculate new size
162
+ scale_x, scale_y = get_size_preset(size)
163
+ new_w = int(orig_w * scale_x)
164
+ new_h = int(orig_h * scale_y)
165
+
166
+ # Resize with high-quality resampling
167
+ resized = img.resize((new_w, new_h), Image.Resampling.LANCZOS)
168
+
169
+ # Determine output path
170
+ if output_path is None:
171
+ # Insert size marker before extension
172
+ stem = input_file.stem
173
+ suffix = input_file.suffix
174
+ output_path = str(input_file.parent / f"{stem}_{size}{suffix}")
175
+
176
+ # Save resized image
177
+ resized.save(output_path, quality=quality, optimize=True)
178
+
179
+ return (output_path, new_w, new_h)
180
+
181
+
182
+ def capture_screenshot(
183
+ udid: str,
184
+ output_path: str | None = None,
185
+ size: str = "half",
186
+ inline: bool = False,
187
+ app_name: str | None = None,
188
+ screen_name: str | None = None,
189
+ state: str | None = None,
190
+ ) -> dict[str, Any]:
191
+ """Capture screenshot with flexible output modes.
192
+
193
+ Supports both file-based (persistent artifacts) and inline base64 modes
194
+ (for vision-based automation).
195
+
196
+ Args:
197
+ udid: Device UDID
198
+ output_path: File path for file mode (generates semantic name if None)
199
+ size: 'full', 'half', 'quarter', 'thumb' (default: 'half')
200
+ inline: If True, returns base64 data instead of saving to file
201
+ app_name: App name for semantic naming
202
+ screen_name: Screen name for semantic naming
203
+ state: State description for semantic naming
204
+
205
+ Returns:
206
+ Dict with mode-specific fields:
207
+
208
+ File mode:
209
+ {
210
+ 'mode': 'file',
211
+ 'file_path': str,
212
+ 'size_bytes': int,
213
+ 'width': int,
214
+ 'height': int,
215
+ 'size_preset': str
216
+ }
217
+
218
+ Inline mode:
219
+ {
220
+ 'mode': 'inline',
221
+ 'base64_data': str,
222
+ 'mime_type': 'image/png',
223
+ 'width': int,
224
+ 'height': int,
225
+ 'size_preset': str
226
+ }
227
+
228
+ Example:
229
+ # File mode
230
+ result = capture_screenshot('ABC123', app_name='MyApp')
231
+ print(f"Saved to: {result['file_path']}")
232
+
233
+ # Inline mode
234
+ result = capture_screenshot('ABC123', inline=True, size='half')
235
+ print(f"Screenshot: {result['width']}x{result['height']}")
236
+ print(f"Base64: {result['base64_data'][:50]}...")
237
+ """
238
+ try:
239
+ # Capture raw screenshot to temp file
240
+ temp_path = "/tmp/ios_simulator_screenshot.png"
241
+ cmd = ["xcrun", "simctl", "io", udid, "screenshot", temp_path]
242
+
243
+ subprocess.run(cmd, capture_output=True, text=True, check=True)
244
+
245
+ if inline:
246
+ # Inline mode: resize and convert to base64
247
+ # Resize if needed
248
+ if size != "full" and HAS_PIL:
249
+ resized_path, width, height = resize_screenshot(temp_path, size=size)
250
+ else:
251
+ resized_path = temp_path
252
+ # Get dimensions via PIL if available
253
+ if HAS_PIL:
254
+ img = Image.open(resized_path)
255
+ width, height = img.size
256
+ else:
257
+ width, height = 390, 844 # Fallback to common device size
258
+
259
+ # Read and encode as base64
260
+ with open(resized_path, "rb") as f:
261
+ base64_data = base64.b64encode(f.read()).decode("utf-8")
262
+
263
+ # Clean up temp files
264
+ Path(temp_path).unlink(missing_ok=True)
265
+ if resized_path != temp_path:
266
+ Path(resized_path).unlink(missing_ok=True)
267
+
268
+ return {
269
+ "mode": "inline",
270
+ "base64_data": base64_data,
271
+ "mime_type": "image/png",
272
+ "width": width,
273
+ "height": height,
274
+ "size_preset": size,
275
+ }
276
+
277
+ # File mode: save to output path with semantic naming
278
+ if output_path is None:
279
+ output_path = generate_screenshot_name(app_name, screen_name, state)
280
+
281
+ # Resize if needed
282
+ if size != "full" and HAS_PIL:
283
+ final_path, width, height = resize_screenshot(temp_path, output_path, size)
284
+ else:
285
+ # Just move temp to output
286
+ import shutil
287
+
288
+ shutil.move(temp_path, output_path)
289
+ final_path = output_path
290
+
291
+ # Get dimensions via PIL if available
292
+ if HAS_PIL:
293
+ img = Image.open(final_path)
294
+ width, height = img.size
295
+ else:
296
+ width, height = 390, 844 # Fallback
297
+
298
+ # Get file size
299
+ size_bytes = Path(final_path).stat().st_size
300
+
301
+ return {
302
+ "mode": "file",
303
+ "file_path": final_path,
304
+ "size_bytes": size_bytes,
305
+ "width": width,
306
+ "height": height,
307
+ "size_preset": size,
308
+ }
309
+
310
+ except subprocess.CalledProcessError as e:
311
+ raise RuntimeError(f"Failed to capture screenshot: {e.stderr}") from e
312
+ except Exception as e:
313
+ raise RuntimeError(f"Screenshot capture error: {e!s}") from e
314
+
315
+
316
+ def format_screenshot_result(result: dict[str, Any]) -> str:
317
+ """Format screenshot result for human-readable output.
318
+
319
+ Args:
320
+ result: Result dictionary from capture_screenshot()
321
+
322
+ Returns:
323
+ Formatted string for printing
324
+
325
+ Example:
326
+ result = capture_screenshot('ABC123', inline=True)
327
+ print(format_screenshot_result(result))
328
+ """
329
+ if result["mode"] == "file":
330
+ return (
331
+ f"Screenshot: {result['file_path']}\n"
332
+ f"Dimensions: {result['width']}x{result['height']}\n"
333
+ f"Size: {result['size_bytes']} bytes"
334
+ )
335
+ return (
336
+ f"Screenshot (inline): {result['width']}x{result['height']}\n"
337
+ f"Base64 length: {len(result['base64_data'])} chars"
338
+ )