@leejungkiin/awkit 1.7.1 → 1.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (245) hide show
  1. package/bin/awk.js +576 -84
  2. package/core/CLAUDE.md +1 -1
  3. package/core/GEMINI.md +148 -167
  4. package/core/GEMINI.md.bak +149 -116
  5. package/core/skill-runtime-manifest.json +3 -0
  6. package/docs/Claude Fable 5.md +3826 -0
  7. package/docs/android_kotlin_system_instruction.md +210 -0
  8. package/docs/brainstorm_ponytail_integration.md +146 -0
  9. package/docs/brainstorm_smart_setup.md +113 -0
  10. package/docs/deep-research-report (1).md +293 -0
  11. package/docs/history/GEMINI.v1.md +135 -0
  12. package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
  13. package/docs/history/implementation_plan.v1.md +58 -0
  14. package/package.json +4 -1
  15. package/scripts/artifact-storage.js +130 -0
  16. package/scripts/automation-gate.js +35 -2
  17. package/scripts/claude-plan.js +76 -0
  18. package/scripts/dependency-manager.js +210 -0
  19. package/scripts/exec-rtk.js +11 -5
  20. package/scripts/i18n-helper.js +381 -0
  21. package/scripts/multi-model-pipeline.js +144 -0
  22. package/skill-packs/mobile-ios/pack.json +4 -2
  23. package/skill-packs/reverse-engineering/pack.json +1 -0
  24. package/skills/CATALOG.md +20 -0
  25. package/skills/GEMINI.md +9 -1
  26. package/skills/TRIGGER_INDEX.md +10 -0
  27. package/skills/ai-music/SKILL.md +275 -0
  28. package/skills/android-re-analyzer/SKILL.md +238 -0
  29. package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
  30. package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
  31. package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
  32. package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
  33. package/skills/android-re-analyzer/references/setup-guide.md +221 -0
  34. package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
  35. package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
  36. package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
  37. package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
  38. package/skills/animal-island-ui-style/SKILL.md +1450 -0
  39. package/skills/app-store-review-agent/SKILL.md +164 -0
  40. package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
  41. package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
  42. package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
  43. package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
  44. package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
  45. package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
  46. package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
  47. package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
  48. package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
  49. package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
  50. package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
  51. package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
  52. package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
  53. package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
  54. package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
  55. package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
  56. package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
  57. package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
  58. package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
  59. package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
  60. package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
  61. package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
  62. package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
  63. package/skills/awf-ponytail/SKILL.md +91 -0
  64. package/skills/awf-ponytail-review/SKILL.md +67 -0
  65. package/skills/awf-session-restore/SKILL.md +3 -3
  66. package/skills/brainstorm-agent/SKILL.md +11 -2
  67. package/skills/brainstorm-agent/templates/brief-template.md +8 -0
  68. package/skills/claude-planner/SKILL.md +47 -0
  69. package/skills/code-review/SKILL.md +87 -0
  70. package/skills/expo-game-development/SKILL.md +163 -0
  71. package/skills/flutter/LICENSE.txt +202 -0
  72. package/skills/flutter/SKILL.md +127 -0
  73. package/skills/flutter-project-creater/LICENSE.txt +202 -0
  74. package/skills/flutter-project-creater/SKILL.md +106 -0
  75. package/skills/game-developer/SKILL.md +163 -0
  76. package/skills/game-developer/references/ecs-patterns.md +501 -0
  77. package/skills/game-developer/references/multiplayer-networking.md +475 -0
  78. package/skills/game-developer/references/performance-optimization.md +422 -0
  79. package/skills/game-developer/references/unity-patterns.md +271 -0
  80. package/skills/game-developer/references/unreal-cpp.md +352 -0
  81. package/skills/generate-gui-assets/SKILL.md +305 -0
  82. package/skills/generate-gui-assets/agents/openai.yaml +4 -0
  83. package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
  84. package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
  85. package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
  86. package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
  87. package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
  88. package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
  89. package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
  90. package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
  91. package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
  92. package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
  93. package/skills/godot-game-development/SKILL.md +142 -0
  94. package/skills/hatch-pet/LICENSE.txt +201 -0
  95. package/skills/hatch-pet/SKILL.md +420 -0
  96. package/skills/hatch-pet/agents/openai.yaml +4 -0
  97. package/skills/hatch-pet/references/animation-rows.md +29 -0
  98. package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
  99. package/skills/hatch-pet/references/qa-rubric.md +60 -0
  100. package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
  101. package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
  102. package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
  103. package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
  104. package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
  105. package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
  106. package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
  107. package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
  108. package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
  109. package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
  110. package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
  111. package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
  112. package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
  113. package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
  114. package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
  115. package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
  116. package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
  117. package/skills/i18n-orchestrator/SKILL.md +37 -0
  118. package/skills/ios-simulator-skill/SKILL.md +390 -0
  119. package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
  120. package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
  121. package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
  122. package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
  123. package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
  124. package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
  125. package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
  126. package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
  127. package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
  128. package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
  129. package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
  130. package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
  131. package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
  132. package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
  133. package/skills/ios-simulator-skill/scripts/container.py +668 -0
  134. package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
  135. package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
  136. package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
  137. package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
  138. package/skills/ios-simulator-skill/scripts/location.py +467 -0
  139. package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
  140. package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
  141. package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
  142. package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
  143. package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
  144. package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
  145. package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
  146. package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
  147. package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
  148. package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
  149. package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
  150. package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
  151. package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
  152. package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
  153. package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
  154. package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
  155. package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
  156. package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
  157. package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
  158. package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
  159. package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
  160. package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
  161. package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
  162. package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
  163. package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
  164. package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
  165. package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
  166. package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
  167. package/skills/orchestrator/SKILL.md +0 -20
  168. package/skills/persistent-storage/SKILL.md +55 -0
  169. package/skills/short-maker/SKILL.md +23 -0
  170. package/skills/short-maker/scripts/effects.js +56 -0
  171. package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
  172. package/skills/short-maker/scripts/videomix.js +601 -0
  173. package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
  174. package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
  175. package/skills/smali-to-kotlin/SKILL.md +128 -0
  176. package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
  177. package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
  178. package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
  179. package/skills/smali-to-kotlin/library-patterns.md +189 -0
  180. package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
  181. package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
  182. package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
  183. package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
  184. package/skills/smali-to-kotlin/phase-3-build.md +248 -0
  185. package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
  186. package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
  187. package/skills/smali-to-kotlin/templates/app-map.md +101 -0
  188. package/skills/smali-to-kotlin/templates/architecture.md +142 -0
  189. package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
  190. package/skills/spec-gate/SKILL.md +6 -2
  191. package/skills/symphony-enforcer/SKILL.md +8 -0
  192. package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
  193. package/skills/symphony-enforcer/examples/three-phase.md +16 -0
  194. package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
  195. package/skills/unity-game-development/SKILL.md +231 -0
  196. package/skills/video-edit/SKILL.md +36 -0
  197. package/skills/video-edit/scripts/video_edit.py +324 -0
  198. package/templates/project-identity/android.json +2 -2
  199. package/templates/project-identity/backend-nestjs.json +2 -2
  200. package/templates/project-identity/expo.json +2 -2
  201. package/templates/project-identity/ios.json +2 -2
  202. package/templates/project-identity/web-nextjs.json +2 -2
  203. package/templates/setup-mapping.json +48 -0
  204. package/templates/specs/design-template.md +161 -71
  205. package/templates/specs/requirements-template.md +65 -133
  206. package/templates/specs/task-spec-template.xml +3 -0
  207. package/workflows/_uncategorized/critic.md +40 -0
  208. package/workflows/_uncategorized/git-rebase-flow.md +81 -0
  209. package/workflows/_uncategorized/image-gen.md +118 -0
  210. package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
  211. package/workflows/_uncategorized/pixel-gen.md +86 -0
  212. package/workflows/_uncategorized/pixel-setup.md +90 -0
  213. package/workflows/_uncategorized/ponytail-review.md +59 -0
  214. package/workflows/_uncategorized/reverse-android-build.md +222 -0
  215. package/workflows/_uncategorized/reverse-android-design.md +139 -0
  216. package/workflows/_uncategorized/reverse-android-discover.md +150 -0
  217. package/workflows/_uncategorized/reverse-android-scan.md +158 -0
  218. package/workflows/_uncategorized/reverse-android.md +143 -0
  219. package/workflows/_uncategorized/reverse-ios-build.md +240 -0
  220. package/workflows/_uncategorized/reverse-ios-design.md +112 -0
  221. package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
  222. package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
  223. package/workflows/_uncategorized/reverse-ios.md +152 -0
  224. package/workflows/_uncategorized/safety-router.md +34 -0
  225. package/workflows/_uncategorized/teach.md +89 -0
  226. package/workflows/_uncategorized/verify-ui.md +53 -0
  227. package/workflows/_uncategorized/visualize-screenshots.md +34 -0
  228. package/workflows/ads/ads-analyst.md +201 -0
  229. package/workflows/ads/ads-audit.md +106 -0
  230. package/workflows/ads/ads-optimize.md +97 -0
  231. package/workflows/ads/ads-targeting.md +241 -0
  232. package/workflows/ads/adsExpert.md +160 -0
  233. package/workflows/ads/smali-ads-config.md +400 -0
  234. package/workflows/ads/smali-ads-flow.md +331 -0
  235. package/workflows/ads/smali-ads-interstitial.md +377 -0
  236. package/workflows/ads/smali-ads-native.md +382 -0
  237. package/workflows/context/teach.md +89 -0
  238. package/workflows/gitnexus.md +8 -8
  239. package/workflows/lifecycle/brainstorm.md +43 -0
  240. package/workflows/lifecycle/code.md +5 -0
  241. package/workflows/lifecycle/init.md +23 -5
  242. package/workflows/lifecycle/multi-model-pipeline.md +60 -0
  243. package/workflows/quality/ponytail-review.md +59 -0
  244. package/workflows/roles/critic.md +40 -0
  245. package/workflows/roles/safety-router.md +34 -0
@@ -0,0 +1,461 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ iOS Simulator Navigator - Smart Element Finder and Interactor
4
+
5
+ Finds and interacts with UI elements using accessibility data.
6
+ Prioritizes structured navigation over pixel-based interaction.
7
+
8
+ This script is the core automation tool for iOS simulator navigation. It finds
9
+ UI elements by text, type, or accessibility ID and performs actions on them
10
+ (tap, enter text). Uses semantic element finding instead of fragile pixel coordinates.
11
+
12
+ Key Features:
13
+ - Find elements by text (fuzzy or exact matching)
14
+ - Find elements by type (Button, TextField, etc.)
15
+ - Find elements by accessibility identifier
16
+ - Tap elements at their center point
17
+ - Enter text into text fields
18
+ - List all tappable elements on screen
19
+ - Automatic element caching for performance
20
+
21
+ Usage Examples:
22
+ # Find and tap a button by text
23
+ python scripts/navigator.py --find-text "Login" --tap --udid <device-id>
24
+
25
+ # Enter text into first text field
26
+ python scripts/navigator.py --find-type TextField --index 0 --enter-text "username" --udid <device-id>
27
+
28
+ # Tap element by accessibility ID
29
+ python scripts/navigator.py --find-id "submitButton" --tap --udid <device-id>
30
+
31
+ # List all interactive elements
32
+ python scripts/navigator.py --list --udid <device-id>
33
+
34
+ # Tap at specific coordinates (fallback)
35
+ python scripts/navigator.py --tap-at 200,400 --udid <device-id>
36
+
37
+ Output Format:
38
+ Tapped: Button "Login" at (320, 450)
39
+ Entered text in: TextField "Username"
40
+ Not found: text='Submit'
41
+
42
+ Navigation Priority (best to worst):
43
+ 1. Find by accessibility label/text (most reliable)
44
+ 2. Find by element type + index (good for forms)
45
+ 3. Find by accessibility ID (precise but app-specific)
46
+ 4. Tap at coordinates (last resort, fragile)
47
+
48
+ Technical Details:
49
+ - Uses IDB's accessibility tree via `idb ui describe-all --json --nested`
50
+ - Caches tree for multiple operations (call with force_refresh to update)
51
+ - Finds elements by parsing tree recursively
52
+ - Calculates tap coordinates from element frame center
53
+ - Uses `idb ui tap` for tapping, `idb ui text` for text entry
54
+ - Extracts data from AXLabel, AXValue, and AXUniqueId fields
55
+ """
56
+
57
+ import argparse
58
+ import json
59
+ import subprocess
60
+ import sys
61
+ from dataclasses import dataclass
62
+
63
+ from common import (
64
+ flatten_tree,
65
+ get_accessibility_tree,
66
+ get_device_screen_size,
67
+ resolve_udid,
68
+ transform_screenshot_coords,
69
+ )
70
+ from common.env_config import env_float, env_int
71
+
72
+ MAX_ELEMENTS_LISTED = env_int("IOS_SIM_MAX_ELEMENTS", 25)
73
+ TAP_SETTLE_SECONDS = env_float("IOS_SIM_TAP_SETTLE_MS", 500.0) / 1000.0
74
+
75
+
76
+ @dataclass
77
+ class Element:
78
+ """Represents a UI element from accessibility tree."""
79
+
80
+ type: str
81
+ label: str | None
82
+ value: str | None
83
+ identifier: str | None
84
+ frame: dict[str, float]
85
+ traits: list[str]
86
+ enabled: bool = True
87
+
88
+ @property
89
+ def center(self) -> tuple[int, int]:
90
+ """Calculate center point for tapping."""
91
+ x = int(self.frame["x"] + self.frame["width"] / 2)
92
+ y = int(self.frame["y"] + self.frame["height"] / 2)
93
+ return (x, y)
94
+
95
+ @property
96
+ def description(self) -> str:
97
+ """Human-readable description."""
98
+ label = self.label or self.value or self.identifier or "Unnamed"
99
+ return f'{self.type} "{label}"'
100
+
101
+
102
+ class Navigator:
103
+ """Navigates iOS apps using accessibility data."""
104
+
105
+ def __init__(self, udid: str | None = None):
106
+ """Initialize navigator with optional device UDID."""
107
+ self.udid = udid
108
+ self._tree_cache = None
109
+
110
+ def get_accessibility_tree(self, force_refresh: bool = False) -> dict:
111
+ """Get accessibility tree (cached for efficiency)."""
112
+ if self._tree_cache and not force_refresh:
113
+ return self._tree_cache
114
+
115
+ # Delegate to shared utility
116
+ self._tree_cache = get_accessibility_tree(self.udid, nested=True)
117
+ return self._tree_cache
118
+
119
+ def _flatten_tree(self, node: dict, elements: list[Element] | None = None) -> list[Element]:
120
+ """Flatten accessibility tree into list of elements."""
121
+ if elements is None:
122
+ elements = []
123
+
124
+ # Create element from node
125
+ if node.get("type"):
126
+ element = Element(
127
+ type=node.get("type", "Unknown"),
128
+ label=node.get("AXLabel"),
129
+ value=node.get("AXValue"),
130
+ identifier=node.get("AXUniqueId"),
131
+ frame=node.get("frame", {}),
132
+ traits=node.get("traits", []),
133
+ enabled=node.get("enabled", True),
134
+ )
135
+ elements.append(element)
136
+
137
+ # Process children
138
+ for child in node.get("children", []):
139
+ self._flatten_tree(child, elements)
140
+
141
+ return elements
142
+
143
+ def list_elements(self, force_refresh: bool = False) -> list[Element]:
144
+ """Get flat list of all UI elements on current screen."""
145
+ tree = self.get_accessibility_tree(force_refresh)
146
+ return self._flatten_tree(tree)
147
+
148
+ def find_element(
149
+ self,
150
+ text: str | None = None,
151
+ element_type: str | None = None,
152
+ identifier: str | None = None,
153
+ index: int = 0,
154
+ fuzzy: bool = True,
155
+ ) -> Element | None:
156
+ """
157
+ Find element by various criteria.
158
+
159
+ Args:
160
+ text: Text to search in label/value
161
+ element_type: Type of element (Button, TextField, etc.)
162
+ identifier: Accessibility identifier
163
+ index: Which matching element to return (0-based)
164
+ fuzzy: Use fuzzy matching for text
165
+
166
+ Returns:
167
+ Element if found, None otherwise
168
+ """
169
+ tree = self.get_accessibility_tree()
170
+ elements = self._flatten_tree(tree)
171
+
172
+ matches = []
173
+
174
+ for elem in elements:
175
+ # Skip disabled elements
176
+ if not elem.enabled:
177
+ continue
178
+
179
+ # Check type
180
+ if element_type and elem.type != element_type:
181
+ continue
182
+
183
+ # Check identifier (exact match)
184
+ if identifier and elem.identifier != identifier:
185
+ continue
186
+
187
+ # Check text (in label or value)
188
+ if text:
189
+ elem_text = (elem.label or "") + " " + (elem.value or "")
190
+ if fuzzy:
191
+ if text.lower() not in elem_text.lower():
192
+ continue
193
+ elif text not in (elem.label, elem.value):
194
+ continue
195
+
196
+ matches.append(elem)
197
+
198
+ if matches and index < len(matches):
199
+ return matches[index]
200
+
201
+ return None
202
+
203
+ def tap(self, element: Element) -> bool:
204
+ """Tap on an element."""
205
+ x, y = element.center
206
+ return self.tap_at(x, y)
207
+
208
+ def tap_at(self, x: int, y: int) -> bool:
209
+ """Tap at specific coordinates."""
210
+ cmd = ["idb", "ui", "tap", str(x), str(y)]
211
+ if self.udid:
212
+ cmd.extend(["--udid", self.udid])
213
+
214
+ try:
215
+ subprocess.run(cmd, capture_output=True, check=True)
216
+ return True
217
+ except subprocess.CalledProcessError:
218
+ return False
219
+
220
+ def enter_text(self, text: str, element: Element | None = None) -> bool:
221
+ """
222
+ Enter text into element or current focus.
223
+
224
+ Args:
225
+ text: Text to enter
226
+ element: Optional element to tap first
227
+
228
+ Returns:
229
+ Success status
230
+ """
231
+ # Tap element if provided
232
+ if element:
233
+ if not self.tap(element):
234
+ return False
235
+ # Small delay for focus
236
+ import time
237
+
238
+ time.sleep(TAP_SETTLE_SECONDS)
239
+
240
+ # Enter text
241
+ cmd = ["idb", "ui", "text", text]
242
+ if self.udid:
243
+ cmd.extend(["--udid", self.udid])
244
+
245
+ try:
246
+ subprocess.run(cmd, capture_output=True, check=True)
247
+ return True
248
+ except subprocess.CalledProcessError:
249
+ return False
250
+
251
+ def find_and_tap(
252
+ self,
253
+ text: str | None = None,
254
+ element_type: str | None = None,
255
+ identifier: str | None = None,
256
+ index: int = 0,
257
+ ) -> tuple[bool, str]:
258
+ """
259
+ Find element and tap it.
260
+
261
+ Returns:
262
+ (success, message) tuple
263
+ """
264
+ element = self.find_element(text, element_type, identifier, index)
265
+
266
+ if not element:
267
+ criteria = []
268
+ if text:
269
+ criteria.append(f"text='{text}'")
270
+ if element_type:
271
+ criteria.append(f"type={element_type}")
272
+ if identifier:
273
+ criteria.append(f"id={identifier}")
274
+ return (False, f"Not found: {', '.join(criteria)}")
275
+
276
+ if self.tap(element):
277
+ return (True, f"Tapped: {element.description} at {element.center}")
278
+ return (False, f"Failed to tap: {element.description}")
279
+
280
+ def find_and_enter_text(
281
+ self,
282
+ text_to_enter: str,
283
+ find_text: str | None = None,
284
+ element_type: str | None = "TextField",
285
+ identifier: str | None = None,
286
+ index: int = 0,
287
+ ) -> tuple[bool, str]:
288
+ """
289
+ Find element and enter text into it.
290
+
291
+ Returns:
292
+ (success, message) tuple
293
+ """
294
+ element = self.find_element(find_text, element_type, identifier, index)
295
+
296
+ if not element:
297
+ return (False, "TextField not found")
298
+
299
+ if self.enter_text(text_to_enter, element):
300
+ return (True, f"Entered text in: {element.description}")
301
+ return (False, "Failed to enter text")
302
+
303
+
304
+ def main():
305
+ """Main entry point."""
306
+ parser = argparse.ArgumentParser(description="Navigate iOS apps using accessibility data")
307
+
308
+ # Finding options
309
+ parser.add_argument("--find-text", help="Find element by text (fuzzy match)")
310
+ parser.add_argument("--find-exact", help="Find element by exact text")
311
+ parser.add_argument("--find-type", help="Element type (Button, TextField, etc.)")
312
+ parser.add_argument("--find-id", help="Accessibility identifier")
313
+ parser.add_argument("--index", type=int, default=0, help="Which match to use (0-based)")
314
+
315
+ # Action options
316
+ parser.add_argument("--tap", action="store_true", help="Tap the found element")
317
+ parser.add_argument("--tap-at", help="Tap at coordinates (x,y)")
318
+ parser.add_argument("--enter-text", help="Enter text into element")
319
+
320
+ # Coordinate transformation
321
+ parser.add_argument(
322
+ "--screenshot-coords",
323
+ action="store_true",
324
+ help="Interpret tap coordinates as from a screenshot (requires --screenshot-width/height)",
325
+ )
326
+ parser.add_argument(
327
+ "--screenshot-width",
328
+ type=int,
329
+ help="Screenshot width for coordinate transformation",
330
+ )
331
+ parser.add_argument(
332
+ "--screenshot-height",
333
+ type=int,
334
+ help="Screenshot height for coordinate transformation",
335
+ )
336
+
337
+ # Other options
338
+ parser.add_argument(
339
+ "--udid",
340
+ help="Device UDID (auto-detects booted simulator if not provided)",
341
+ )
342
+ parser.add_argument("--list", action="store_true", help="List all tappable elements")
343
+
344
+ args = parser.parse_args()
345
+
346
+ # Resolve UDID with auto-detection
347
+ try:
348
+ udid = resolve_udid(args.udid)
349
+ except RuntimeError as e:
350
+ print(f"Error: {e}")
351
+ sys.exit(1)
352
+
353
+ navigator = Navigator(udid=udid)
354
+
355
+ # List mode
356
+ if args.list:
357
+ elements = navigator.list_elements()
358
+
359
+ # Filter to tappable elements
360
+ tappable = [
361
+ e
362
+ for e in elements
363
+ if e.enabled and e.type in ["Button", "Link", "Cell", "TextField", "SecureTextField"]
364
+ ]
365
+
366
+ print(f"Tappable elements ({len(tappable)}):")
367
+ for elem in tappable[:MAX_ELEMENTS_LISTED]:
368
+ print(f" {elem.type}: \"{elem.label or elem.value or 'Unnamed'}\" {elem.center}")
369
+
370
+ if len(tappable) > MAX_ELEMENTS_LISTED:
371
+ print(f" ... and {len(tappable) - MAX_ELEMENTS_LISTED} more")
372
+ sys.exit(0)
373
+
374
+ # Direct tap at coordinates
375
+ if args.tap_at:
376
+ coords = args.tap_at.split(",")
377
+ if len(coords) != 2:
378
+ print("Error: --tap-at requires x,y format")
379
+ sys.exit(1)
380
+
381
+ x, y = int(coords[0]), int(coords[1])
382
+
383
+ # Handle coordinate transformation if requested
384
+ if args.screenshot_coords:
385
+ if not args.screenshot_width or not args.screenshot_height:
386
+ print(
387
+ "Error: --screenshot-coords requires --screenshot-width and --screenshot-height"
388
+ )
389
+ sys.exit(1)
390
+
391
+ device_w, device_h = get_device_screen_size(udid)
392
+ x, y = transform_screenshot_coords(
393
+ x,
394
+ y,
395
+ args.screenshot_width,
396
+ args.screenshot_height,
397
+ device_w,
398
+ device_h,
399
+ )
400
+ print(
401
+ f"Transformed screenshot coords ({coords[0]}, {coords[1]}) "
402
+ f"to device coords ({x}, {y})"
403
+ )
404
+
405
+ if navigator.tap_at(x, y):
406
+ print(f"Tapped at ({x}, {y})")
407
+ else:
408
+ print(f"Failed to tap at ({x}, {y})")
409
+ sys.exit(1)
410
+
411
+ # Find and tap
412
+ elif args.tap:
413
+ text = args.find_text or args.find_exact
414
+ fuzzy = args.find_text is not None
415
+
416
+ success, message = navigator.find_and_tap(
417
+ text=text, element_type=args.find_type, identifier=args.find_id, index=args.index
418
+ )
419
+
420
+ print(message)
421
+ if not success:
422
+ sys.exit(1)
423
+
424
+ # Find and enter text
425
+ elif args.enter_text:
426
+ text = args.find_text or args.find_exact
427
+
428
+ success, message = navigator.find_and_enter_text(
429
+ text_to_enter=args.enter_text,
430
+ find_text=text,
431
+ element_type=args.find_type or "TextField",
432
+ identifier=args.find_id,
433
+ index=args.index,
434
+ )
435
+
436
+ print(message)
437
+ if not success:
438
+ sys.exit(1)
439
+
440
+ # Just find (no action)
441
+ else:
442
+ text = args.find_text or args.find_exact
443
+ fuzzy = args.find_text is not None
444
+
445
+ element = navigator.find_element(
446
+ text=text,
447
+ element_type=args.find_type,
448
+ identifier=args.find_id,
449
+ index=args.index,
450
+ fuzzy=fuzzy,
451
+ )
452
+
453
+ if element:
454
+ print(f"Found: {element.description} at {element.center}")
455
+ else:
456
+ print("Element not found")
457
+ sys.exit(1)
458
+
459
+
460
+ if __name__ == "__main__":
461
+ main()