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